Ecto.Schema.belongs_to

You're seeing just the macro belongs_to, go back to Ecto.Schema module for more information.
Link to this macro

belongs_to(name, queryable, opts \\ [])

View Source (macro)

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 associated has_one or has_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. See Ecto.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 set Comment.belongs_to :post, defaults: [public: true], then when using Ecto.build_assoc(comment, :post), the post will have post.public == true. Alternatively, you can set it to Comment.belongs_to :post, defaults: :update_post, which will invoke Comment.update_post(post, comment), or set it to a MFA tuple such as {Mod, fun, [arg3, arg4]}, which will invoke Mod.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" in has_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.