Many to Many

many_to_many association happens through a join schema or source, containing foreign keys to the associated schemas. For example, the association below:

# from MyApp.Post
many_to_many :tags, MyApp.Tag, join_through: "posts_tags"
#is backed by relational databases through a join table as follows:
[Post] <-> [posts_tags] <-> [Tag]
  id   <--   post_id
              tag_id    -->  id

Step by Step Guide

  1. Create Tables

create table("todo_lists") do
  add :title
  timestamps()
end

create table("todo_items") do
  add :description
  timestamps()
end

create table("todo_list_x_items", primary_key: false) do
  add :todo_item_id, references(:todo_items)
  add :todo_list_id, references(:todo_lists)
  #timestamps()
end

No id recommended for join tables, basically because it creates the a useless "primary" index tree in db data structure

  1. Create Schemas

defmodule MyApp.TodoList do
  use Ecto.Schema

  schema "todo_lists" do
    field :title
    many_to_many :todo_items, MyApp.TodoItem,
      join_through: MyApp.TodoListItem
    # timestamps()
  end
end

defmodule MyApp.TodoListXItem do
  use Ecto.Schema

  @primary_key false
  schema "todo_list_items" do
    belongs_to :todo_list, MyApp.TodoList
    belongs_to :todo_item, MyApp.TodoItem
    timestamps()
  end
end

defmodule MyApp.TodoItem do
  use Ecto.Schema

  schema "todo_items" do
    field :description
    timestamps()
  end
end
  1. Inserting Data

In this form

%{"todo_list" => %{
  "title" => "shipping list",
  "todo_items" => %{
    0 => %{"description" => "bread"},
    1 => %{"description" => "eggs"},
  }
}}

With these changesets

# In MyApp.TodoList
def changeset(struct, params \\ %{}) do
  struct
  |> Ecto.Changeset.cast(params, [:title])
  |> Ecto.Changeset.cast_assoc(:todo_items, required: true)
end

# And then in MyApp.TodoItem
def changeset(struct, params \\ %{}) do
  struct
  |> Ecto.Changeset.cast(params, [:description])
end

Last updated