@@ -9,11 +9,11 @@ defmodule Ecto.Changeset.Relation do
99 required ( :__struct__ ) => atom ( ) ,
1010 required ( :cardinality ) => :one | :many ,
1111 required ( :on_replace ) => :raise | :mark_as_invalid | atom ,
12- required ( :relationship ) => :parent | :child ,
13- required ( :ordered ) => boolean ,
14- required ( :owner ) => atom ,
15- required ( :related ) => atom ,
1612 required ( :field ) => atom ,
13+ optional ( :unique ) => boolean ,
14+ optional ( :ordered ) => boolean ,
15+ optional ( :owner ) => atom ,
16+ optional ( :related ) => atom ,
1717 optional ( atom ( ) ) => any ( )
1818 }
1919
@@ -106,7 +106,7 @@ defmodule Ecto.Changeset.Relation do
106106 end
107107 end
108108
109- def cast ( % { related: mod } = relation , owner , params , current , on_cast ) do
109+ def cast ( relation , owner , params , current , on_cast ) do
110110 on_cast =
111111 case on_cast do
112112 { module , fun , args } ->
@@ -119,16 +119,16 @@ defmodule Ecto.Changeset.Relation do
119119 end
120120
121121 nil ->
122- on_cast_default ( mod )
122+ on_cast_default ( relation )
123123
124124 fun when is_function ( fun ) ->
125125 fun
126126 end
127127
128- pks = mod . __schema__ ( :primary_key )
128+ pks = primary_keys ( relation )
129129 fun = & do_cast ( relation , owner , & 1 , & 2 , & 3 , & 4 , on_cast )
130130 data_pk = data_pk ( pks )
131- param_pk = param_pk ( mod , pks )
131+ param_pk = param_pk ( relation , pks )
132132
133133 with :error <- cast_or_change ( relation , params , current , data_pk , param_pk , fun ) do
134134 { :error , { "is invalid" , [ type: expected_type ( relation ) ] } }
@@ -181,8 +181,9 @@ defmodule Ecto.Changeset.Relation do
181181 end
182182 end
183183
184- def change ( % { related: mod } = relation , value , current ) do
185- get_pks = data_pk ( mod . __schema__ ( :primary_key ) )
184+ def change ( relation , value , current ) do
185+ pks = primary_keys ( relation )
186+ get_pks = data_pk ( pks )
186187 fun = & do_change ( relation , & 1 , & 2 , & 3 , & 4 )
187188
188189 with :error <- cast_or_change ( relation , value , current , get_pks , get_pks , fun ) do
@@ -253,23 +254,27 @@ defmodule Ecto.Changeset.Relation do
253254 raise ArgumentError , "expected changeset data to be a #{ mod } struct, got: #{ inspect ( data ) } "
254255 end
255256
257+ defp assert_changeset_struct! ( changeset , _relation ) do
258+ # For relations without a related module, any data type is accepted
259+ changeset
260+ end
261+
256262 @ doc """
257263 Handles the changeset or struct when being replaced.
258264 """
259265 def on_replace ( % { on_replace: :mark_as_invalid } , _changeset_or_struct ) do
260266 :error
261267 end
262268
263- def on_replace ( % { on_replace: :raise , field: name , owner: owner } , _ ) do
269+ def on_replace ( % { on_replace: :raise , field: name } = relation , _ ) do
264270 raise """
265- you are attempting to change relation #{ inspect ( name ) } of
266- #{ inspect ( owner ) } but the `:on_replace` option of this relation
267- is set to `:raise`.
271+ you are attempting to change relation #{ pretty_relation ( relation ) }
272+ but the `:on_replace` option of this relation is set to `:raise`.
268273
269274 By default it is not possible to replace or delete embeds and
270275 associations during `cast`. Therefore Ecto requires the parameters
271276 given to `cast` to have IDs matching the data currently associated
272- to #{ inspect ( owner ) } . Failing to do so results in this error message.
277+ in #{ inspect ( name ) } . Failing to do so results in this error message.
273278
274279 If you want to replace data or automatically delete any data
275280 not sent to `cast`, please set the appropriate `:on_replace`
@@ -293,9 +298,9 @@ defmodule Ecto.Changeset.Relation do
293298 { :ok , Changeset . change ( changeset_or_struct ) |> put_new_action ( :replace ) }
294299 end
295300
296- defp raise_if_updating_with_struct! ( % { field: name , owner: owner } , % { __struct__: _ } = new ) do
301+ defp raise_if_updating_with_struct! ( % { field: name } = relation , % { __struct__: _ } = new ) do
297302 raise """
298- you have set that the relation #{ inspect ( name ) } of #{ inspect ( owner ) }
303+ you have set that the relation #{ pretty_relation ( relation ) }
299304 has `:on_replace` set to `:update` but you are giving it a struct/
300305 changeset to put_assoc/put_change.
301306
@@ -316,6 +321,9 @@ defmodule Ecto.Changeset.Relation do
316321 true
317322 end
318323
324+ defp pretty_relation ( % { field: name , owner: owner } ) , do: "#{ inspect ( name ) } of #{ inspect ( owner ) } "
325+ defp pretty_relation ( % { field: name } ) , do: inspect ( name )
326+
319327 defp cast_or_change (
320328 % { cardinality: :one } = relation ,
321329 value ,
@@ -342,12 +350,11 @@ defmodule Ecto.Changeset.Relation do
342350 )
343351 when is_list ( value ) do
344352 { current_pks , current_map } = process_current ( current , current_pks_fun , relation )
345- % { unique: unique , ordered: ordered , related: mod } = relation
346- change_pks_fun = change_pk ( mod . __schema__ ( :primary_key ) )
347- ordered = if ordered , do: current_pks , else: [ ]
353+ change_pks_fun = change_pk ( primary_keys ( relation ) )
354+ ordered = if Map . get ( relation , :ordered , false ) , do: current_pks , else: [ ]
355+ unique = if Map . get ( relation , :unique , false ) , do: % { } , else: nil
348356 funs = { new_pks_fun , change_pks_fun , fun }
349-
350- map_changes ( value , current_map , [ ] , true , true , unique && % { } , 0 , ordered , funs )
357+ map_changes ( value , current_map , [ ] , true , true , unique , 0 , ordered , funs )
351358 end
352359
353360 defp cast_or_change ( _ , _ , _ , _ , _ , _ ) , do: :error
@@ -405,18 +412,7 @@ defmodule Ecto.Changeset.Relation do
405412 case fun . ( changes , struct , allowed_actions , idx ) do
406413 { :ok , % { action: :ignore } } ->
407414 ordered = pop_ordered ( pk_values , ordered )
408-
409- map_changes (
410- rest ,
411- current ,
412- acc ,
413- valid? ,
414- skip? ,
415- unique ,
416- idx + 1 ,
417- ordered ,
418- funs
419- )
415+ map_changes ( rest , current , acc , valid? , skip? , unique , idx + 1 , ordered , funs )
420416
421417 { :ok , changeset } ->
422418 pk_values = change_pks . ( changeset )
@@ -485,7 +481,10 @@ defmodule Ecto.Changeset.Relation do
485481
486482 # helpers
487483
488- defp on_cast_default ( module ) do
484+ defp primary_keys ( % { related: mod } ) , do: mod . __schema__ ( :primary_key )
485+ defp primary_keys ( _relation ) , do: [ ]
486+
487+ defp on_cast_default ( % { related: module } ) do
489488 fn struct , params ->
490489 try do
491490 module . changeset ( struct , params )
@@ -514,6 +513,14 @@ defmodule Ecto.Changeset.Relation do
514513 end
515514 end
516515
516+ defp on_cast_default ( % { field: field } ) do
517+ raise ArgumentError , """
518+ the relation `#{ field } ` does not have a schema module and requires an explicit :with option.
519+
520+ Please pass a changeset function of arity 2 (or arity 3 for cardinality :many) using the :with option.
521+ """
522+ end
523+
517524 defp check_action! ( changeset , allowed_actions ) do
518525 action = changeset . action
519526
@@ -582,7 +589,7 @@ defmodule Ecto.Changeset.Relation do
582589 end
583590 end
584591
585- defp param_pk ( mod , pks ) do
592+ defp param_pk ( % { related: mod } , pks ) do
586593 pks = Enum . map ( pks , & { & 1 , Atom . to_string ( & 1 ) , mod . __schema__ ( :type , & 1 ) } )
587594
588595 fn params ->
@@ -597,6 +604,10 @@ defmodule Ecto.Changeset.Relation do
597604 end
598605 end
599606
607+ defp param_pk ( _relation , [ ] ) do
608+ fn _params -> [ ] end
609+ end
610+
600611 defp change_pk ( pks ) do
601612 fn % Changeset { } = cs ->
602613 Enum . map ( pks , fn pk ->
0 commit comments