Skip to content

Commit 4f599da

Browse files
committed
Support where in manytomany
1 parent 9168b1e commit 4f599da

9 files changed

Lines changed: 181 additions & 119 deletions

File tree

lib/ecto_sync.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ defmodule EctoSync do
202202
@spec sync(syncable(), {{struct(), atom()}, {integer() | String.t(), reference()}}) ::
203203
syncable()
204204
def sync(value, event, opts \\ [])
205+
206+
def sync(value, event, opts)
205207
when is_list(value) or is_struct(value) or is_nil(value) or is_map(value) do
206208
config = Config.new(event, opts)
207209
Syncer.sync(value, config)

lib/ecto_sync/helpers.ex

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,7 @@ defmodule EctoSync.Helpers do
163163
def walk_preloaded_assocs(list, acc, function) when is_list(list),
164164
do: Enum.reduce(list, acc, &walk_preloaded_assocs(&1, &2, function))
165165

166-
def walk_preloaded_assocs(%{__struct__: schema_mod} = value, acc, function)
167-
when is_function(function) do
166+
def walk_preloaded_assocs(value, acc, function) when is_function(function) do
168167
reduce_preloaded_assocs(value, acc, fn {key, assoc_info}, struct, acc ->
169168
acc = function.(key, assoc_info, struct, acc)
170169
walk_preloaded_assocs(struct, acc, function)

lib/ecto_sync/subscriber.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ defmodule EctoSync.Subscriber do
9696
[{{schema, :inserted}, assoc_field}] ++ [Enum.map(assocs, &subscribe_events/1)]
9797
end
9898

99-
def subscribe_events(struct, %HasThrough{through: through} = assoc) do
99+
def subscribe_events(struct, %HasThrough{through: through}) do
100100
preloads =
101101
through
102102
|> Enum.reverse()
@@ -217,7 +217,7 @@ defmodule EctoSync.Subscriber do
217217
defp subscribe_events_assocs(nil, _, acc), do: acc
218218

219219
defp subscribe_events_assocs(parent, true, acc) when is_struct(parent) do
220-
walk_preloaded_assocs(parent, acc, fn key, assoc_info, assoc, acc ->
220+
walk_preloaded_assocs(parent, acc, fn _key, assoc_info, assoc, acc ->
221221
subscribe_events(parent, assoc_info) ++ subscribe_events(assoc) ++ acc
222222
end)
223223
|> Enum.filter(fn

lib/ecto_sync/syncer.ex

Lines changed: 51 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,8 @@ defmodule EctoSync.Syncer do
4343

4444
Subscriber.subscribe(new, assocs: preloads)
4545

46-
value_or_values =
47-
do_sync(value_or_values, new, config)
48-
|> IO.inspect(label: :after_sync)
49-
|> maybe_update_has_through(new, config)
50-
51-
# reduce_preloaded_assocs(new, value_or_values, fn key, struct, acc ->
52-
# if not same_record?(struct, value_or_values) do
53-
# struct
54-
# |> IO.inspect(label: :other)
55-
# |> List.wrap()
56-
# |> Enum.reduce(
57-
# acc,
58-
# &(do_sync(IO.inspect(&2, label: :val), IO.inspect(&1, label: :input), config)
59-
# |> IO.inspect(label: :reducing))
60-
# )
61-
# else
62-
# acc
63-
# end
64-
# end)
46+
do_sync(value_or_values, new, config)
47+
|> maybe_update_has_through(new, config)
6548
end
6649

6750
def sync(value_or_values, config) do
@@ -121,26 +104,6 @@ defmodule EctoSync.Syncer do
121104
end
122105
end
123106

124-
defp maybe_update_has_through(value_or_values, new, config) do
125-
# For each preloaded assoc, check if there is another schema that has it as a HasThrough. If so, update that association based on its path for the assoc
126-
127-
if is_list(value_or_values) do
128-
Enum.map(value_or_values, fn value ->
129-
walk_preloaded_assocs(new, value, fn _, _, struct, acc ->
130-
do_sync(acc, struct, config)
131-
end)
132-
end)
133-
else
134-
walk_preloaded_assocs(new, value_or_values, fn _, _, struct, acc ->
135-
if is_list(struct) do
136-
Enum.reduce(struct, acc, &do_sync(&2, &1, config))
137-
else
138-
do_sync(acc, struct, config)
139-
end
140-
end)
141-
end
142-
end
143-
144107
defp do_sync(value, _new, _config) do
145108
value
146109
end
@@ -186,7 +149,7 @@ defmodule EctoSync.Syncer do
186149
Enum.reduce(new, value, &deep_update(&2, path, &1, config))
187150
end
188151

189-
defp deep_update(value, {key, []}, new, %{schema: schema} = config) do
152+
defp deep_update(value, {key, []}, new, config) do
190153
value_schema = get_schema(value)
191154

192155
assoc_info = value_schema.__schema__(:association, key)
@@ -203,9 +166,19 @@ defmodule EctoSync.Syncer do
203166
defp maybe_update(values, new, config) when is_list(values),
204167
do: Enum.map(values, &maybe_update(&1, new, config)) |> Enum.reject(&is_nil/1)
205168

169+
defp maybe_update(value, new, config) do
170+
if same_record?(value, new) do
171+
preloads = find_preloads(config.preloads[new.__struct__] || value)
172+
173+
get_preloaded(get_schema(value), new.id, preloads, config)
174+
else
175+
value
176+
end
177+
end
178+
206179
defp assoc_update(_, %Ecto.Association.NotLoaded{} = not_loaded, _, _, _), do: not_loaded
207180

208-
defp assoc_update(_value, assocs, new, %HasThrough{}, %{event: :inserted} = config)
181+
defp assoc_update(_value, assocs, new, %HasThrough{}, %{event: :inserted})
209182
when is_list(assocs) do
210183
if is_nil(find_by_primary_key(assocs, new)) do
211184
assocs ++ [new]
@@ -217,7 +190,7 @@ defmodule EctoSync.Syncer do
217190
defp assoc_update(_value, _assoc, new, %HasThrough{}, %{event: :inserted}),
218191
do: new
219192

220-
defp assoc_update(_value, assocs, new, %HasThrough{}, %{event: :updated} = config) do
193+
defp assoc_update(_value, assocs, new, %HasThrough{}, %{event: :updated}) do
221194
if is_list(assocs) do
222195
possible_index = find_by_primary_key(assocs, new)
223196
List.replace_at(assocs, possible_index, new)
@@ -226,19 +199,11 @@ defmodule EctoSync.Syncer do
226199
end
227200
end
228201

229-
defp assoc_update(
230-
value,
231-
assocs,
232-
new,
233-
%Has{field: key, where: where} = assoc_info,
234-
%{schema: schema} = config
235-
) do
202+
defp assoc_update(value, assocs, new, %Has{} = assoc_info, %{schema: schema} = config) do
236203
possible_index = find_by_primary_key(assocs, new)
237204
related_id = Map.get(new, assoc_info.related_key)
238205
owner_id = Map.get(value, assoc_info.owner_key)
239206

240-
in_where = match_where?(new, where)
241-
242207
cond do
243208
# Maybe we are removed as assoc
244209
not is_nil(possible_index) and related_id != owner_id and
@@ -259,11 +224,7 @@ defmodule EctoSync.Syncer do
259224
# Maybe we are assigned as assoc
260225
is_nil(possible_index) and related_id == owner_id and
261226
assoc_info.related == schema ->
262-
if in_where do
263-
do_insert(assocs, new, config)
264-
else
265-
assocs
266-
end
227+
do_insert(assocs, new, assoc_info, config)
267228

268229
true ->
269230
maybe_update(assocs, new, config)
@@ -274,34 +235,33 @@ defmodule EctoSync.Syncer do
274235
{related?, resolved} = resolve_assoc(assoc_info, value, new, config)
275236

276237
if related? and config.event == :inserted do
277-
do_insert(assoc, resolved, config)
238+
do_insert(assoc, resolved, assoc_info, config)
278239
else
279240
maybe_update(assoc, new, config)
280241
end
281242
end
282243

283-
defp maybe_update(value, new, config) do
284-
if same_record?(value, new) do
285-
preloads = find_preloads(config.preloads[new.__struct__] || value)
244+
defp maybe_update_has_through(value_or_values, new, config) do
245+
# For each preloaded assoc, check if there is another schema that has it as a HasThrough. If so, update that association based on its path for the assoc
286246

287-
get_preloaded(get_schema(value), new.id, preloads, config)
247+
if is_list(value_or_values) do
248+
Enum.map(value_or_values, fn value ->
249+
walk_preloaded_assocs(new, value, fn _, _, struct, acc ->
250+
do_sync(acc, struct, config)
251+
end)
252+
end)
288253
else
289-
value
254+
walk_preloaded_assocs(new, value_or_values, fn _, _, struct, acc ->
255+
if is_list(struct) do
256+
Enum.reduce(struct, acc, &do_sync(&2, &1, config))
257+
else
258+
do_sync(acc, struct, config)
259+
end
260+
end)
290261
end
291262
end
292263

293-
defp do_insert(value, new, %Ecto.Association.NotLoaded{} = assoc, resolved, config) do
294-
assoc =
295-
if assoc.__cardinality__ == :many do
296-
[]
297-
else
298-
resolved
299-
end
300-
301-
do_insert(value, new, assoc, resolved, config)
302-
end
303-
304-
defp do_insert(assocs, resolved, %{schema: schema} = config)
264+
defp do_insert(assocs, resolved, %{where: where}, %{schema: schema} = config)
305265
when is_list(assocs) do
306266
preloads =
307267
case assocs do
@@ -319,19 +279,31 @@ defmodule EctoSync.Syncer do
319279
{new, get_preloaded(schema, new.id, preloads, config)}
320280
end
321281

322-
EctoSync.subscribe(inserted)
282+
in_where = match_where?(inserted, where)
283+
284+
if in_where do
285+
EctoSync.subscribe(inserted)
323286

324-
Enum.map(assocs, &maybe_update(&1, new, config)) ++ [inserted]
287+
Enum.map(assocs, &maybe_update(&1, new, config)) ++ [inserted]
288+
else
289+
assocs
290+
end
325291
end
326292

327-
defp do_insert(assoc, new, %{schema: schema} = config) do
293+
defp do_insert(assoc, new, %{where: where}, %{schema: schema} = config) do
328294
preloads = find_preloads(config.preloads[schema] || assoc)
329295

330296
new = get_preloaded(schema, new.id, preloads, config)
331297

332-
EctoSync.subscribe(new)
298+
in_where = match_where?(new, where)
299+
300+
if in_where do
301+
EctoSync.subscribe(new)
333302

334-
new
303+
new
304+
else
305+
assoc
306+
end
335307
end
336308

337309
defp get_preloaded(schema, id, preloads, config) do
@@ -422,37 +394,6 @@ defmodule EctoSync.Syncer do
422394
Map.get(config.schemas.paths, {from, to}, [])
423395
end
424396

425-
defp filter_reachable_paths(paths, struct) do
426-
paths
427-
|> Enum.filter(fn path ->
428-
do_traverse_path(struct, path)
429-
end)
430-
end
431-
432-
defp do_traverse_path(struct, [{key, nested} | path]) do
433-
value = Map.get(struct, key)
434-
435-
case value do
436-
%Ecto.Association.NotLoaded{} ->
437-
false
438-
439-
[] ->
440-
false
441-
442-
_ ->
443-
if nested == [] do
444-
true
445-
else
446-
do_traverse_path(nested, path)
447-
end
448-
end
449-
450-
do_traverse_path(struct, path)
451-
end
452-
453-
defp get_with_path(path, struct) do
454-
end
455-
456397
defp match_where?(_struct, []) do
457398
true
458399
end

priv/test_repo/migrations/20250225082733_person.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule TestRepo.Migrations.Person do
44
def change do
55
create table("persons") do
66
add :name, :string
7+
add :other, :integer, [:increment, start_value: 0]
78
end
89
end
910
end

priv/test_repo/migrations/20250225082743_post.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule TestRepo.Migrations.Post do
77
add :body, :string
88
add :person_id, references(:persons, on_delete: :nilify_all)
99
add :comment_id, references(:posts)
10+
add :other, :integer, [:increment, start_value: 0]
1011
end
1112
end
1213
end

0 commit comments

Comments
 (0)