Update Ecto association in Phoenix controller

I have a small Phoenix app that handles user registration.

Each user belongs to a Sector, and each Sector has many users. The schemas are defined as:

schema "users" do
   belongs_to :sector, MyApp.Sector
end

schema "sectors" do
   has_many :users, MyApp.User
end

In my Phoenix controller, I do some validations and load all the sectors, building a User association, like this:

sector =
  Repo.get!(Sector, user_params["sector_id"])
  |> Repo.preload(:users)

user = Ecto.build_assoc(sector, :users)
changeset = User.changeset(user, user_params)

I really don’t know if that’s the most idiomatic way, but it worked. Users are inserted belonging to the specified Sector.

The problem is when I try to update it. In traditional SQL, this means changing the sector_id from the User. I have tried the following:

user = Repo.get!(User, id) |> Repo.preload(:sector)
changeset = User.changeset(user, user_params)
changeset = 
  Ecto.Changeset.put_assoc(changeset, :sector, Repo.get!(params["sector_id"]))

or even:

user = Repo.get!(User, id) |> Repo.preload(:sector)
user = %{ user | sector: Repo.get!(Sector, params["sector_id"])
changeset = User.changeset(user, user_params)

But the sector that came from params is ignored in the update.
What am I doing wrong?

Thanks!

1 Like

I’m having some trouble understanding what you really want to do but I’m going to give it a try:

You’ve got a Users table with a Foreign Key sector_id that references the ID’s in the Sectors table, right?

If you want to update the sector of a given user you may do so:

user      = Repo.get!(User, id)
changeset = User.changeset(user, %{"sector_id" => new_id})
Repo.update!(changeset)
1 Like

Yes, my User has a sector_id. So, I need to update that? And that’s that? I’ll try it today, and post the results here.

1 Like

Sometimes, we ignore the simple, when we shouldn’t. It worked! Thank you!

1 Like

For people struggling with this in the future:
make sure to have :sector_id present in the list in function changeset/2 (the second argument of the cast function in pipeline inside).

4 Likes