diff --git a/lib/ash_archival/resource/changes/archive_related.ex b/lib/ash_archival/resource/changes/archive_related.ex index 39ba15e..1c6eddb 100644 --- a/lib/ash_archival/resource/changes/archive_related.ex +++ b/lib/ash_archival/resource/changes/archive_related.ex @@ -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) diff --git a/test/archival_test.exs b/test/archival_test.exs index 693f755..8aab212 100644 --- a/test/archival_test.exs +++ b/test/archival_test.exs @@ -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 diff --git a/test/archival_with_policy_test.exs b/test/archival_with_policy_test.exs new file mode 100644 index 0000000..217977d --- /dev/null +++ b/test/archival_with_policy_test.exs @@ -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