zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

relation.ex (19365B)


      1 defmodule Ecto.Changeset.Relation do
      2   @moduledoc false
      3 
      4   require Logger
      5   alias Ecto.Changeset
      6   alias Ecto.Association.NotLoaded
      7 
      8   @type t :: %{required(:__struct__) => atom(),
      9                required(:cardinality) => :one | :many,
     10                required(:on_replace) => :raise | :mark_as_invalid | atom,
     11                required(:relationship) => :parent | :child,
     12                required(:ordered) => boolean,
     13                required(:owner) => atom,
     14                required(:related) => atom,
     15                required(:field) => atom,
     16                optional(atom()) => any()}
     17 
     18   @doc """
     19   Builds the related data.
     20   """
     21   @callback build(t, owner :: Ecto.Schema.t) :: Ecto.Schema.t
     22 
     23   @doc """
     24   Returns empty container for relation.
     25   """
     26   def empty(%{cardinality: cardinality}), do: cardinality_to_empty(cardinality)
     27 
     28   defp cardinality_to_empty(:one), do: nil
     29   defp cardinality_to_empty(:many), do: []
     30 
     31   @doc """
     32   Checks if the container can be considered empty.
     33   """
     34   def empty?(%{cardinality: _}, %NotLoaded{}), do: true
     35   def empty?(%{cardinality: :many}, []), do: true
     36   def empty?(%{cardinality: :many}, changes), do: filter_empty(changes) == []
     37   def empty?(%{cardinality: :one}, nil), do: true
     38   def empty?(%{}, _), do: false
     39 
     40   @doc """
     41   Filter empty changes
     42   """
     43   def filter_empty(changes) do
     44     Enum.filter(changes, fn
     45       %Changeset{action: action} when action in [:replace, :delete] -> false
     46       _ -> true
     47     end)
     48   end
     49 
     50   @doc """
     51   Applies related changeset changes
     52   """
     53   def apply_changes(%{cardinality: :one}, nil) do
     54     nil
     55   end
     56 
     57   def apply_changes(%{cardinality: :one}, changeset) do
     58     apply_changes(changeset)
     59   end
     60 
     61   def apply_changes(%{cardinality: :many}, changesets) do
     62     for changeset <- changesets,
     63       struct = apply_changes(changeset),
     64       do: struct
     65   end
     66 
     67   defp apply_changes(%Changeset{action: :delete}),  do: nil
     68   defp apply_changes(%Changeset{action: :replace}), do: nil
     69   defp apply_changes(changeset), do: Changeset.apply_changes(changeset)
     70 
     71   @doc """
     72   Loads the relation with the given struct.
     73 
     74   Loading will fail if the association is not loaded but the struct is.
     75   """
     76   def load!(%{__meta__: %{state: :built}}, %NotLoaded{__cardinality__: cardinality}) do
     77     cardinality_to_empty(cardinality)
     78   end
     79 
     80   def load!(struct, %NotLoaded{__field__: field}) do
     81     raise "attempting to cast or change association `#{field}` " <>
     82           "from `#{inspect struct.__struct__}` that was not loaded. Please preload your " <>
     83           "associations before manipulating them through changesets"
     84   end
     85 
     86   def load!(_struct, loaded), do: loaded
     87 
     88   @doc """
     89   Casts related according to the `on_cast` function.
     90   """
     91   def cast(%{cardinality: :one} = relation, _owner, nil, current, _on_cast) do
     92     case current && on_replace(relation, current) do
     93       :error -> {:error, {"is invalid", [type: expected_type(relation)]}}
     94       _ -> {:ok, nil, true}
     95     end
     96   end
     97 
     98   def cast(%{cardinality: :one} = relation, owner, params, current, on_cast) when is_list(params) do
     99     if Keyword.keyword?(params) do
    100       cast(relation, owner, Map.new(params), current, on_cast)
    101     else
    102       {:error, {"is invalid", [type: expected_type(relation)]}}
    103     end
    104   end
    105 
    106   def cast(%{cardinality: :many} = relation, owner, params, current, on_cast) when is_map(params) do
    107     params =
    108       params
    109       |> Enum.map(&key_as_int/1)
    110       |> Enum.sort
    111       |> Enum.map(&elem(&1, 1))
    112     cast(relation, owner, params, current, on_cast)
    113   end
    114 
    115   def cast(%{related: mod} = relation, owner, params, current, on_cast) do
    116     pks = mod.__schema__(:primary_key)
    117     fun = &do_cast(relation, owner, &1, &2, &3, on_cast)
    118     data_pk = data_pk(pks)
    119     param_pk = param_pk(mod, pks)
    120 
    121     with :error <- cast_or_change(relation, params, current, data_pk, param_pk, fun) do
    122       {:error, {"is invalid", [type: expected_type(relation)]}}
    123     end
    124   end
    125 
    126   defp do_cast(meta, owner, params, struct, allowed_actions, {module, fun, args})
    127        when is_atom(module) and is_atom(fun) and is_list(args) do
    128     on_cast = fn changeset, attrs ->
    129       apply(module, fun, [changeset, attrs | args])
    130     end
    131 
    132     do_cast(meta, owner, params, struct, allowed_actions, on_cast)
    133   end
    134 
    135   defp do_cast(meta, owner, params, nil = _struct, allowed_actions, on_cast) do
    136     {:ok,
    137       on_cast.(meta.__struct__.build(meta, owner), params)
    138       |> put_new_action(:insert)
    139       |> check_action!(allowed_actions)}
    140   end
    141 
    142   defp do_cast(relation, _owner, nil = _params, current, _allowed_actions, _on_cast) do
    143     on_replace(relation, current)
    144   end
    145 
    146   defp do_cast(_meta, _owner, params, struct, allowed_actions, on_cast) do
    147     {:ok,
    148       on_cast.(struct, params)
    149       |> put_new_action(:update)
    150       |> check_action!(allowed_actions)}
    151   end
    152 
    153   @doc """
    154   Wraps related structs in changesets.
    155   """
    156   def change(%{cardinality: :one} = relation, nil, current) do
    157     case current && on_replace(relation, current) do
    158       :error -> {:error, {"is invalid", [type: expected_type(relation)]}}
    159       _ -> {:ok, nil, true}
    160     end
    161   end
    162 
    163   def change(%{related: mod} = relation, value, current) do
    164     get_pks = data_pk(mod.__schema__(:primary_key))
    165     with :error <- cast_or_change(relation, value, current, get_pks, get_pks,
    166                                   &do_change(relation, &1, &2, &3)) do
    167       {:error, {"is invalid", [type: expected_type(relation)]}}
    168     end
    169   end
    170 
    171   # This may be an insert or an update, get all fields.
    172   defp do_change(relation, %{__struct__: _} = changeset_or_struct, nil, _allowed_actions) do
    173     changeset = Changeset.change(changeset_or_struct)
    174     {:ok,
    175      changeset
    176      |> assert_changeset_struct!(relation)
    177      |> put_new_action(action_from_changeset(changeset, nil))}
    178   end
    179 
    180   defp do_change(relation, nil, current, _allowed_actions) do
    181     on_replace(relation, current)
    182   end
    183 
    184   defp do_change(relation, %Changeset{} = changeset, _current, allowed_actions) do
    185     {:ok,
    186      changeset
    187      |> assert_changeset_struct!(relation)
    188      |> put_new_action(:update)
    189      |> check_action!(allowed_actions)}
    190   end
    191 
    192   defp do_change(_relation, %{__struct__: _} = struct, _current, allowed_actions) do
    193     {:ok,
    194      struct
    195      |> Ecto.Changeset.change
    196      |> put_new_action(:update)
    197      |> check_action!(allowed_actions)}
    198   end
    199 
    200   defp do_change(relation, changes, current, allowed_actions)
    201       when is_list(changes) or is_map(changes) do
    202     changeset = Ecto.Changeset.change(current || relation.__struct__.build(relation, nil), changes)
    203     changeset = put_new_action(changeset, action_from_changeset(changeset, current))
    204     do_change(relation, changeset, current, allowed_actions)
    205   end
    206 
    207   defp action_from_changeset(%{data: %{__meta__: %{state: state}}}, _current) do
    208     case state do
    209       :built   -> :insert
    210       :loaded  -> :update
    211       :deleted -> :delete
    212     end
    213   end
    214 
    215   defp action_from_changeset(_, nil) do
    216     :insert
    217   end
    218 
    219   defp action_from_changeset(_, _current) do
    220     :update
    221   end
    222 
    223   defp assert_changeset_struct!(%{data: %{__struct__: mod}} = changeset, %{related: mod}) do
    224     changeset
    225   end
    226   defp assert_changeset_struct!(%{data: data}, %{related: mod}) do
    227     raise ArgumentError, "expected changeset data to be a #{mod} struct, got: #{inspect data}"
    228   end
    229 
    230   @doc """
    231   Handles the changeset or struct when being replaced.
    232   """
    233   def on_replace(%{on_replace: :mark_as_invalid}, _changeset_or_struct) do
    234     :error
    235   end
    236 
    237   def on_replace(%{on_replace: :raise, field: name, owner: owner}, _) do
    238     raise """
    239     you are attempting to change relation #{inspect name} of
    240     #{inspect owner} but the `:on_replace` option of this relation
    241     is set to `:raise`.
    242 
    243     By default it is not possible to replace or delete embeds and
    244     associations during `cast`. Therefore Ecto requires the parameters
    245     given to `cast` to have IDs matching the data currently associated
    246     to #{inspect owner}. Failing to do so results in this error message.
    247 
    248     If you want to replace data or automatically delete any data
    249     not sent to `cast`, please set the appropriate `:on_replace`
    250     option when defining the relation. The docs for `Ecto.Changeset`
    251     covers the supported options in the "Associations, embeds and on
    252     replace" section.
    253 
    254     However, if you don't want to allow data to be replaced or
    255     deleted, only updated, make sure that:
    256 
    257       * If you are attempting to update an existing entry, you
    258         are including the entry primary key (ID) in the data.
    259 
    260       * If you have a relationship with many children, all children
    261         must be given on update.
    262 
    263     """
    264   end
    265 
    266   def on_replace(_relation, changeset_or_struct) do
    267     {:ok, Changeset.change(changeset_or_struct) |> put_new_action(:replace)}
    268   end
    269 
    270   defp raise_if_updating_with_struct!(%{field: name, owner: owner}, %{__struct__: _} = new) do
    271     raise """
    272     you have set that the relation #{inspect name} of #{inspect owner}
    273     has `:on_replace` set to `:update` but you are giving it a struct/
    274     changeset to put_assoc/put_change.
    275 
    276     Since you have set `:on_replace` to `:update`, you are only allowed
    277     to update the existing entry by giving updated fields as a map or
    278     keyword list or set it to nil.
    279 
    280     If you indeed want to replace the existing #{inspect name}, you have
    281     to change the foreign key field directly.
    282 
    283     Got: #{inspect new}
    284     """
    285   end
    286 
    287   defp raise_if_updating_with_struct!(_, _) do
    288     true
    289   end
    290 
    291   defp cast_or_change(%{cardinality: :one} = relation, value, current, current_pks_fun, new_pks_fun, fun)
    292        when is_map(value) or is_list(value) or is_nil(value) do
    293     single_change(relation, value, current_pks_fun, new_pks_fun, fun, current)
    294   end
    295 
    296   defp cast_or_change(%{cardinality: :many}, [], [], _current_pks, _new_pks, _fun) do
    297     {:ok, [], true}
    298   end
    299 
    300   defp cast_or_change(%{cardinality: :many} = relation, value, current, current_pks_fun, new_pks_fun, fun)
    301        when is_list(value) do
    302     {current_pks, current_map} = process_current(current, current_pks_fun, relation)
    303     %{unique: unique, ordered: ordered} = relation
    304     ordered = if ordered, do: current_pks, else: []
    305     map_changes(value, new_pks_fun, fun, current_map, [], true, true, unique && %{}, ordered)
    306   end
    307 
    308   defp cast_or_change(_, _, _, _, _, _), do: :error
    309 
    310   # single change
    311 
    312   defp single_change(_relation, nil, _current_pks_fun, _new_pks_fun, fun, current) do
    313     single_change(nil, current, fun, [:update, :delete], false)
    314   end
    315 
    316   defp single_change(_relation, new, _current_pks_fun, _new_pks_fun, fun, nil) do
    317     single_change(new, nil, fun, [:insert], false)
    318   end
    319 
    320   defp single_change(%{on_replace: on_replace} = relation, new, current_pks_fun, new_pks_fun, fun, current) do
    321     pk_values = new_pks_fun.(new)
    322 
    323     if (pk_values == current_pks_fun.(current) and pk_values != []) or
    324          (on_replace == :update and raise_if_updating_with_struct!(relation, new)) do
    325       single_change(new, current, fun, allowed_actions(pk_values), true)
    326     else
    327       case on_replace(relation, current) do
    328         {:ok, _changeset} -> single_change(new, nil, fun, [:insert], false)
    329         :error -> :error
    330       end
    331     end
    332   end
    333 
    334   defp single_change(new, current, fun, allowed_actions, skippable?) do
    335     case fun.(new, current, allowed_actions) do
    336       {:ok, %{action: :ignore}} ->
    337         :ignore
    338       {:ok, changeset} ->
    339         if skippable? and skip?(changeset) do
    340           :ignore
    341         else
    342           {:ok, changeset, changeset.valid?}
    343         end
    344       :error ->
    345         :error
    346     end
    347   end
    348 
    349   # map changes
    350 
    351   defp map_changes([changes | rest], new_pks, fun, current, acc, valid?, skip?, unique, ordered)
    352       when is_map(changes) or is_list(changes) do
    353     pk_values = new_pks.(changes)
    354     {struct, current, allowed_actions} = pop_current(current, pk_values)
    355 
    356     case fun.(changes, struct, allowed_actions) do
    357       {:ok, %{action: :ignore}} ->
    358         ordered = pop_ordered(pk_values, ordered)
    359         map_changes(rest, new_pks, fun, current, acc, valid?, skip?, unique, ordered)
    360       {:ok, changeset} ->
    361         changeset = maybe_add_error_on_pk(changeset, pk_values, unique)
    362         acc = [changeset | acc]
    363         valid? = valid? and changeset.valid?
    364         skip? = (struct != nil) and skip? and skip?(changeset)
    365         unique = unique && Map.put(unique, pk_values, true)
    366         ordered = pop_ordered(pk_values, ordered)
    367         map_changes(rest, new_pks, fun, current, acc, valid?, skip?, unique, ordered)
    368       :error ->
    369         :error
    370     end
    371   end
    372 
    373   defp map_changes([], _new_pks, fun, current, acc, valid?, skip?, _unique, ordered) do
    374     current_structs = Enum.map(current, &elem(&1, 1))
    375     skip? = skip? and ordered == []
    376     reduce_delete_changesets(current_structs, fun, Enum.reverse(acc), valid?, skip?)
    377   end
    378 
    379   defp map_changes(_params, _new_pks, _fun, _current, _acc, _valid?, _skip?, _unique, _ordered) do
    380     :error
    381   end
    382 
    383   defp pop_ordered(pk_values, [pk_values | tail]), do: tail
    384   defp pop_ordered(_pk_values, tail), do: tail
    385 
    386   defp maybe_add_error_on_pk(%{data: %{__struct__: schema}} = changeset, pk_values, unique) do
    387     if is_map(unique) and not missing_pks?(pk_values) and Map.has_key?(unique, pk_values) do
    388       Enum.reduce(schema.__schema__(:primary_key), changeset, fn pk, acc ->
    389         Changeset.add_error(acc, pk, "has already been taken")
    390       end)
    391     else
    392       changeset
    393     end
    394   end
    395 
    396   defp missing_pks?(pk_values) do
    397     pk_values == [] or Enum.any?(pk_values, &is_nil/1)
    398   end
    399 
    400   defp allowed_actions(pk_values) do
    401     if Enum.all?(pk_values, &is_nil/1) do
    402       [:insert, :update, :delete]
    403     else
    404       [:update, :delete]
    405     end
    406   end
    407 
    408   defp reduce_delete_changesets([struct | rest], fun, acc, valid?, _skip?) do
    409     case fun.(nil, struct, [:update, :delete]) do
    410       {:ok, changeset} ->
    411         valid? = valid? and changeset.valid?
    412         reduce_delete_changesets(rest, fun, [changeset | acc], valid?, false)
    413 
    414       :error ->
    415         :error
    416     end
    417   end
    418 
    419   defp reduce_delete_changesets([], _fun, _acc, _valid?, true), do: :ignore
    420   defp reduce_delete_changesets([], _fun, acc, valid?, false), do: {:ok, acc, valid?}
    421 
    422   # helpers
    423 
    424   defp check_action!(changeset, allowed_actions) do
    425     action = changeset.action
    426 
    427     cond do
    428       action in allowed_actions ->
    429         changeset
    430 
    431       action == :ignore ->
    432         changeset
    433 
    434       action == :insert ->
    435         raise "cannot insert related #{inspect changeset.data} " <>
    436                 "because it is already associated with the given struct"
    437 
    438       action == :replace ->
    439         raise "cannot replace related #{inspect changeset.data}. " <>
    440                 "This typically happens when you are calling put_assoc/put_embed " <>
    441                 "with the results of a previous put_assoc/put_embed/cast_assoc/cast_embed " <>
    442                 "operation, which is not supported. You must call such operations only once " <>
    443                 "per embed/assoc, in order for Ecto to track changes efficiently"
    444 
    445       true ->
    446         raise "cannot #{action} related #{inspect changeset.data} because " <>
    447                 "it already exists and it is not currently associated with the " <>
    448                 "given struct. Ecto forbids casting existing records through " <>
    449                 "the association field for security reasons. Instead, set " <>
    450                 "the foreign key value accordingly"
    451     end
    452   end
    453 
    454   defp key_as_int({key, val}) when is_binary(key) do
    455     case Integer.parse(key) do
    456       {key, ""} -> {key, val}
    457       _ -> {key, val}
    458     end
    459   end
    460   defp key_as_int(key_val), do: key_val
    461 
    462   defp process_current(nil, _get_pks, _relation),
    463     do: {[], %{}}
    464   defp process_current(current, get_pks, relation) do
    465     {pks, {map, counter}} =
    466       Enum.map_reduce(current, {%{}, 0}, fn struct, {acc, counter} ->
    467         pks = get_pks.(struct)
    468         key = if pks == [], do: map_size(acc), else: pks
    469         {pks, {Map.put(acc, key, struct), counter+ 1}}
    470       end)
    471 
    472     if map_size(map) != counter do
    473       Logger.warn """
    474       found duplicate primary keys for association/embed `#{inspect(relation.field)}` \
    475       in `#{inspect(relation.owner)}`. In case of duplicate IDs, only the last entry \
    476       with the same ID will be kept. Make sure that all entries in `#{inspect(relation.field)}` \
    477       have an ID and the IDs are unique between them
    478       """
    479     end
    480 
    481     {pks, map}
    482   end
    483 
    484   defp pop_current(current, pk_values) do
    485     case Map.pop(current, pk_values) do
    486       {nil, current} -> {nil, current, [:insert]}
    487       {struct, current} -> {struct, current, allowed_actions(pk_values)}
    488     end
    489   end
    490 
    491   defp data_pk(pks) do
    492     fn
    493       %Changeset{data: data} -> Enum.map(pks, &Map.get(data, &1))
    494       map when is_map(map) -> Enum.map(pks, &Map.get(map, &1))
    495       list when is_list(list) -> Enum.map(pks, &Keyword.get(list, &1))
    496     end
    497   end
    498 
    499   defp param_pk(mod, pks) do
    500     pks = Enum.map(pks, &{&1, Atom.to_string(&1), mod.__schema__(:type, &1)})
    501     fn params ->
    502       Enum.map pks, fn {atom_key, string_key, type} ->
    503         original = Map.get(params, string_key) || Map.get(params, atom_key)
    504         case Ecto.Type.cast(type, original) do
    505           {:ok, value} -> value
    506           _ -> original
    507         end
    508       end
    509     end
    510   end
    511 
    512   defp put_new_action(%{action: action} = changeset, new_action) when is_nil(action),
    513     do: Map.put(changeset, :action, new_action)
    514   defp put_new_action(changeset, _new_action),
    515     do: changeset
    516 
    517   defp skip?(%{valid?: true, changes: empty, action: :update}) when empty == %{},
    518     do: true
    519   defp skip?(_changeset),
    520     do: false
    521 
    522   defp expected_type(%{cardinality: :one}), do: :map
    523   defp expected_type(%{cardinality: :many}), do: {:array, :map}
    524 
    525   ## Surface changes on insert
    526 
    527   def surface_changes(%{changes: changes, types: types} = changeset, struct, fields) do
    528     {changes, errors} =
    529       Enum.reduce fields, {changes, []}, fn field, {changes, errors} ->
    530         case {struct, changes, types} do
    531           # User has explicitly changed it
    532           {_, %{^field => _}, _} ->
    533             {changes, errors}
    534 
    535           # Handle associations specially
    536           {_, _, %{^field => {tag, embed_or_assoc}}} when tag in [:assoc, :embed] ->
    537             # This is partly reimplementing the logic behind put_relation
    538             # in Ecto.Changeset but we need to do it in a way where we have
    539             # control over the current value.
    540             value = not_loaded_to_empty(Map.get(struct, field))
    541             empty = empty(embed_or_assoc)
    542             case change(embed_or_assoc, value, empty) do
    543               {:ok, change, _} when change != empty ->
    544                 {Map.put(changes, field, change), errors}
    545               {:error, error} ->
    546                 {changes, [{field, error}]}
    547               _ -> # :ignore or ok with change == empty
    548                 {changes, errors}
    549             end
    550 
    551           # Struct has a non nil value
    552           {%{^field => value}, _, %{^field => _}} when value != nil ->
    553             {Map.put(changes, field, value), errors}
    554 
    555           {_, _, _} ->
    556             {changes, errors}
    557         end
    558       end
    559 
    560     case errors do
    561       [] -> %{changeset | changes: changes}
    562       _  -> %{changeset | errors: errors ++ changeset.errors, valid?: false, changes: changes}
    563     end
    564   end
    565 
    566   defp not_loaded_to_empty(%NotLoaded{__cardinality__: cardinality}),
    567     do: cardinality_to_empty(cardinality)
    568 
    569   defp not_loaded_to_empty(loaded), do: loaded
    570 end