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]]
3 Comments
Another great episode!
Thanks for another helpful episode! Minor suggestion.
Note: “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.” should probably end as “links and users.” .
-