Creating and updating many to many associations in Ecto

Hi all, I am having a bit of a hard time saving many to many relationships here. Here is what I want:
I have a User model and a Role model where User has a many-to-many relationship with Role(already setup in User schema as appropriate). Assuming I have a user and some roles already created, and a list of some of these role ids that I would like to associate with the user, how do I go about this? What about next time if I want to update this user by removing some roles and adding new ones, will I have to manually remove the roles I no longer want for the user(like write a query or something) then add the new ones or will Ecto take care of this automatically, and if so, how do I go about it? Please help, am still very new to Elixir.

You have a couple different approaches here.

The simplest approach, if you are receiving a list of role_ids from the client, is to do this:

user
|> Ecto.Changeset.cast_assoc(params, [:name, ...])
|> Ecto.Changeset.put_assoc(load_roles(params))

def load_roles(params) do
  case params["role_ids"] || [] do
    [] -> []
    ids -> Repo.all from r in Role, where: r.id in ^ids
end

Another approach is to use a has_many :through. That’s because you don’t want to insert roles but you want to insert the association between a role and a user. So assuming this structure:

create table(:users) do
  ...
end

create table(:user_roles) do
  add :user_id, references(:users)
  add :role_id, references(:roles)
end

create table(:roles) do

end

And the schemas:

defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    has_many :roles, through: [:user_roles, :role]
    has_many :user_roles, MyApp.UserRole
    ...
  end
end

defmodule MyApp.UserRole do
  use Ecto.Schema

  schema "user_roles" do
    belongs_to :user, MyApp.User
    belongs_to :role, MyApp.Role
  end
end

defmodule MyApp.Role do
  use Ecto.Schema

  schema "roles" do
    ...
  end
end

You can now associate many roles to a user by using user |> cast(...) |> cast_assoc(:user_roles) as long as the parameters are sent in the following shape:

%{
  "user" => %{
    "user_roles" => [
      %{"role_id" => 1},
      %{"role_id" => 3},
      %{"role_id" => 5},
    ]
  }
}

Btw, we cover those cases in the Ecto 2.0 book, so if you haven’t grabbed your copy yet, you definitely should: http://pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0

8 Likes

Thanks @josevalim. I’ll try the first approach since am trying to avoid creating a model for every relationship I have, unless its really necessary. What about updating the user roles, how will it work?

put_assoc will take care of it for you. It will see each roles you have set, which roles are currently associated, and make sure to propagate the changes.

Ok, that’s cool. I’ll try it in the morning and post an update. Thanks a lot for replying.