fix: Fix composite key in changeset functions (#125)

Co-authored-by: Zach Daniel <zachary.s.daniel@gmail.com>
This commit is contained in:
A.shalaby 2020-10-03 08:37:17 +02:00 committed by GitHub
parent 0f805803e1
commit a2f5db08f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 175 additions and 30 deletions

View file

@ -635,13 +635,7 @@ defmodule Ash.Changeset do
multiple_primary_keys(relationship, records) multiple_primary_keys(relationship, records)
_ -> _ ->
case single_primary_key(relationship, records) do pluck_pk_fields(relationship, records)
{:ok, keys} ->
{:ok, keys}
{:error, _} ->
do_primary_key(relationship, records)
end
end end
end end
@ -649,13 +643,30 @@ defmodule Ash.Changeset do
do_primary_key(relationship, record) do_primary_key(relationship, record)
end end
defp pluck_pk_fields(relationship, records) do
Enum.reduce_while(
records,
{:ok, []},
fn
record, {:ok, acc} ->
case do_primary_key(relationship, record) do
{:ok, pk} -> {:cont, {:ok, [pk | acc]}}
{:error, error} -> {:halt, {:error, error}}
end
end
)
end
defp do_primary_key(relationship, record) when is_map(record) do defp do_primary_key(relationship, record) when is_map(record) do
primary_key = Ash.Resource.primary_key(relationship.destination) primary_key = Ash.Resource.primary_key(relationship.destination)
is_pkey_map? = is_pkey_map? =
Enum.all?(primary_key, fn key -> Enum.all?(
primary_key,
fn key ->
Map.has_key?(record, key) || Map.has_key?(record, to_string(key)) Map.has_key?(record, key) || Map.has_key?(record, to_string(key))
end) end
)
if is_pkey_map? do if is_pkey_map? do
pkey = pkey =

View file

@ -123,8 +123,9 @@ defmodule Ash.Test.Changeset.ChangesetTest do
end end
attributes do attributes do
attribute :serial, :integer, primary_key?: true
attribute :id, :uuid, primary_key?: true, default: &Ecto.UUID.generate/0 attribute :id, :uuid, primary_key?: true, default: &Ecto.UUID.generate/0
attribute :title, :string, primary_key?: true attribute :title, :string
attribute :contents, :string attribute :contents, :string
end end
@ -446,7 +447,7 @@ defmodule Ash.Test.Changeset.ChangesetTest do
assert %{replace: [%{id: post1.id}]} == changeset.relationships.posts assert %{replace: [%{id: post1.id}]} == changeset.relationships.posts
end end
test "it accepts a map %{id: value} representing primary key as a second param only if primary key is a single attribute" do test "it accepts a map %{id: value} representing primary key as a second param" do
post1 = post1 =
Post Post
|> Changeset.new(%{title: "foo"}) |> Changeset.new(%{title: "foo"})
@ -460,24 +461,157 @@ defmodule Ash.Test.Changeset.ChangesetTest do
assert %{replace: [%{id: post1.id}]} == changeset.relationships.posts assert %{replace: [%{id: post1.id}]} == changeset.relationships.posts
end end
# test "it accepts a map %{att1: value1, att2: value2} representing primary key as a second param" do test "it accepts a map %{att1: value1, att2: value2} representing primary key as a second param" do
# post1 = CompositeKeyPost |> Changeset.new(%{title: "foo"}) |> Api.create!() post1 =
# CompositeKeyPost
# assert [:id, :title] == Ash.Resource.primary_key(CompositeKeyPost) |> Changeset.new(%{serial: 1})
# |> Api.create!()
# changeset =
# Author changeset =
# |> Changeset.new() Author
# |> Changeset.replace_relationship(:composite_key_posts, %{ |> Changeset.new()
# id: post1.id, |> Changeset.replace_relationship(
# title: "some title" :composite_key_posts,
# }) %{id: post1.id, serial: post1.serial}
# )
# refute [%Ash.Error.Changes.InvalidRelationship{}] = changeset.errors
# assert %{replace: [%{id: post1.id, serial: post1.serial}]} ==
# assert %{replace: [%{id: post1.id, title: post1.title}]} == changeset.relationships.composite_key_posts
# changeset.relationships.composite_key_posts
# end assert [] == changeset.errors
author =
changeset
|> Api.create!()
[fetched_post] =
CompositeKeyPost
|> Ash.Query.load(author: :composite_key_posts)
|> Ash.Query.filter(id: post1.id, serial: post1.serial)
|> Api.read!()
assert author == fetched_post.author
end
test "it accepts a list of maps representing primary_keys as a second param" do
post1 =
CompositeKeyPost
|> Changeset.new(%{serial: 1})
|> Api.create!()
post2 =
CompositeKeyPost
|> Changeset.new(%{serial: 2})
|> Api.create!()
changeset =
Author
|> Changeset.new()
|> Changeset.replace_relationship(
:composite_key_posts,
[
%{id: post1.id, serial: post1.serial},
%{id: post2.id, serial: post2.serial}
]
)
assert Enum.sort([
%{id: post1.id, serial: post1.serial},
%{id: post2.id, serial: post2.serial}
]) ==
Enum.sort(changeset.relationships.composite_key_posts.replace)
assert [] == changeset.errors
author =
changeset
|> Api.create!()
[fetched_post] =
CompositeKeyPost
|> Ash.Query.load(author: :composite_key_posts)
|> Ash.Query.filter(id: post1.id, serial: post1.serial)
|> Api.read!()
assert author == fetched_post.author
end
test "it accepts mix of entities and maps representing primary_keys as a second param" do
post1 =
CompositeKeyPost
|> Changeset.new(%{serial: 1})
|> Api.create!()
post2 =
CompositeKeyPost
|> Changeset.new(%{serial: 2})
|> Api.create!()
changeset =
Author
|> Changeset.new()
|> Changeset.replace_relationship(
:composite_key_posts,
[
%{id: post1.id, serial: post1.serial},
post2
]
)
assert Enum.sort([
%{id: post1.id, serial: post1.serial},
%{id: post2.id, serial: post2.serial}
]) ==
Enum.sort(changeset.relationships.composite_key_posts.replace)
assert [] == changeset.errors
author =
changeset
|> Api.create!()
[fetched_author] =
Author
|> Ash.Query.load(:composite_key_posts)
|> Ash.Query.filter(id: author.id)
|> Api.read!()
assert [post2, post1] = fetched_author.composite_key_posts
end
test "it returns error if one of relationship entities is invalid" do
post1 =
CompositeKeyPost
|> Changeset.new(%{serial: 1})
|> Api.create!()
post2 =
CompositeKeyPost
|> Changeset.new(%{serial: 2})
|> Api.create!()
invalid_post =
Post
|> Changeset.new(%{title: "a title"})
|> Api.create!()
changeset =
Author
|> Changeset.new()
|> Changeset.replace_relationship(
:composite_key_posts,
[
%{id: post1.id, serial: post1.serial},
post2,
invalid_post
]
)
assert Enum.empty?(changeset.relationships)
assert [%Ash.Error.Changes.InvalidRelationship{} = relation_error] = changeset.errors
assert relation_error.message =~ "Invalid identifier"
end
test "it accepts many-to-many relationship" do test "it accepts many-to-many relationship" do
post1 = post1 =