Ecto belongs_to and has_many

In order to get the true power of what Ecto offers, let's add some associations to our schemas. Remember that our Linkly app is a basic Delicious clone that allows users to bookmark various links on the web and tag them with various topics.

Each link is just a URL, and each bookmark is a join table between users and links. A bookmark has a title, a link_id and a user_id in its schema.

defmodule Linkly.Bookmark do
  use Ecto.Schema
  alias Linkly.{Link, User}

  schema "bookmarks" do
    field :title
    field :link_id
    field :user_id

    timestamps()
  end
end

belongs_to vs has_one and has_many

The easy way to remember which schema "belongs" to the other and which one "has" the other, is that each schema belongs to whatever foreign keys are in it. In our case that means that bookmarks belong to both links and titles. By updating the schema to replace the foreign key lines with belongs_to/2, we can create the associations in Ecto:

  schema "bookmarks" do
    field :title
    belongs_to(:link, Link)
    belongs_to(:user, User)

    timestamps()
  end

Now, in IEx, when fetching a bookmark, we still see the link_id and user_id fields as before, but also fields for links and users themselves. The field for user displays the value #Ecto.Association.NotLoaded<association :user is not loaded> and the field for link is very similar. We can use Repo.preload to fetch those values:

b1 = Repo.get!(Bookmark, 1)
Repo.preload(b1, [:link, :user])

Setting up the has_many in User follows the same kind of logic. Bookmark schemas include user_id, so a user has bookmarks. We can set up the Ecto association by adding a line to user.ex:

defmodule Linkly.User do
  use Ecto.Schema
  alias Linkly.Bookmark

  schema "users" do
    field :about
    field :email
    field :username
    has_many(:bookmarks, Bookmark)

    timestamps()
  end
end

Similiarly, a link has many bookmarks. For example, the link waitbutwhy.com might be bookmarked by thousands of users.

defmodule Linkly.Link do
  use Ecto.Schema
  alias Linkly.Bookmark

  schema "links" do
    field :url
    has_many(:bookmarks, Bookmark)

    timestamps()
  end
end

Preloading a has_many association works exactly the same as a belongs_to or any other association:

link1 = Repo.get!(Link, 1)
Repo.preload(link1, [:bookmarks])

We can even do nested preloads by nesting the list we pass to the preload function:

Repo.preload(link1, [bookmarks: [:user]]
Back to index