Ecto.Schema.embeds_many
embeds_many
, go back to Ecto.Schema module for more information.
Indicates an embedding of many schemas.
The current schema has zero or more records of the other schema embedded inside of it. Embeds have all the things regular schemas have.
It is recommended to declare your embeds_many/3
field with type :map
in your migrations, instead of using {:array, :map}
. Ecto can work with
both maps and arrays as the container for embeds (and in most databases
map are represented as JSON which allows Ecto to choose what works best).
The embedded may or may not have a primary key. Ecto uses the primary keys
to detect if an embed is being updated or not. If a primary is not present
and you still want the list of embeds to be updated, :on_replace
must be
set to :delete
, forcing all current embeds to be deleted and replaced by
new ones whenever a new list of embeds is set.
For encoding and decoding of embeds, please read the docs for
embeds_one/3
.
Options
:on_replace
- The action taken on associations when the embed is replaced when casting or manipulating parent changeset. May be:raise
(default),:mark_as_invalid
, or:delete
. SeeEcto.Changeset
's section on related data for more info.:source
- Defines the name that is to be used in database for this field. This is useful when attaching to an existing database. The value should be an atom.
Examples
defmodule Order do
use Ecto.Schema
schema "orders" do
embeds_many :items, Item
end
end
defmodule Item do
use Ecto.Schema
embedded_schema do
field :title
end
end
# The items are loaded with the order
order = Repo.get!(Order, 42)
order.items #=> [%Item{...}, ...]
Adding and removal of embeds can only be done via the Ecto.Changeset
API so Ecto can properly track the embed life-cycle:
# Order has no items
order = Repo.get!(Order, 42)
order.items
# => []
items = [%Item{title: "Soap"}]
# Generate a changeset
changeset = Ecto.Changeset.change(order)
# Put a one or more new items
changeset = Ecto.Changeset.put_embed(changeset, :items, items)
# Update the order and fetch items
items = Repo.update!(changeset).items
# Items are generated with a unique identification
items
# => [%Item{id: "20a97d94-f79b-4e63-a875-85deed7719b7", title: "Soap"}]
Updating of embeds must be done using a changeset for each changed embed.
# Order has an existing items
order = Repo.get!(Order, 42)
order.items
# => [%Item{id: "20a97d94-f79b-4e63-a875-85deed7719b7", title: "Soap"}]
# Generate a changeset
changeset = Ecto.Changeset.change(order)
# Put the updated item as a changeset
current_item = List.first(order.items)
item_changeset = Ecto.Changeset.change(current_item, title: "Mujju's Soap")
order_changeset = Ecto.Changeset.put_embed(changeset, :items, [item_changeset])
# Update the order and fetch items
items = Repo.update!(order_changeset).items
# Item has the updated title
items
# => [%Item{id: "20a97d94-f79b-4e63-a875-85deed7719b7", title: "Mujju's Soap"}]
Inline embedded schema
The schema module can be defined inline in the parent schema in simple cases:
defmodule Parent do
use Ecto.Schema
schema "parents" do
field :name, :string
embeds_many :children, Child do
field :name, :string
field :age, :integer
end
end
end
Primary keys are automatically set up for embedded schemas as well,
defaulting to {:id, :binary_id, autogenerate: true}
. You can
customize it by passing a :primary_key
option with the same arguments
as @primary_key
(see the Schema attributes
section for more info).
Defining embedded schema in such a way will define a Parent.Child
module
with the appropriate struct. In order to properly cast the embedded schema.
When casting the inline-defined embedded schemas you need to use the :with
option of cast_embed/3
to provide the proper function to do the casting.
For example:
def changeset(schema, params) do
schema
|> cast(params, [:name])
|> cast_embed(:children, with: &child_changeset/2)
end
defp child_changeset(schema, params) do
schema
|> cast(params, [:name, :age])
end
Indicates an embedding of many schemas.
For options and examples see documentation of embeds_many/3
.