Skip to content

Commit

Permalink
diffs cardinality many through (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
leftstanding authored Oct 4, 2023
1 parent b91baf0 commit 3ff4479
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 10 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased][]

- Nothing yet
- Implement diffing for has_many through associations, resolves associated error.

## [0.5.0][]

Expand Down
48 changes: 46 additions & 2 deletions lib/ecto_diff.ex
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,40 @@ defmodule EctoDiff do
end
end

defp diff_association(previous, current, %{cardinality: :many, field: field, related: struct}, acc, opts) do
# The association likely already has the diff for the many through association,
# Attempt to get the diff through the association, calculate the diff if not found.
defp diff_association(
previous,
current,
%{cardinality: :many, through: [through, assoc_field], owner: owner},
acc,
opts
) do
diff =
case get_association_through_diff(acc, through, assoc_field) do
[%EctoDiff{} | _] = diff ->
diff

[] ->
through_association = owner.__schema__(:association, through).related
association = through_association.__schema__(:association, assoc_field)

diff = diff_association(previous, current, association, acc, opts)
get_association_through_diff(diff, through, assoc_field)
end

if diff == [], do: acc, else: [{assoc_field, diff} | acc]
end

defp diff_association(
previous,
current,
%{cardinality: :many, field: field} = association,
acc,
opts
) do
%{related: struct} = association

primary_key_fields = get_primary_key_fields(struct, opts)

if primary_key_fields == [],
Expand Down Expand Up @@ -404,6 +437,17 @@ defmodule EctoDiff do
end
end

# returns the diff for an association through for assoc_field, or an empty list
# removes [nil] to return empty list when `assoc_field` is not present
# removes diffs with no changes
defp get_association_through_diff(diff, through, assoc_field) do
with [assoc_diffs] <- Keyword.get_values(diff, through) do
assoc_diffs
|> Enum.flat_map(& &1[:changes][assoc_field])
|> Enum.reject(&(&1 == nil or no_changes?(&1)))
end
end

defp get_primary_key_fields(struct, opts) do
overrides = Keyword.get(opts, :overrides, [])

Expand All @@ -428,7 +472,7 @@ defmodule EctoDiff do
keys
end

defp no_changes?(%{effect: :changed, changes: map}) when map == %{}, do: true
defp no_changes?(%{effect: effect, changes: map}) when map == %{} and effect in [:added, :changed], do: true
defp no_changes?(_), do: false

defp primary_key_nil?(key), do: Enum.all?(key, fn {_key, value} -> is_nil(value) end)
Expand Down
18 changes: 18 additions & 0 deletions priv/repo/migrations/20230915120413_create_resources_toys.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule EctoDiff.Repo.Migrations.CreateResources do
use Ecto.Migration

def change do
create table(:resources) do
add :pet_id, references(:pets)
add :refid, :uuid
end

create table(:toys) do
add :name, :string
add :type, :string
add :quantity, :integer, default: 1
add :resource_id, references(:resources)
add :refid, :uuid
end
end
end
Loading

0 comments on commit 3ff4479

Please sign in to comment.