Original

Associations

Associations in Ecto are used when two difference sources(tables) are linked via foreign keys.

A classic example of this setup is “Post has many comments”. First create the two tables in migrations

1
2
3
4
5
6
7
8
9
10
11
12
13
create table(:posts) do
add :title, :string
add :body, :text

timestamps()
end

create table(:comments) do
add :post_id, references(:posts)
add :body, :text

timestamps()
end

Each comment contains a post_id column that by default points to a post id

And now defined the schemas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
defmodule MyApp.Post do
use Ecto.Schema

schema "posts" do
field :title
field :body
has_many :comments, MyApp.Comment
timestamps
end
end

defmodule MyApp.Comment do
use Ecto.Schema

schema "comments" do
field :body
belongs_to :post, MyApp.Post
timestamps
end
end

Querying associations

One of the benefits of defining associations is taht they can be used in queries. For example:

1
Repo.all from p in Post, preload: [:comments]

Now all posts will be fetched from the database with their associated comments. The example above will perform two queries: one for loading all posts and another for loading all comments. This is often the most efficient way of loading associations from the database(even if two queries are performed) because we need to receive and parse only POSTS + COMMENTS results.

It is also possible to preload associations using joins while performing more complex queries. For example, imagine both posts and comments have votes and you want only comments with more votes than the post itself:

1
2
3
4
Repo.all from p in Post,
join: c in assoc(p, :comments),
where: c.votes > p.votes
preload: [comments: c]

Manipulating associations

While Ecto 2.0 allows you insert a post with multiple comments in one operation:

1
2
3
4
5
6
7
Repo.insert!(%Post{
title: "Hello",
body: "world",
comments: [
%Comment{body: "Excellent"}
]
})

Many times you may want to break it into distinct steps so you have more flexibility in managing those entries. For example, you could use changesets to build your posts and comments along the way

1
2
3
4
post = Ecto.Changeset.change(%Post{}, title: "Hello", body: "World")
comment = Ecto.Changeset.change(%Comment{}, body: "Excellent")
post_with_comments = Ecto.Changeset.put_assoc(post, :comments, [comment]) # Main Step
Repo.insert!(post_with_comments)

Or by handling each entry individually inside a transation:

1
2
3
4
5
6
Repo.transaction fn ->
post = Repo.insert!(%Post{title: "Hello", body: "World"})
# Build a comment from the post struct
comment = Ecto.build_assoc(post, :comments, body: "Execellent")
Repo.insert!(comment)
end

Ecto.build_assoc/3 builds the comment using the id currently set in the post struct. It is equivalnet to:

1
%Comment{post_id: post.id, body: "Execellent"}

The Ecto.build_assoc/3 function is specially useful in Phoenix controllers. For example when creating the post, one would do:

1
Ecto.build_assoc(current_user, :post)

As we likely want to associate the post to the user currently signed in the application. In another controller, we could build a comment for an existing post with:

1
Ecto.build_assoc(post, :comments)

Ecto does not provide functions like post.comments << comment that allows mixing persisted data with non-persisted data. The only macha