@@ -12,9 +12,13 @@ defmodule Diffo.Provider.Extension.Characteristic do
1212
1313 defstruct [ :name , :value_type , __spark_metadata__: nil ]
1414
15+ # ── build_before: dynamic characteristics only ─────────────────────────────
16+
1517 def set_characteristics_argument ( changeset , declarations )
1618 when is_struct ( changeset , Ash.Changeset ) and is_list ( declarations ) do
17- case characteristics = create_characteristics_from_declarations ( declarations , :instance ) do
19+ dynamic = Enum . reject ( declarations , & typed? ( & 1 . value_type ) )
20+
21+ case characteristics = create_characteristics_from_declarations ( dynamic , :instance ) do
1822 [ ] ->
1923 changeset
2024
@@ -54,6 +58,8 @@ defmodule Diffo.Provider.Extension.Characteristic do
5458 end )
5559 end
5660
61+ # ── build_after: relate dynamic, create typed ──────────────────────────────
62+
5763 def relate_instance ( result , changeset )
5864 when is_struct ( result ) and is_struct ( changeset , Ash.Changeset ) do
5965 characteristics = Ash.Changeset . get_argument ( changeset , :characteristics )
@@ -63,72 +69,134 @@ defmodule Diffo.Provider.Extension.Characteristic do
6369 } )
6470 end
6571
72+ def create_typed ( result , declarations ) when is_struct ( result ) and is_list ( declarations ) do
73+ typed = Enum . filter ( declarations , & typed? ( & 1 . value_type ) )
74+
75+ Enum . reduce_while ( typed , { :ok , result } , fn % { name: name , value_type: module } , { :ok , acc } ->
76+ case module
77+ |> Ash.Changeset . for_create ( :create , % { name: name , instance_id: acc . id } )
78+ |> Ash . create ( ) do
79+ { :ok , _ } -> { :cont , { :ok , acc } }
80+ { :error , error } -> { :halt , { :error , error } }
81+ end
82+ end )
83+ end
84+
85+ # ── update: handle both typed and dynamic characteristics ──────────────────
86+
6687 def update_values ( result , changeset )
6788 when is_struct ( result ) and is_struct ( changeset , Ash.Changeset ) do
89+ update_all ( result , changeset , [ ] )
90+ end
91+
92+ def update_all ( result , changeset , declarations )
93+ when is_struct ( result ) and is_struct ( changeset , Ash.Changeset ) and is_list ( declarations ) do
6894 characteristic_value_updates =
6995 Ash.Changeset . get_argument ( changeset , :characteristic_value_updates )
7096
7197 case characteristic_value_updates do
72- nil ->
73- { :ok , result }
98+ nil -> { :ok , result }
99+ [ ] -> { :ok , result }
100+ _ -> apply_updates ( result , characteristic_value_updates , declarations )
101+ end
102+ end
74103
75- [ ] ->
76- { :ok , result }
104+ defp apply_updates ( result , updates , declarations ) do
105+ Enum . reduce_while ( updates , { :ok , result } , fn { name , update } , { :ok , acc } ->
106+ decl = Enum . find ( declarations , & ( & 1 . name == name ) )
77107
78- _ ->
79- characteristic_updates =
80- Enum . reduce ( characteristic_value_updates , [ ] , fn { name , update } , acc ->
81- characteristic =
82- Enum . find ( changeset . data . characteristics , fn % { name: n } -> n == name end )
83-
84- if characteristic do
85- cond do
86- is_list ( update ) ->
87- unwrapped = Diffo.Unwrap . unwrap ( characteristic . value )
88- value_type = unwrapped . __struct__
89-
90- updated =
91- Enum . reduce ( update , unwrapped , fn { field , val } , acc ->
92- Map . put ( acc , field , val )
93- end )
94-
95- new_value = Value . dynamic ( struct ( value_type , Map . from_struct ( updated ) ) )
96- [ { characteristic , new_value } | acc ]
97-
98- true ->
99- [ { characteristic , update } | acc ]
100- end
101- else
102- Logger . warning ( "couldn't find characteristic #{ name } " )
103- acc
104- end
105- end )
106-
107- characteristics =
108- Enum . reduce_while ( characteristic_updates , [ ] , fn { characteristic , value } , acc ->
109- case Provider . update_characteristic ( characteristic , % { value: value } ) do
110- { :ok , characteristic } ->
111- { :cont , [ characteristic | acc ] }
112-
113- { :error , error } ->
114- { :halt , { :error , error } }
115- end
116- end )
117-
118- case characteristics do
119- { :error , error } ->
120- { :error , error }
108+ if decl && typed? ( decl . value_type ) do
109+ apply_typed_update ( acc , name , decl . value_type , update )
110+ else
111+ apply_dynamic_update ( acc , name , update )
112+ end
113+ end )
114+ end
121115
122- [ ] ->
123- { :error , "couldn't update characteristics" }
116+ defp apply_typed_update ( result , name , module , field_updates ) do
117+ case module
118+ |> Ash.Query . new ( )
119+ |> Ash.Query . filter_input ( instance_id: result . id , name: name )
120+ |> Ash . read_one ( ) do
121+ { :ok , nil } ->
122+ Logger . warning ( "couldn't find typed characteristic #{ name } " )
123+ { :cont , { :ok , result } }
124+
125+ { :ok , char } ->
126+ attrs = if is_list ( field_updates ) , do: Map . new ( field_updates ) , else: field_updates
127+
128+ case char
129+ |> Ash.Changeset . for_update ( :update , attrs )
130+ |> Ash . update ( ) do
131+ { :ok , _ } -> { :cont , { :ok , result } }
132+ { :error , error } -> { :halt , { :error , error } }
133+ end
124134
125- _ ->
126- { :ok , Map . put ( result , :characteristics , characteristics ) }
135+ { :error , error } ->
136+ { :halt , { :error , error } }
137+ end
138+ end
139+
140+ defp apply_dynamic_update ( result , name , update ) do
141+ characteristic = Enum . find ( result . characteristics , fn % { name: n } -> n == name end )
142+
143+ if characteristic do
144+ new_value =
145+ cond do
146+ is_list ( update ) ->
147+ unwrapped = Diffo.Unwrap . unwrap ( characteristic . value )
148+ value_type = unwrapped . __struct__
149+
150+ updated =
151+ Enum . reduce ( update , unwrapped , fn { field , val } , acc ->
152+ Map . put ( acc , field , val )
153+ end )
154+
155+ Value . dynamic ( struct ( value_type , Map . from_struct ( updated ) ) )
156+
157+ true ->
158+ update
127159 end
160+
161+ case Provider . update_characteristic ( characteristic , % { value: new_value } ) do
162+ { :ok , updated_char } ->
163+ updated_chars =
164+ Enum . map ( result . characteristics , fn c ->
165+ if c . id == updated_char . id , do: updated_char , else: c
166+ end )
167+
168+ { :cont , { :ok , % { result | characteristics: updated_chars } } }
169+
170+ { :error , error } ->
171+ { :halt , { :error , error } }
172+ end
173+ else
174+ Logger . warning ( "couldn't find characteristic #{ name } " )
175+ { :cont , { :ok , result } }
128176 end
129177 end
130178
131179 defimpl String.Chars do
132180 def to_string ( struct ) , do: inspect ( struct )
133181 end
182+
183+ # ── helpers ────────────────────────────────────────────────────────────────
184+
185+ def typed? ( module ) when is_atom ( module ) and not is_nil ( module ) do
186+ case Code . ensure_loaded ( module ) do
187+ { :module , _ } ->
188+ try do
189+ module != Diffo.Provider.Characteristic and
190+ Diffo.Provider.Characteristic.Extension in Ash.Resource.Info . extensions ( module )
191+ rescue
192+ _ -> false
193+ end
194+
195+ _ ->
196+ false
197+ end
198+ end
199+
200+ def typed? ( _ ) , do: false
201+
134202end
0 commit comments