Fix: policy error on cascade archive

This commit is contained in:
Zach Daniel 2024-02-15 20:28:47 -05:00 committed by GitHub
commit b1858267b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 358 additions and 35 deletions

View file

@ -3,7 +3,7 @@ defmodule AshArchival.Resource.Changes.ArchiveRelated do
use Ash.Resource.Change
require Ash.Query
def change(changeset, _, _) do
def change(changeset, _, context) do
archive_related = AshArchival.Resource.Info.archive_related(changeset.resource)
if Enum.empty?(archive_related) do
@ -11,7 +11,8 @@ defmodule AshArchival.Resource.Changes.ArchiveRelated do
else
Ash.Changeset.after_action(changeset, fn changeset, result ->
# This is not optimized. We should do this with bulk queries, not resource actions.
loaded = changeset.api.load!(result, archive_related)
opts = context |> Ash.context_to_opts()
loaded = changeset.api.load!(result, archive_related, opts)
notifications =
Enum.flat_map(archive_related, fn relationship ->
@ -25,8 +26,8 @@ defmodule AshArchival.Resource.Changes.ArchiveRelated do
|> List.wrap()
|> Enum.flat_map(fn related ->
related
|> Ash.Changeset.for_destroy(destroy_action)
|> (relationship.api || changeset.api).destroy!(return_notifications?: true)
|> Ash.Changeset.for_destroy(destroy_action, opts)
|> (relationship.api || changeset.api).destroy!(opts |> Keyword.merge(return_notifications?: true))
end)
end)

View file

@ -1,6 +1,52 @@
defmodule ArchivalTest do
use ExUnit.Case
defmodule Author do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshArchival.Resource]
ets do
table(:authors)
private?(true)
end
archive do
archive_related([:posts])
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
end
relationships do
has_many(:posts, ArchivalTest.Post)
end
end
defmodule AuthorWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
table(:authors)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Post do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
@ -24,10 +70,33 @@ defmodule ArchivalTest do
end
relationships do
belongs_to :author, Author do
attribute_writable?(true)
end
has_many(:comments, ArchivalTest.Comment)
end
end
defmodule PostWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
table(:posts)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Comment do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
@ -70,35 +139,14 @@ defmodule ArchivalTest do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
relationships do
belongs_to(:post, Post)
end
end
defmodule PostWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
private?(true)
table(:posts)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Registry do
use Ash.Registry
entries do
entry(Author)
entry(AuthorWithArchive)
entry(Post)
entry(PostWithArchive)
entry(Comment)
@ -120,12 +168,9 @@ defmodule ArchivalTest do
|> Ash.Changeset.for_create(:create)
|> Api.create!()
assert :ok =
post
|> Api.destroy!()
assert :ok = post |> Api.destroy!()
[archived] = Api.read!(PostWithArchive)
assert archived.id == post.id
assert archived.archived_at
end
@ -141,13 +186,37 @@ defmodule ArchivalTest do
|> Ash.Changeset.for_create(:create, %{post_id: post.id})
|> Api.create!()
assert :ok =
post
|> Api.destroy!()
assert :ok = post |> Api.destroy!()
[archived] = Api.read!(CommentWithArchive)
assert archived.id == comment.id
assert archived.archived_at
end
test "destroying a record triggers a cascading archive." do
author =
Author
|> Ash.Changeset.for_create(:create)
|> Api.create!()
post =
Post
|> Ash.Changeset.for_create(:create, %{author_id: author.id})
|> Api.create!()
comment =
Comment
|> Ash.Changeset.for_create(:create, %{post_id: post.id})
|> Api.create!()
assert :ok = author |> Api.destroy!()
[archived_post] = Api.read!(PostWithArchive)
assert archived_post.id == post.id
assert archived_post.archived_at
[archived_comment] = Api.read!(CommentWithArchive)
assert archived_comment.id == comment.id
assert archived_comment.archived_at
end
end

View file

@ -0,0 +1,253 @@
defmodule ArchivalWithPolicyTest do
use ExUnit.Case
defmodule Author do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshArchival.Resource],
authorizers: [Ash.Policy.Authorizer]
ets do
table(:authors)
private?(true)
end
archive do
archive_related([:posts])
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
end
relationships do
has_many(:posts, ArchivalWithPolicyTest.Post)
end
policies do
policy always() do
authorize_if(action_type(:create))
authorize_if(action_type(:read))
authorize_if(actor_attribute_equals(:admin, true))
end
end
end
defmodule AuthorWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
table(:authors)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Post do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshArchival.Resource],
authorizers: [Ash.Policy.Authorizer]
ets do
table(:posts)
private?(true)
end
archive do
archive_related([:comments])
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
end
relationships do
belongs_to :author, Author do
attribute_writable?(true)
end
has_many(:comments, ArchivalWithPolicyTest.Comment)
end
policies do
policy always() do
authorize_if(action_type(:create))
authorize_if(action_type(:read))
authorize_if(actor_attribute_equals(:admin, true))
end
end
end
defmodule PostWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
table(:posts)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Comment do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshArchival.Resource],
authorizers: [Ash.Policy.Authorizer]
ets do
table(:comments)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
end
relationships do
belongs_to :post, Post do
attribute_writable?(true)
end
end
policies do
policy always() do
authorize_if(action_type(:create))
authorize_if(action_type(:read))
authorize_if(actor_attribute_equals(:admin, true))
end
end
end
defmodule CommentWithArchive do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
ets do
table(:comments)
private?(true)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:archived_at, :utc_datetime_usec)
end
end
defmodule Registry do
use Ash.Registry
entries do
entry(Author)
entry(AuthorWithArchive)
entry(Post)
entry(PostWithArchive)
entry(Comment)
entry(CommentWithArchive)
end
end
defmodule Api do
use Ash.Api
authorization do
authorize(:by_default)
end
resources do
registry(Registry)
end
end
test "destroying a record archives it" do
comment =
Comment
|> Ash.Changeset.for_create(:create)
|> Api.create!()
assert :ok = comment |> Api.destroy!(actor: %{admin: true})
[archived] = Api.read!(CommentWithArchive)
assert archived.id == comment.id
assert archived.archived_at
end
test "destroying a record archives any `archive_related` it has configured" do
post =
Post
|> Ash.Changeset.for_create(:create)
|> Api.create!()
comment =
Comment
|> Ash.Changeset.for_create(:create, %{post_id: post.id})
|> Api.create!()
assert :ok = post |> Api.destroy!(actor: %{admin: true})
[archived] = Api.read!(CommentWithArchive)
assert archived.id == comment.id
assert archived.archived_at
end
test "destroying a record triggers a cascading archive." do
author =
Author
|> Ash.Changeset.for_create(:create)
|> Api.create!()
post =
Post
|> Ash.Changeset.for_create(:create, %{author_id: author.id})
|> Api.create!()
comment =
Comment
|> Ash.Changeset.for_create(:create, %{post_id: post.id})
|> Api.create!()
assert :ok = author |> Api.destroy!(actor: %{admin: true})
[archived_post] = Api.read!(PostWithArchive)
assert archived_post.id == post.id
assert archived_post.archived_at
[archived_comment] = Api.read!(CommentWithArchive)
assert archived_comment.id == comment.id
assert archived_comment.archived_at
end
end