Packages
ecto
2.0.0-rc.2
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/embedded.ex
defmodule Ecto.Embedded do
@moduledoc false
alias __MODULE__
alias Ecto.Changeset
@type t :: %Embedded{cardinality: :one | :many,
on_replace: :raise | :mark_as_invalid | :delete,
field: atom,
owner: atom,
on_cast: nil | fun,
related: atom}
@behaviour Ecto.Changeset.Relation
@on_replace_opts [:raise, :mark_as_invalid, :delete]
defstruct [:cardinality, :field, :owner, :related, :on_cast, on_replace: :raise]
@doc """
Builds the embedded struct.
## Options
* `:cardinality` - tells if there is one embedded schema or many
* `:related` - name of the embedded schema
* `:on_replace` - the action taken on embeds when the embed is replaced
"""
def struct(module, name, opts) do
opts = Keyword.put_new(opts, :on_replace, :raise)
unless opts[:on_replace] in @on_replace_opts do
raise ArgumentError, "invalid `:on_replace` option for #{inspect name}. " <>
"The only valid options are: " <>
Enum.map_join(@on_replace_opts, ", ", &"`#{inspect &1}`")
end
struct(%Embedded{field: name, owner: module}, opts)
end
@doc """
Callback invoked by repository to prepare embeds.
It replaces the changesets for embeds inside changes
by actual structs so it can be dumped by adapters and
loaded into the schema struct afterwards.
"""
def prepare(changeset, adapter, repo_action) do
%{changes: changes, data: %{__struct__: schema}} = changeset
prepare(changeset, Map.take(changes, schema.__schema__(:embeds)), adapter, repo_action)
end
defp prepare(changeset, embeds, _adapter, _repo_action) when embeds == %{} do
changeset
end
defp prepare(%{types: types} = changeset, embeds, adapter, repo_action) do
update_in changeset.changes, fn changes ->
Enum.reduce embeds, changes, fn {name, changeset}, acc ->
{:embed, embed} = Map.get(types, name)
Map.put(acc, name, prepare_each(embed, changeset, adapter, repo_action))
end
end
end
defp prepare_each(%{cardinality: :one}, nil, _adapter, _repo_action) do
nil
end
defp prepare_each(%{cardinality: :one} = embed, changeset, adapter, repo_action) do
action = check_action!(changeset.action, repo_action, embed)
to_struct(changeset, action, embed, adapter)
end
defp prepare_each(%{cardinality: :many} = embed, changesets, adapter, repo_action) do
for changeset <- changesets,
action = check_action!(changeset.action, repo_action, embed),
prepared = to_struct(changeset, action, embed, adapter),
do: prepared
end
defp to_struct(%Changeset{valid?: false}, _action,
%{related: schema}, _adapter) do
raise ArgumentError, "changeset for embedded #{inspect schema} is invalid, " <>
"but the parent changeset was not marked as invalid"
end
defp to_struct(%Changeset{data: %{__struct__: actual}}, _action,
%{related: expected}, _adapter) when actual != expected do
raise ArgumentError, "expected changeset for embedded schema `#{inspect expected}`, " <>
"got: #{inspect actual}"
end
defp to_struct(%Changeset{changes: changes, data: model}, :update,
_embed, _adapter) when changes == %{} do
model
end
defp to_struct(%Changeset{}, :delete, _embed, _adapter) do
nil
end
defp to_struct(%Changeset{types: types} = changeset, action,
%{related: schema}, adapter) do
%{data: struct, changes: changes} = prepare(changeset, adapter, action)
changes
|> autogenerate_id(struct, action, schema, adapter)
|> autogenerate(action, schema, types, adapter)
|> apply_embeds(struct)
end
defp apply_embeds(changes, struct) do
struct(struct, changes)
end
defp check_action!(:replace, action, %{on_replace: :delete} = embed),
do: check_action!(:delete, action, embed)
defp check_action!(:update, :insert, %{related: schema}),
do: raise(ArgumentError, "got action :update in changeset for embedded #{inspect schema} while inserting")
defp check_action!(:delete, :insert, %{related: schema}),
do: raise(ArgumentError, "got action :delete in changeset for embedded #{inspect schema} while inserting")
defp check_action!(action, _, _), do: action
defp autogenerate_id(changes, _struct, :insert, schema, adapter) do
case schema.__schema__(:autogenerate_id) do
{key, :binary_id} ->
case Map.fetch(changes, key) do
{:ok, _} ->
changes
:error ->
{:ok, value} = Ecto.Type.adapter_load(adapter, :binary_id, adapter.autogenerate(:embed_id))
Map.put(changes, key, value)
end
other ->
raise ArgumentError, "embedded schema `#{inspect schema}` must have " <>
"`:binary_id` primary key with `autogenerate: true`, got: #{inspect other}"
end
end
defp autogenerate_id(changes, struct, :update, _schema, _adapter) do
for {_, nil} <- Ecto.primary_key(struct) do
raise Ecto.NoPrimaryKeyValueError, struct: struct
end
changes
end
defp autogenerate(changes, action, schema, types, adapter) do
Enum.reduce schema.__schema__(action_to_auto(action)), changes, fn
{k, {mod, fun, args}}, acc ->
case Map.fetch(acc, k) do
{:ok, _} -> acc
:error -> Map.put(acc, k, load!(types, k, apply(mod, fun, args), adapter))
end
end
end
defp action_to_auto(:insert), do: :autogenerate
defp action_to_auto(:update), do: :autoupdate
defp load!(types, k, v, adapter) do
type = Map.fetch!(types, k)
case Ecto.Type.adapter_load(adapter, type, v) do
{:ok, v} -> v
:error -> raise ArgumentError, "cannot load `#{inspect v}` as type #{inspect type}"
end
end
@doc """
Callback invoked to build relations.
"""
def build(%Embedded{related: related}) do
related.__struct__
end
end