{"id":4878,"date":"2015-08-12T05:00:35","date_gmt":"2015-08-12T08:00:35","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=4878"},"modified":"2016-12-01T09:32:06","modified_gmt":"2016-12-01T11:32:06","slug":"working-with-ecto-associations-and-embeds","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2015\/08\/working-with-ecto-associations-and-embeds\/","title":{"rendered":"Working with Ecto associations and embeds"},"content":{"rendered":"

This blog post aims to document how to work with associations in Ecto, covering how to read, insert, update and delete associations and embeds. At the end, we give a more complex example that uses Ecto associations to build nested forms in Phoenix.<\/p>\n

This article expects basic knowledge Ecto, particularly how repositories, schema and the query syntax work. You can learn more about those in Ecto docs<\/a>.<\/p>\n

\n Note: this article has been updated to Ecto 2.0 by using the new cast_assoc<\/code> and cast_embed<\/code> APIs.\n<\/p><\/blockquote>\n

Associations<\/h2>\n

Associations in Ecto are used when two different sources (tables) are linked via foreign keys.<\/p>\n

A classic example of this setup is “Post has many comments”. First create the two tables in migrations:<\/p>\n

create table(:posts) do\n  add :title, :string\n  add :body, :text\n  timestamps\nend\n\ncreate table(:comments) do\n  add :post_id, references(:posts)\n  add :body, :text\n  timestamps\nend\n<\/code><\/pre>\n

Each comment contains a post_id<\/code> column that by default points to a post id<\/code>.<\/p>\n

And now define the schemas:<\/p>\n

defmodule MyApp.Post do\n  use Ecto.Schema\n\n  schema \"posts\" do\n    field :title\n    field :body\n    has_many :comments, MyApp.Comment\n    timestamps\n  end\nend\n\ndefmodule MyApp.Comment do\n  use Ecto.Schema\n\n  schema \"comments\" do\n    field :body\n    belongs_to :post, MyApp.Post\n    timestamps\n  end\nend\n<\/code><\/pre>\n

All the schema definitions like field<\/code>, has_many<\/code> and others are defined in Ecto.Schema<\/code><\/a>.<\/p>\n

Similar to has_many\/3<\/code>, a schema can also invoke has_one\/3<\/code> when the parent has at most one child entry. For example, you could think of a metadata association where “Post has one metadata” and the “Metadata belongs to post”.<\/p>\n

The difference between has_one\/3<\/code> and belongs_to\/3<\/code> is that the foreign key is always defined in the schema that invokes belongs_to\/3<\/code>. You can think of the schema that calls has_*<\/code> as the parent schema and the one that invokes belongs_to<\/code> as the child one.<\/p>\n

Querying associations<\/h3>\n

One of the benefits of defining associations is that they can be used in queries. For example:<\/p>\n

Repo.all from p in Post,\n            preload: [:comments]\n<\/code><\/pre>\n

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.<\/p>\n

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:<\/p>\n

Repo.all from p in Post,\n            join: c in assoc(p, :comments),\n            where: c.votes > p.votes\n            preload: [comments: c]\n<\/code><\/pre>\n

The example above will now perform a single query, finding all posts and the respective comments that match the criteria. Because this query performs a JOIN, the number of results returned by the database is POSTS * COMMENTS, which Ecto then processes and associates all comments into the appropriate post.<\/p>\n

Finally, Ecto also allows data to be preloaded into structs after they have been loaded via the Repo.preload\/3<\/code> function:<\/p>\n

Repo.preload posts, :comments\n<\/code><\/pre>\n

This is specially handy because Ecto does not support lazy loading. If you invoke post.comments<\/code> and comments have not been preloaded, it will return Ecto.Association.NotLoaded<\/code>. Lazy loading is often a source of confusion and performance issues and Ecto pushes developers to do the proper thing. Therefore Repo.preload\/3<\/code> allow associations to be explicitly loaded anywhere, at any time.<\/p>\n

Manipulating associations<\/h3>\n

While Ecto 2.0 allows you insert a post with multiple comments in one operation:<\/p>\n

Repo.insert!(%Post{\n  title: \"Hello\",\n  body: \"world\",\n  comments: [\n    %Comment{body: \"Excellent!\"}\n  ]\n})\n<\/code><\/pre>\n

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:<\/p>\n

post = Ecto.Changeset.change(%Post{}, title: \"Hello\", body: \"world\")\ncomment = Ecto.Changeset.change(%Comment{}, body: \"Excellent!\")\npost_with_comments = Ecto.Changeset.put_assoc(post, :comments, [comment])\nRepo.insert!(post_with_comments)\n<\/code><\/pre>\n

Or by handling each entry individually inside a transaction:<\/p>\n

Repo.transaction fn ->\n  post = Repo.insert!(%Post{title: \"Hello\", body: \"world\"})\n\n  # Build a comment from the post struct\n  comment = Ecto.build_assoc(post, :comments, body: \"Excellent!\")\n\n  Repo.insert!(comment)\nend\n<\/code><\/pre>\n

Ecto.build_assoc\/3<\/code> builds the comment using the id currently set in the post struct. It is equivalent to:<\/p>\n

%Comment{post_id: post.id, body: \"Excellent!\"}\n<\/code><\/pre>\n

The Ecto.build_assoc\/3<\/code><\/a> function is specially useful in Phoenix<\/a> controllers. For example, when creating the post, one would do:<\/p>\n

Ecto.build_assoc(current_user, :post)\n<\/code><\/pre>\n

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:<\/p>\n

Ecto.build_assoc(post, :comments)\n<\/code><\/pre>\n

Ecto does not provide functions like post.comments << comment<\/code> that allows mixing persisted data with non-persisted data. The only mechanism for changing both post and comments at the same time is via changesets which we will explore when talking about embeds and nested associations.<\/p>\n

Deleting associations<\/h3>\n

When defining a has_many\/3<\/code>, has_one\/3<\/code> and friends, you can also pass a :on_delete<\/code> option that specifies which action should be performed on associations when the parent is deleted.<\/p>\n

has_many :comments, MyApp.Comment, on_delete: :delete_all\n<\/code><\/pre>\n

Besides the value above, :nilify_all<\/code> is also supported, with :nothing<\/code> being the default. Check has_many\/3<\/code><\/a> docs for more information.<\/p>\n

Embeds<\/h2>\n

Besides associations, Ecto also supports embeds in some databases. With embeds, the child is embedded inside the parent, instead of being stored in another table.<\/p>\n

Databases like PostgreSQL uses a mixture of JSONB (embeds_one\/3<\/code>) and ARRAY columns to provide this functionality (both JSONB and ARRAY are supported by default and first-class citizens in Ecto).<\/p>\n

Working with embeds is mostly the same as working with another field in a schema, except when it comes to manipulating them. Let’s see an example:<\/p>\n

defmodule MyApp.Permalink do\n  use Ecto.Schema\n\n  embedded_schema do\n    field :url\n    timestamps\n  end\nend\n\ndefmodule MyApp.Post do\n  use Ecto.Schema\n\n  schema \"posts\" do\n    field :title\n    field :body\n    has_many :comments, MyApp.Comment\n    embeds_many :permalinks, MyApp.Permalink\n    timestamps\n  end\nend\n<\/code><\/pre>\n

It is possible to insert a post with multiple permalinks directly:<\/p>\n

Repo.insert!(%Post{\n  title: \"Hello\",\n  permalinks: [\n    %Permalink{url: \"example.com\/thebest\"},\n    %Permalink{url: \"another.com\/mostaccessed\"}\n  ]\n})\n<\/code><\/pre>\n

Similar to associations, you may also manage those entries using changesets:<\/p>\n

# Generate a changeset for the post\nchangeset = Ecto.Changeset.change(post)\n\n# Let's track the new permalinks\nchangeset = Ecto.Changeset.put_embed(changeset, :permalinks,\n  [%Permalink{url: \"example.com\/thebest\"},\n   %Permalink{url: \"another.com\/mostaccessed\"}]\n)\n\n# Now insert the post with permalinks at once\npost = Repo.insert!(changeset)\n<\/code><\/pre>\n

Now if you want to replace or remove a particular permalink, you can work with permalinks as a collection and then just put it as a change again:<\/p>\n

# Remove all permalinks from example.com\npermalinks = Enum.reject post.permalinks, fn permalink ->\n  permalink.url =~ \"example.com\"\nend\n\n# Let's create a new changeset\nchangeset =\n  post\n  |> Ecto.Changeset.change\n  |> Ecto.Changeset.put_embed(:permalinks, permalinks)\n\n# And update the entry\npost = Repo.update!(changeset)\n<\/code><\/pre>\n

The beauty of working with changesets is that they keep track of all changes that will be sent to the database and we can introspect them at any time. For example, if we called before Repo.update!\/3<\/code>:<\/p>\n

IO.inspect(changeset.changes.permalinks)\n<\/code><\/pre>\n

We would see something like:<\/p>\n

[%Ecto.Changeset{action: :delete, changes: %{},\n                 model: %Permalink{url: \"example.com\/thebest\"}},\n %Ecto.Changeset{action: :update, changes: %{},\n                 model: %Permalink{url: \"another.com\/mostaccessed\"}}]\n<\/code><\/pre>\n

If, by any chance, we were also inserting a permalink in this operation, we would see another changeset there with action :insert<\/code>.<\/p>\n

Changesets contain a complete view of what is changing, how they are changing and you can manipulate them directly.<\/p>\n

Nested associations and embeds<\/h2>\n

The same way we have used changesets to manipulate embeds, we can also use them to change child associations at the same time we are manipulating the parent.<\/p>\n

One of the benefits of this feature is that we can use them to build nested forms in a Phoenix application. While nested forms in other languages and frameworks can be confusing and complex, Ecto uses changesets and explicit validations to provide a straightforward and simple way to manipulate multiple structs at once.<\/p>\n

To finish this post, let’s see an example of how to use what we have seen so far to work with nested associations in Phoenix.<\/p>\n

\n Note: you will need phoenix_ecto 3.0<\/code> in order to follow this example.\n<\/p><\/blockquote>\n

First, create a new Phoenix application if you haven’t yet. The Phoenix guides<\/a> can help you get started with that if it is your first time using Phoenix.<\/p>\n

The example we will build is a classic to do list, where a list has many items. Let’s generate the TodoList<\/code> resource:<\/p>\n

mix phoenix.gen.html TodoList todo_lists title\n<\/code><\/pre>\n

Follow the steps printed by the command above and after let’s generate a TodoItem<\/code> model:<\/p>\n

mix phoenix.gen.model TodoItem todo_items body:text todo_list_id:references:todo_lists\n<\/code><\/pre>\n

Open up the MyApp.TodoList<\/code> module at “web\/models\/todo_list.ex” and add the has_many<\/code> definition inside the schema<\/code> block:<\/p>\n

has_many :todo_items, MyApp.TodoItem\n<\/code><\/pre>\n

Next let’s also cast “todo_items” on the TodoList<\/code> changeset function:<\/p>\n

def changeset(todo_list, params \\\\ %{}) do\n  todo_list\n  |> cast(params, [:body])\n  |> cast_assoc(:todo_items, required: true)\nend\n<\/code><\/pre>\n

Note we are using cast_assoc<\/code> instead of put_assoc<\/code> in this example. Both functions are defined in Ecto.Changeset<\/code>. cast_assoc<\/code> (or cast_embed<\/code>) is used when you want to manage associations or embeds based on external parameters, such as the data received through Phoenix forms. In such cases, Ecto will compare the data existing in the struct with the data sent through the form and generate the proper operations. On the other hand, we use put_assoc<\/code> (or put_embed<\/code>) when we aleady have the associations (or embeds) as structs and changesets, and we simply want to tell Ecto to take those entries as is.<\/p>\n

Because we have added todo_items<\/code> as a required field, we are ready to submit them through the form. So let’s change our template to submit todo items too. Open up “web\/templates\/todo_list\/form.html.eex” and add the following between the title input and the submit button:<\/p>\n

<%= inputs_for f, :todo_items, fn i -> %>\n  <div class=\"form-group\">\n    <%= label i, :body, \"Task ##{i.index + 1}\", class: \"control-label\" %>\n    <%= text_input i, :body, class: \"form-control\" %>\n    <%= if message = i.errors[:body] do %>\n      <span class=\"help-block\"><%= message %><\/span>\n    <% end %>\n  <\/div>\n<% end %>\n<\/code><\/pre>\n

The inputs_for\/4<\/code> function comes from Phoenix.HTML.Form<\/a> and it allows us to generate fields for an association or an embed, emitting a new form struct (represented by the variable i<\/code> in the example above) for us to work with. Inside the inputs_for\/4<\/code> function, we generate a text input for each item.<\/p>\n

Now that we have changed the template, the final step is to change the new<\/code> action in the controller to include two empty todo items by default in the todo list:<\/p>\n

changeset = TodoList.changeset(%TodoList{todo_items: [%MyApp.TodoItem{}, %MyApp.TodoItem{}]})\n<\/code><\/pre>\n

Head to “http:\/\/localhost:4000\/todo_lists” and you can now create a todo list with both items! However, if you try to edit the newly created todo list, you should get an error:<\/p>\n

attempting to cast or change association :todo_items for MyApp.TodoList that was not loaded.\nPlease preload your associations before casting or changing the model.\n<\/code><\/pre>\n

As the error message says we need to preload the todo items
\nfor both edit<\/code> and update<\/code> actions in MyApp.TodoListController<\/code>.
\nOpen up your controller and change the following line on both actions:<\/p>\n

todo_list = Repo.get!(TodoList, id)\n<\/code><\/pre>\n

to<\/p>\n

todo_list = Repo.get!(TodoList, id) |> Repo.preload(:todo_items)\n<\/code><\/pre>\n

Now it should also be possible to update the todo items alongside the todo list.<\/p>\n

Both insert and update operations are ultimately powered by changesets, as we can see in our controller actions:<\/p>\n

changeset = TodoList.changeset(todo_list, todo_list_params)\n<\/code><\/pre>\n

All the benefits we have discussed regarding changesets in the previous section still apply here. By inspecting the changeset before calling Repo.insert<\/code> or Repo.update<\/code>, it is possible to see a snapshot of all the changes that are going to happen in the database.<\/p>\n

Not only that, the validation process behind changesets is explicit. Since we added todo_items<\/code> as a required field in the todo list model, every time we call MyApp.TodoList.changeset\/2<\/code>, MyApp.TodoItem.changeset\/2<\/code> will be called for every todo item sent through the form. The changesets returned for each todo item is then stored in the main todo list changeset (it is effectively a tree of changes).<\/p>\n

To help us build our intuition regarding changesets a bit more, let’s add some validations to todo items and also allow them to be deleted.<\/p>\n

Deleting todo items<\/h3>\n

Open up the MyApp.TodoItem<\/code> at “web\/models\/todo_item.ex” and add a virtual field named :delete<\/code> to the schema:<\/p>\n

field :delete, :boolean, virtual: true\n<\/code><\/pre>\n

As we know the MyApp.TodoItem.changeset\/2<\/code> function is the one invoked by default when manipulating todo items through todo lists. So let’s change it to the following:<\/p>\n

@required_fields ~w(body)\n@optional_fields ~w(delete) # 1. Make delete an optional field\n\ndef changeset(todo_item, params \\\\ :empty) do\n  todo_item\n  |> cast(params, @required_fields, @optional_fields)\n  |> validate_length(:body, min: 3)\n  |> mark_for_deletion() # 2. Call mark for deletion\nend\n\ndefp mark_for_deletion(changeset) do\n  # If delete was set and it is true, let's change the action\n  if get_change(changeset, :delete) do\n    %{changeset | action: :delete}\n  else\n    changeset\n  end\nend\n<\/code><\/pre>\n

We have added a call to validate_length<\/code> as well as a private function that checks if the :delete<\/code> field changed and, if so, we mark the changeset action to be :delete<\/code>.<\/p>\n

The functions cast<\/code>, validate_length<\/code>, get_change<\/code> and more are all part of the Ecto.Changeset<\/code> module<\/a>, which is automatically imported into Ecto models.<\/p>\n

Let’s now change our view to include the delete field. Add the following somewhere inside the inputs_for\/4<\/code> call in “web\/templates\/todo_list\/form.html.eex”:<\/p>\n

<%= if i.model.id do %>\n  <span class=\"pull-right\">\n    <%= label i, :delete, \"Delete?\", class: \"control-label\" %>\n    <%= checkbox i, :delete %>\n  <\/span>\n<% end %>\n<\/code><\/pre>\n

And that’s all. Our todo items should now validate the body as well as allow deletion on update pages!<\/p>\n

Notice we had control over the changeset and validations at all times. There are no special fields for deletion or implicit validation. Still, we were able to wire everything up with very few lines of codes.<\/p>\n

And while the default is to call MyApp.TodoItem.changeset\/2<\/code>, it is possible to customize the function to be invoked when casting todo items from the todo list changeset via the :with<\/code> option:<\/p>\n

|> cast_assoc(:todo_items, required: true, with: &custom_changeset\/2)\n<\/code><\/pre>\n

Therefore if an association has different validation rules depending if it is sent as part of a nested association or when managed directly, we can easily keep those business rules apart by providing two different changeset functions. And because we just use functions, all the way down, they are easy to compose and test.<\/p>\n

Summing up<\/h2>\n

In this blog post we have learned the foundations for working with associations and embeds, up to a more complex example using nested associations. If you want to further customize their behavior, read the docs for declaring the associations\/embeds in Ecto.Schema<\/code><\/a> or how to further manipulate changesets via Ecto.Changeset<\/code><\/a>.<\/p>\n

When it comes to the view, you can find more information on the Phoenix.HTML<\/code> project, specially under the Phoenix.HTML.Form<\/code><\/a>, where the inputs_for\/4<\/code> function is defined.<\/p>\n


\n \"Subscribe
\n<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"

This blog post aims to document how to work with associations in Ecto, covering how to read, insert, update and delete associations and embeds. At the end, we give a more complex example that uses Ecto associations to build nested forms in Phoenix. This article expects basic knowledge Ecto, particularly how repositories, schema and the … \u00bb<\/a><\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[238,143],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4878"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=4878"}],"version-history":[{"count":16,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4878\/revisions"}],"predecessor-version":[{"id":5904,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4878\/revisions\/5904"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=4878"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=4878"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=4878"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}