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