loader.ex (2949B)
1 defmodule Ecto.Schema.Loader do 2 @moduledoc false 3 4 alias Ecto.Schema.Metadata 5 6 @doc """ 7 Loads a struct to be used as a template in further operations. 8 """ 9 def load_struct(nil, _prefix, _source), do: %{} 10 11 def load_struct(schema, prefix, source) do 12 case schema.__schema__(:loaded) do 13 %{__meta__: %Metadata{prefix: ^prefix, source: ^source}} = struct -> 14 struct 15 16 %{__meta__: %Metadata{} = metadata} = struct -> 17 Map.put(struct, :__meta__, %{metadata | source: source, prefix: prefix}) 18 19 %{} = struct -> 20 struct 21 end 22 end 23 24 @doc """ 25 Loads data coming from the user/embeds into schema. 26 27 Assumes data does not all belongs to schema/struct 28 and that it may also require source-based renaming. 29 """ 30 def unsafe_load(schema, data, loader) do 31 types = schema.__schema__(:load) 32 struct = schema.__schema__(:loaded) 33 unsafe_load(struct, types, data, loader) 34 end 35 36 @doc """ 37 Loads data coming from the user/embeds into struct and types. 38 39 Assumes data does not all belongs to schema/struct 40 and that it may also require source-based renaming. 41 """ 42 def unsafe_load(struct, types, map, loader) when is_map(map) do 43 Enum.reduce(types, struct, fn pair, acc -> 44 {field, source, type} = field_source_and_type(pair) 45 46 case fetch_string_or_atom_field(map, source) do 47 {:ok, value} -> Map.put(acc, field, load!(struct, field, type, value, loader)) 48 :error -> acc 49 end 50 end) 51 end 52 53 @compile {:inline, field_source_and_type: 1, fetch_string_or_atom_field: 2} 54 defp field_source_and_type({field, {:source, source, type}}) do 55 {field, source, type} 56 end 57 58 defp field_source_and_type({field, type}) do 59 {field, field, type} 60 end 61 62 defp fetch_string_or_atom_field(map, field) when is_atom(field) do 63 case Map.fetch(map, Atom.to_string(field)) do 64 {:ok, value} -> {:ok, value} 65 :error -> Map.fetch(map, field) 66 end 67 end 68 69 @compile {:inline, load!: 5} 70 defp load!(struct, field, type, value, loader) do 71 case loader.(type, value) do 72 {:ok, value} -> 73 value 74 75 :error -> 76 raise ArgumentError, 77 "cannot load `#{inspect(value)}` as type #{inspect(type)} " <> 78 "for field `#{field}`#{error_data(struct)}" 79 end 80 end 81 82 defp error_data(%{__struct__: atom}) do 83 " in schema #{inspect(atom)}" 84 end 85 86 defp error_data(other) when is_map(other) do 87 "" 88 end 89 90 @doc """ 91 Dumps the given data. 92 """ 93 def safe_dump(struct, types, dumper) do 94 Enum.reduce(types, %{}, fn {field, {source, type}}, acc -> 95 value = Map.get(struct, field) 96 97 case dumper.(type, value) do 98 {:ok, value} -> 99 Map.put(acc, source, value) 100 :error -> 101 raise ArgumentError, "cannot dump `#{inspect value}` as type #{inspect type} " <> 102 "for field `#{field}` in schema #{inspect struct.__struct__}" 103 end 104 end) 105 end 106 end