Phoenix ecto has_many :through inserting data

I have a problem with inserting associations when using :through. I have not been writing anything in Ecto or Phoenix for a quite a long time and spend few past hours trying to make it any without success.

I have 3 models:

defmodule Office.Client do
  ...
  schema "clients" do
    ...
    has_many :clients_phones, Office.Litigation.Schemas.ClientPhone
    has_many :phones, through: [:clients_phones, :phone]
    ...
  end
defmodule Office.Phone do
  ...
  schema "phones" do
    field :number, :string 
  end
  
  def changeset(%Phone{} = phone, params \\ %{}) do
    phone
    |> cast(params, [:number])
    |> validate_required([:number])
  end
end
defmodule Office.ClientPhone do
  ...
  schema "clients_phones" do
    belongs_to :client, Office.Litigation.Schemas.Client
    belongs_to :phone, Office.Litigation.Schemas.Phone
  end

What I want to do is having a client_id = x, and number = xxxxxxxxx assosiate this number with client through.

I don’t know who should ClientPhone changeset look like, and I don’t know how a function taking client_id and number should look like. As I said i spent few hours on this, and having many errors I don’t understand mostly from cast_assoc and put_assoc

I can do it like that:

client = Repo.get!(Client, 1)
phone = Repo.insert!(%Phone{number: "123"})
Repo.insert!(%ClientPhone{phone: phone, client: client})

But i don’t know how to do it using Repo.insert!(ClientPhone with not yet inserted phone number, and only client_id withouth selecting client from DB first) in one go :confused:

1 Like

What I really didnt understand is that when i do this:

params = %{"phone" => %{"number" => "123"}, "client_id" => 1}
%ClientPhone{} |> Ecto.Changeset.cast(params, [:client_id, :phone])

I got this:
** (RuntimeError) casting assocs with cast/3 is not supported, use cast_assoc/3 instead
but I thought the problem was with client_id and not the phone.

I went for a walk with kid and now i got it fairly quickly

x = "some_phone-555-number"
params = %{"client_id" => 1}

phone = %Phone{number: x}
%ClientPhone{} 
|> Ecto.Changeset.cast(params, [:client_id]) 
|> Ecto.Changeset.put_assoc(:phone, phone) 
|> Repo.insert!

And it suddenly works :wink:

And knowing that Ecto as Elixir is aiming to have better error msg’s and best ever documentation I think that listing key in that msg like
** (RuntimeError) casting assocs with cast/3 for :phone is not supported, use cast_assoc/3 instead

Would really help me solve it in no time (but again I might be wrong, and all I really needed was a walk :wink: )

Also having concrete end to end examples in documentation for every method would really help… but I don’t have any concrete example here, because not reading documentation on cast_assoc thoroughly is only my fault :stuck_out_tongue:

3 Likes

You should submit that as an issue on the ecto github. :slight_smile:

Actually improving the message is so simple that I’ve sent a pull request actually :wink:

4 Likes