Ecto.Schema.belongs_to
belongs_to
, go back to Ecto.Schema module for more information.
Indicates a one-to-one or many-to-one association with another schema.
The current schema belongs to zero or one records of the other schema. The other
schema often has a has_one
or a has_many
field with the reverse association.
You should use belongs_to
in the table that contains the foreign key. Imagine
a company <-> employee relationship. If the employee contains the company_id
in
the underlying database table, we say the employee belongs to company.
In fact, when you invoke this macro, a field with the name of foreign key is automatically defined in the schema for you.
Options
:foreign_key
- Sets the foreign key field name, defaults to the name of the association suffixed by_id
. For example,belongs_to :company
will define foreign key of:company_id
. The associatedhas_one
orhas_many
field in the other schema should also have its:foreign_key
option set with the same value.:references
- Sets the key on the other schema to be used for the association, defaults to::id
:define_field
- When false, does not automatically define a:foreign_key
field, implying the user is defining the field manually elsewhere:type
- Sets the type of automatically defined:foreign_key
. Defaults to::integer
and can be set per schema via@foreign_key_type
:on_replace
- The action taken on associations when the record is replaced when casting or manipulating parent changeset. May be:raise
(default),:mark_as_invalid
,:nilify
,:update
, or:delete
. SeeEcto.Changeset
's section on related data for more info.:defaults
- Default values to use when building the association. It may be a keyword list of options that override the association schema or a atom/{module, function, args}
that receives the struct and the owner as arguments. For example, if you setComment.belongs_to :post, defaults: [public: true]
, then when usingEcto.build_assoc(comment, :post)
, the post will havepost.public == true
. Alternatively, you can set it toComment.belongs_to :post, defaults: :update_post
, which will invokeComment.update_post(post, comment)
, or set it to a MFA tuple such as{Mod, fun, [arg3, arg4]}
, which will invokeMod.fun(post, comment, arg3, arg4)
:primary_key
- If the underlying belongs_to field is a primary key:source
- Defines the name that is to be used in database for this field:where
- A filter for the association. See "Filtering associations" inhas_many/3
.
Examples
defmodule Comment do
use Ecto.Schema
schema "comments" do
belongs_to :post, Post
end
end
# The post can come preloaded on the comment record
[comment] = Repo.all(from(c in Comment, where: c.id == 42, preload: :post))
comment.post #=> %Post{...}
If you need custom options on the underlying field, you can define the
field explicitly and then pass define_field: false
to belongs_to
:
defmodule Comment do
use Ecto.Schema
schema "comments" do
field :post_id, :integer, ... # custom options
belongs_to :post, Post, define_field: false
end
end
Polymorphic associations
One common use case for belongs to associations is to handle polymorphism. For example, imagine you have defined a Comment schema and you wish to use it for commenting on both tasks and posts.
Some abstractions would force you to define some sort of polymorphic association with two fields in your database:
* commentable_type
* commentable_id
The problem with this approach is that it breaks references in the database. You can't use foreign keys and it is very inefficient, both in terms of query time and storage.
In Ecto, we have three ways to solve this issue. The simplest is to define multiple fields in the Comment schema, one for each association:
* task_id
* post_id
Unless you have dozens of columns, this is simpler for the developer, more DB friendly and more efficient in all aspects.
Alternatively, because Ecto does not tie a schema to a given table, we can use separate tables for each association. Let's start over and define a new Comment schema:
defmodule Comment do
use Ecto.Schema
schema "abstract table: comments" do
# This will be used by associations on each "concrete" table
field :assoc_id, :integer
end
end
Notice we have changed the table name to "abstract table: comments". You can choose whatever name you want, the point here is that this particular table will never exist.
Now in your Post and Task schemas:
defmodule Post do
use Ecto.Schema
schema "posts" do
has_many :comments, {"posts_comments", Comment}, foreign_key: :assoc_id
end
end
defmodule Task do
use Ecto.Schema
schema "tasks" do
has_many :comments, {"tasks_comments", Comment}, foreign_key: :assoc_id
end
end
Now each association uses its own specific table, "posts_comments" and "tasks_comments", which must be created on migrations. The advantage of this approach is that we never store unrelated data together, also ensuring we keep database references fast and correct.
When using this technique, the only limitation is that you cannot build comments directly. For example, the command below
Repo.insert!(%Comment{})
will attempt to use the abstract table. Instead, one should use
Repo.insert!(build_assoc(post, :comments))
leveraging the Ecto.build_assoc/3
function. You can also
use Ecto.assoc/2
or pass a tuple in the query syntax
to easily retrieve associated comments to a given post or
task:
# Fetch all comments associated with the given task
Repo.all(Ecto.assoc(task, :comments))
Or all comments in a given table:
Repo.all from(c in {"posts_comments", Comment}), ...)
The third and final option is to use many_to_many/3
to
define the relationships between the resources. In this case,
the comments table won't have the foreign key, instead there
is an intermediary table responsible for associating the entries:
defmodule Comment do
use Ecto.Schema
schema "comments" do
# ...
end
end
In your posts and tasks:
defmodule Post do
use Ecto.Schema
schema "posts" do
many_to_many :comments, Comment, join_through: "posts_comments"
end
end
defmodule Task do
use Ecto.Schema
schema "tasks" do
many_to_many :comments, Comment, join_through: "tasks_comments"
end
end
See many_to_many/3
for more information on this particular approach.