Skip to content

Commit

Permalink
feat: adds store_inputs? true/false option
Browse files Browse the repository at this point in the history
closes: ash-project#20

- introduces `store_inputs?` to the DSL
- `store_inputs?` if not included defaults to false
- when switched on it ensure an attribute is created to capture the exact
  inputs of each new version, when the resource we are paper-trailing changes.
  • Loading branch information
chrishop committed Oct 11, 2023
1 parent acbf752 commit ae8f918
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ spark_locals_without_parens = [
on_actions: 1,
reference_source?: 1,
store_action_name?: 1,
version_extensions: 1
version_extensions: 1,
store_inputs?: 1
]

[
Expand Down
24 changes: 17 additions & 7 deletions lib/resource/changes/create_new_version.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,15 @@ defmodule AshPaperTrail.Resource.Changes.CreateNewVersion do

change_tracking_mode = AshPaperTrail.Resource.Info.change_tracking_mode(changeset.resource)

belongs_to_actors =
AshPaperTrail.Resource.Info.belongs_to_actor(changeset.resource)
belongs_to_actors = AshPaperTrail.Resource.Info.belongs_to_actor(changeset.resource)

actor = changeset.context[:private][:actor]

resource_attributes =
changeset.resource
|> Ash.Resource.Info.attributes()

{input, private} =
{public_params, private_params} =
resource_attributes
|> Enum.filter(&(&1.name in attributes_as_attributes))
|> Enum.reduce({%{}, %{}}, &build_inputs(changeset, &1, &2))
Expand All @@ -66,8 +65,8 @@ defmodule AshPaperTrail.Resource.Changes.CreateNewVersion do
)
|> Enum.reduce(%{}, &build_changes(changeset, &1, &2))

input =
Enum.reduce(belongs_to_actors, input, fn belongs_to_actor, input ->
public_params =
Enum.reduce(belongs_to_actors, public_params, fn belongs_to_actor, input ->
with true <- is_struct(actor) && actor.__struct__ == belongs_to_actor.destination,
relationship when not is_nil(relationship) <-
Ash.Resource.Info.relationship(version_resource, belongs_to_actor.name) do
Expand All @@ -85,15 +84,18 @@ defmodule AshPaperTrail.Resource.Changes.CreateNewVersion do
version_action_name: changeset.action.name,
changes: changes
})
|> maybe_add_original_inputs(changeset)

{_, notifications} =
version_changeset
|> Ash.Changeset.for_create(:create, input,
|> Ash.Changeset.for_create(:create, public_params,
tenant: changeset.tenant,
authorize?: false,
actor: actor
)
|> Ash.Changeset.force_change_attributes(Map.take(private, version_resource_attributes))
|> Ash.Changeset.force_change_attributes(
Map.take(private_params, version_resource_attributes)
)
|> changeset.api.create!(return_notifications?: true)

notifications
Expand Down Expand Up @@ -128,4 +130,12 @@ defmodule AshPaperTrail.Resource.Changes.CreateNewVersion do
{:ok, dumped_value} = Ash.Type.dump_to_embedded(attribute.type, value, [])
Map.put(changes, attribute.name, dumped_value)
end

defp maybe_add_original_inputs(params, changeset) do
if AshPaperTrail.Resource.Info.store_inputs?(changeset.resource) do
Map.put(params, :inputs, Map.merge(changeset.arguments, changeset.attributes))
else
params
end
end
end
5 changes: 5 additions & 0 deletions lib/resource/info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ defmodule AshPaperTrail.Resource.Info do
Module.concat([Spark.Dsl.Extension.get_persisted(resource, :module), Version])
end
end

@spec store_inputs?(Spark.Dsl.t() | Ash.Resource.t()) :: boolean
def store_inputs?(resource) do
Spark.Dsl.Extension.get_opt(resource, [:paper_trail], :store_inputs?, false)
end
end
13 changes: 10 additions & 3 deletions lib/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ defmodule AshPaperTrail.Resource do
@belongs_to_actor %Spark.Dsl.Entity{
name: :belongs_to_actor,
describe: """
Creates a belongs_to relationship for the actor resource. When creating a new version, if the actor on the action is set and
matches the resource type, the version will be related to the actor. If your actors are polymorphic or varying types, declare a
Creates a belongs_to relationship for the actor resource. When creating a new version, if the actor on the action is set and
matches the resource type, the version will be related to the actor. If your actors are polymorphic or varying types, declare a
belongs_to_actor for each type.
A reference is also created with `on_delete: :nilify` and `on_update: :update`
If you need more complex relationships, set `define_attribute? false` and add the relationship via a mixin.
If your actor is not a resource, add a mixin and with a change for all creates that sets the actor's to one your attributes.
If your actor is not a resource, add a mixin and with a change for all creates that sets the actor's to one your attributes.
The actor on the version changeset is set.
""",
examples: [
Expand Down Expand Up @@ -94,6 +94,13 @@ defmodule AshPaperTrail.Resource do
doc: """
Extensions that should be used by the version resource. For example: `extensions: [AshGraphql.Resource], notifier: [Ash.Notifiers.PubSub]`
"""
],
store_inputs?: [
type: :boolean,
default: false,
doc: """
Whether or not to store the original inputs to the action as well as the changes within the version resource.
"""
]
]
}
Expand Down
8 changes: 7 additions & 1 deletion lib/resource/transformers/create_version_resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ defmodule AshPaperTrail.Resource.Transformers.CreateVersionResource do
reference_source? = AshPaperTrail.Resource.Info.reference_source?(dsl_state)
store_action_name? = AshPaperTrail.Resource.Info.store_action_name?(dsl_state)
version_extensions = AshPaperTrail.Resource.Info.version_extensions(dsl_state)
store_inputs? = AshPaperTrail.Resource.Info.store_inputs?(dsl_state)

attributes =
dsl_state
|> Ash.Resource.Info.attributes()
|> Enum.filter(&(&1.name in attributes_as_attributes))

sensitive_changes? = dsl_state
sensitive_changes? =
dsl_state
|> Ash.Resource.Info.attributes()
|> Enum.filter(&(&1.name in ignore_attributes))
|> Enum.any?(& &1.sensitive?)
Expand Down Expand Up @@ -162,6 +164,10 @@ defmodule AshPaperTrail.Resource.Transformers.CreateVersionResource do
end
end

if unquote(store_inputs?) do
attribute :inputs, :map
end

attribute :changes, :map do
sensitive? unquote(sensitive_changes?)
end
Expand Down
39 changes: 33 additions & 6 deletions test/ash_paper_trail_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ defmodule AshPaperTrailTest do
assert %{subject: "new subject", body: "new body"} =
Posts.Post.update!(post, %{subject: "new subject", body: "new body"})

assert [%{subject: "new subject", body: "new body"}] =
Posts.Post.read!(tenant: "acme")
assert [%{subject: "new subject", body: "new body"}] = Posts.Post.read!(tenant: "acme")
end

test "destroys work as normal" do
Expand Down Expand Up @@ -66,8 +65,7 @@ defmodule AshPaperTrailTest do
version_action_name: :create,
version_source_id: ^post_id
}
] =
Articles.Api.read!(Posts.Post.Version, tenant: "acme")
] = Articles.Api.read!(Posts.Post.Version, tenant: "acme")
end

test "a new version is created on update" do
Expand Down Expand Up @@ -133,8 +131,7 @@ defmodule AshPaperTrailTest do
Articles.Api.read!(Articles.Article.Version)
|> Enum.filter(&(&1.version_action_type == :update))

assert [:body, :subject] =
Map.keys(updated_version.changes) |> Enum.sort()
assert [:body, :subject] = Map.keys(updated_version.changes) |> Enum.sort()
end

test "a new version is created on destroy" do
Expand Down Expand Up @@ -260,4 +257,34 @@ defmodule AshPaperTrailTest do
assert [] = Articles.Article.read!()
end
end

describe "store_inputs? options" do
test "when true, on create will create a version with the original inputs" do
assert AshPaperTrail.Resource.Info.store_inputs?(Posts.Post) == true

Posts.Post.create!(@valid_attrs, tenant: "acme")

post_version = Posts.Api.read!(Posts.Post.Version, tenant: "acme")

assert [
%{
inputs: %{
author: %AshPaperTrail.Test.Posts.Author{},
body: "body",
id: _id,
published: false,
subject: "subject",
tags: [
%AshPaperTrail.Test.Posts.Tag{
tag: "ash"
},
%AshPaperTrail.Test.Posts.Tag{
tag: "phoenix"
}
]
}
}
] = post_version
end
end
end
1 change: 1 addition & 0 deletions test/support/posts/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule AshPaperTrail.Test.Posts.Post do
attributes_as_attributes [:subject, :body, :tenant]
change_tracking_mode :changes_only
store_action_name? true
store_inputs? true
belongs_to_actor :user, AshPaperTrail.Test.Accounts.User, api: AshPaperTrail.Test.Accounts.Api

belongs_to_actor :news_feed, AshPaperTrail.Test.Accounts.NewsFeed,
Expand Down

0 comments on commit ae8f918

Please sign in to comment.