Creating and updating many to many associations in Ecto

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

9 Likes