11defmodule PolymorphicEmbed do
22 use Ecto.ParameterizedType
33
4+ alias Ecto.Changeset
5+
46 defmacro polymorphic_embeds_one ( field_name , opts ) do
57 quote do
68 field ( unquote ( field_name ) , PolymorphicEmbed , unquote ( opts ) )
@@ -70,28 +72,9 @@ defmodule PolymorphicEmbed do
7072 required = Keyword . get ( cast_options , :required , false )
7173 with = Keyword . get ( cast_options , :with , nil )
7274
73- changeset_fun = fn
74- struct , params when is_nil ( with ) ->
75- struct . __struct__ . changeset ( struct , params )
76-
77- struct , params when is_list ( with ) ->
78- type = do_get_polymorphic_type ( struct , types_metadata )
79-
80- case Keyword . get ( with , type ) do
81- { module , function_name , args } ->
82- apply ( module , function_name , [ struct , params | args ] )
75+ changeset_fun = & changeset_fun ( & 1 , & 2 , with , types_metadata )
8376
84- nil ->
85- struct . __struct__ . changeset ( struct , params )
86-
87- fun ->
88- apply ( fun , [ struct , params ] )
89- end
90- end
91-
92- ( changeset . params || % { } )
93- |> Map . fetch ( to_string ( field ) )
94- |> case do
77+ case Map . fetch ( changeset . params || % { } , to_string ( field ) ) do
9578 :error when required ->
9679 if data_for_field = Map . fetch! ( changeset . data , field ) do
9780 data_for_field = autogenerate_id ( data_for_field , changeset . action )
@@ -144,6 +127,25 @@ defmodule PolymorphicEmbed do
144127 raise "cast_polymorphic_embed/3 only accepts a changeset as first argument"
145128 end
146129
130+ defp changeset_fun ( struct , params , with , types_metadata ) when is_list ( with ) do
131+ type = do_get_polymorphic_type ( struct , types_metadata )
132+
133+ case Keyword . get ( with , type ) do
134+ { module , function_name , args } ->
135+ apply ( module , function_name , [ struct , params | args ] )
136+
137+ nil ->
138+ struct . __struct__ . changeset ( struct , params )
139+
140+ fun ->
141+ apply ( fun , [ struct , params ] )
142+ end
143+ end
144+
145+ defp changeset_fun ( struct , params , nil , _ ) do
146+ struct . __struct__ . changeset ( struct , params )
147+ end
148+
147149 defp cast_polymorphic_embeds_one ( changeset , field , changeset_fun , params , field_options ) do
148150 % {
149151 types_metadata: types_metadata ,
@@ -155,27 +157,8 @@ defmodule PolymorphicEmbed do
155157
156158 # We support partial update of the embed. If the type cannot be inferred from the parameters, or if the found type
157159 # hasn't changed, pass the data to the changeset.
158- action_and_struct =
159- case do_get_polymorphic_module_from_map ( params , type_field , types_metadata ) do
160- nil ->
161- if data_for_field do
162- { :update , data_for_field }
163- else
164- :type_not_found
165- end
166-
167- module when is_nil ( data_for_field ) ->
168- { :insert , struct ( module ) }
169160
170- module ->
171- if data_for_field . __struct__ != module do
172- { :insert , struct ( module ) }
173- else
174- { :update , data_for_field }
175- end
176- end
177-
178- case action_and_struct do
161+ case action_and_struct ( params , type_field , types_metadata , data_for_field ) do
179162 :type_not_found when on_type_not_found == :raise ->
180163 raise_cannot_infer_type_from_data ( params )
181164
@@ -203,6 +186,27 @@ defmodule PolymorphicEmbed do
203186 end
204187 end
205188
189+ defp action_and_struct ( params , type_field , types_metadata , data_for_field ) do
190+ case do_get_polymorphic_module_from_map ( params , type_field , types_metadata ) do
191+ nil ->
192+ if data_for_field do
193+ { :update , data_for_field }
194+ else
195+ :type_not_found
196+ end
197+
198+ module when is_nil ( data_for_field ) ->
199+ { :insert , struct ( module ) }
200+
201+ module ->
202+ if data_for_field . __struct__ != module do
203+ { :insert , struct ( module ) }
204+ else
205+ { :update , data_for_field }
206+ end
207+ end
208+ end
209+
206210 defp cast_polymorphic_embeds_many ( changeset , field , changeset_fun , list_params , field_options ) do
207211 % {
208212 types_metadata: types_metadata ,
@@ -225,16 +229,7 @@ defmodule PolymorphicEmbed do
225229 module ->
226230 embed_changeset = changeset_fun . ( struct ( module ) , params )
227231 embed_changeset = % { embed_changeset | action: :insert }
228-
229- case embed_changeset do
230- % { valid?: true } = embed_changeset ->
231- embed_changeset
232- |> Ecto.Changeset . apply_changes ( )
233- |> autogenerate_id ( embed_changeset . action )
234-
235- % { valid?: false } = embed_changeset ->
236- embed_changeset
237- end
232+ maybe_apply_changes ( embed_changeset )
238233 end
239234 end )
240235
@@ -259,6 +254,14 @@ defmodule PolymorphicEmbed do
259254 end
260255 end
261256
257+ defp maybe_apply_changes ( % { valid?: true } = embed_changeset ) do
258+ embed_changeset
259+ |> Ecto.Changeset . apply_changes ( )
260+ |> autogenerate_id ( embed_changeset . action )
261+ end
262+
263+ defp maybe_apply_changes ( % Changeset { valid?: false } = changeset ) , do: changeset
264+
262265 @ impl true
263266 def cast ( _data , _params ) ,
264267 do:
@@ -393,7 +396,7 @@ defmodule PolymorphicEmbed do
393396 schema . __schema__ ( :type , field )
394397 rescue
395398 _ in UndefinedFunctionError ->
396- raise ArgumentError , "#{ inspect ( schema ) } is not an Ecto schema"
399+ reraise ArgumentError , "#{ inspect ( schema ) } is not an Ecto schema" , __STACKTRACE__
397400 else
398401 { :parameterized , PolymorphicEmbed , options } -> Map . put ( options , :array? , false )
399402 { :array , { :parameterized , PolymorphicEmbed , options } } -> Map . put ( options , :array? , true )
@@ -438,38 +441,49 @@ defmodule PolymorphicEmbed do
438441 end
439442
440443 defp merge_polymorphic_keys ( map , changes , types , msg_func ) do
441- Enum . reduce ( types , map , fn
442- { field , { :parameterized , PolymorphicEmbed , _opts } } , acc ->
443- if changeset = Map . get ( changes , field ) do
444- case traverse_errors ( changeset , msg_func ) do
445- errors when errors == % { } -> acc
446- errors -> Map . put ( acc , field , errors )
447- end
448- else
449- acc
450- end
444+ Enum . reduce ( types , map , & polymorphic_key_reducer ( & 1 , & 2 , changes , msg_func ) )
445+ end
451446
452- { field , { :array , { :parameterized , PolymorphicEmbed , _opts } } } , acc ->
453- if changesets = Map . get ( changes , field ) do
454- { errors , all_empty? } =
455- Enum . map_reduce ( changesets , true , fn changeset , all_empty? ->
456- errors = traverse_errors ( changeset , msg_func )
457- { errors , all_empty? and errors == % { } }
458- end )
459-
460- case all_empty? do
461- true -> acc
462- false -> Map . put ( acc , field , errors )
463- end
464- else
465- acc
466- end
447+ defp polymorphic_key_reducer (
448+ { field , { :parameterized , PolymorphicEmbed , _opts } } ,
449+ acc ,
450+ changes ,
451+ msg_func
452+ ) do
453+ if changeset = Map . get ( changes , field ) do
454+ case traverse_errors ( changeset , msg_func ) do
455+ errors when errors == % { } -> acc
456+ errors -> Map . put ( acc , field , errors )
457+ end
458+ else
459+ acc
460+ end
461+ end
467462
468- { _ , _ } , acc ->
469- acc
470- end )
463+ defp polymorphic_key_reducer (
464+ { field , { :array , { :parameterized , PolymorphicEmbed , _opts } } } ,
465+ acc ,
466+ changes ,
467+ msg_func
468+ ) do
469+ if changesets = Map . get ( changes , field ) do
470+ { errors , all_empty? } =
471+ Enum . map_reduce ( changesets , true , fn changeset , all_empty? ->
472+ errors = traverse_errors ( changeset , msg_func )
473+ { errors , all_empty? and errors == % { } }
474+ end )
475+
476+ case all_empty? do
477+ true -> acc
478+ false -> Map . put ( acc , field , errors )
479+ end
480+ else
481+ acc
482+ end
471483 end
472484
485+ defp polymorphic_key_reducer ( { _ , _ } , acc , _ , _ ) , do: acc
486+
473487 defp autogenerate_id ( [ ] , _action ) , do: [ ]
474488
475489 defp autogenerate_id ( [ schema | rest ] , action ) do
0 commit comments