Search code examples
elixirphoenix-frameworkecto

Casting error in Ecto changesets


I have the following model (for a chat room) which I'm having problem with while using changesets:

defmodule Elemental.TxChat.Room do
  use Elemental.TxChat.Web, :model

  schema "rooms" do
    field :name, :string

    # Foreign key indicating which user created this room
    # One user can create any number of rooms 
    belongs_to :created_by, Elemental.TxChat.User
    field :created_from_app, :integer

    many_to_many :members, Elemental.TxChat.User, join_through: "rooms_users"

    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :created_by, :created_from_app])
    |> validate_required([:name, :created_by, :created_from_app])
  end
end

I then thought, I'd try to pass an empty struct to the changeset and see the error it produced. So I did (after aliasing):

iex(4)> c = Room.changeset(%Room{}, %{})
** (RuntimeError) casting assocs with cast/3 is not supported, use cast_assoc/3 instead
       (ecto) lib/ecto/changeset.ex:440: Ecto.Changeset.type!/2
       (ecto) lib/ecto/changeset.ex:415: Ecto.Changeset.process_param/8
     (elixir) lib/enum.ex:1151: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
     (elixir) lib/enum.ex:1151: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
       (ecto) lib/ecto/changeset.ex:391: Ecto.Changeset.do_cast/7
    (tx_chat) web/models/room.ex:19: Elemental.TxChat.Room.changeset/2

I then thought that models that had belongs_to, etc., worked with cast_assoc, so I changed the function name. And now:

iex(4)> c = Room.changeset(%Room{}, %{})
** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast_assoc/3
       (ecto) lib/ecto/changeset.ex:518: Ecto.Changeset.cast_assoc(%Elemental.TxChat.Room{__meta__: #Ecto.Schema.Metadata<:built, "rooms">, created_by: #Ecto.Association.NotLoaded<association :created_by is not loaded>, created_by_id: nil, created_from_app: nil, id: nil, inserted_at: nil, members: #Ecto.Association.NotLoaded<association :members is not loaded>, name: nil, updated_at: nil}, %{}, [:name, :created_by, :created_from_app])
    (tx_chat) web/models/room.ex:19: Elemental.TxChat.Room.changeset/2

What am I doing wrong?


Solution

  • As clarified in the comments, you want to associate an existing User with a new Room. You can do this by adding created_by_id in the list of fields passed to cast, an assoc_constraint(:created_by) and then sending the user's id in the key created_by_id to Room.changeset/2. This should work:

    def changeset(struct, params \\ %{}) do
      struct
      |> cast(params, [:name, :created_by_id, :created_from_app])
      |> validate_required([:name, :created_by_id, :created_from_app])
      |> assoc_constraint(:created_by)
    end
    

    And then to create a Room:

    Room.changeset(%Room{}, %{name: "foo", created_by_id: 1, created_from_app: 1})
    |> Repo.insert