Packages

A toolkit for data mapping and language integrated query for Elixir

Current section

Files

Jump to
ecto_tc lib ecto schema loader.ex
Raw

lib/ecto/schema/loader.ex

defmodule Ecto.Schema.Loader do
@moduledoc false
alias Ecto.Schema.Metadata
@doc """
Loads a struct to be used as a template in further operations.
"""
def load_struct(nil, _prefix, _source), do: %{}
def load_struct(schema, prefix, source) do
case schema.__schema__(:loaded) do
%{__meta__: %Metadata{prefix: ^prefix, source: ^source}} = struct ->
struct
%{__meta__: %Metadata{} = metadata} = struct ->
Map.put(struct, :__meta__, %{metadata | source: source, prefix: prefix})
%{} = struct ->
struct
end
end
@doc """
Loads data coming from the user/embeds into schema.
Assumes data does not all belongs to schema/struct
and that it may also require source-based renaming.
"""
def unsafe_load(schema, data, loader) do
types = schema.__schema__(:load)
struct = schema.__schema__(:loaded)
unsafe_load(struct, types, data, loader)
end
@doc """
Loads data coming from the user/embeds into struct and types.
Assumes data does not all belongs to schema/struct
and that it may also require source-based renaming.
"""
def unsafe_load(struct, types, map, loader) when is_map(map) do
Enum.reduce(types, struct, fn pair, acc ->
{field, source, type} = field_source_and_type(pair)
case fetch_string_or_atom_field(map, source) do
{:ok, value} -> Map.put(acc, field, load!(struct, field, type, value, loader))
:error -> acc
end
end)
end
@compile {:inline, field_source_and_type: 1, fetch_string_or_atom_field: 2}
defp field_source_and_type({field, {:source, source, type}}) do
{field, source, type}
end
defp field_source_and_type({field, type}) do
{field, field, type}
end
defp fetch_string_or_atom_field(map, field) when is_atom(field) do
case Map.fetch(map, Atom.to_string(field)) do
{:ok, value} -> {:ok, value}
:error -> Map.fetch(map, field)
end
end
@compile {:inline, load!: 5}
defp load!(struct, field, type, value, loader) do
case loader.(type, value) do
{:ok, value} ->
value
:error ->
raise ArgumentError,
"cannot load `#{inspect(value)}` as type #{inspect(type)} " <>
"for field `#{field}`#{error_data(struct)}"
end
end
defp error_data(%{__struct__: atom}) do
" in schema #{inspect(atom)}"
end
defp error_data(other) when is_map(other) do
""
end
@doc """
Dumps the given data.
"""
def safe_dump(struct, types, dumper) do
Enum.reduce(types, %{}, fn {field, {source, type}}, acc ->
value = Map.get(struct, field)
case dumper.(type, value) do
{:ok, value} ->
Map.put(acc, source, value)
:error ->
raise ArgumentError, "cannot dump `#{inspect value}` as type #{inspect type} " <>
"for field `#{field}` in schema #{inspect struct.__struct__}"
end
end)
end
end