AshPaperTrail is still in its experimental phase. It creates and manages a versions resource for a given resource.
The version resource's changes attribute will be a dumped map of the original resource. You can configure it to be a complete snapshot or just the changes.
First, add ash_paper_trail
dependency
def deps do
[
...
{:ash_paper_trail, git: "https://github.com/ash-project/ash_paper_trail"}
]
end
Then, add the AshPaperTrail.Resource
extension to any resource you would like to version and configure the change tracking mode
use Ash.Resource,
extensions: [
AshPaperTrail.Resource
]
paper_trail do
change_tracking_mode :changes_only # default is :snapshot
store_action_name? true # default is false
ignore_attributes [:inserted_at, :updated_at] # the primary keys are always ignored
end
This will generate the version resource automatically and add them to your api. The autogenerated resource will be named Version
under the namespace of the original resource and will belong to the original resource. For example, if your original resource is MyApp.Post
the autogenerated resource will be MyApp.Post.Version
. Post
has_many paper_trail_versions
and Version
belong_to source_version
Then for each Api for versioned resources, you need the versioned resource to the Api of the original non-versioned resource's api. There are a few ways to achieve this.
If your Api doesn't use a registry and you're declaring your resources directly in your Api (this is strongly preferred), add the AshPaperTrail.Api
extension to api.
use Ash.Api,
extensions: [
AshPaperTrail.Api
]
This approach does not add the resources to the api, but does allow them to be accessed via the api. In most use cases, this is acceptable; however, in some use cases you'll need each resource to be a part of the api--if your versioned resources are part of a graphql. If that's the case, add each versioned resource explicitly to your api's resources.
use Ash.Api
resources do
resource MyApp.Post
resource MyApp.Post.Version
end
If the Api uses a registry, consider removing the registry and going with one the options above. This third option is deprecated and only for backwards compatibility. Add the AshPaperTrail.Registry
extension to the registry.
use Ash.Registry,
extensions: [
AshPaperTrail.Registry
]
If you are using AshPostgres
, and you want to support destroy actions, you need to do one of two things:
-
use something like
AshArchival
in conjunction with this resource to ensure that destroy actions aresoft?
and do not actually result in row deletion -
configure
AshPaperTrail
not to create references, via:
paper_trail do
reference_source? false
end
By default, attribute values are stored in the changes
attribute. This is to protect you over time as your resources change. However, if there are attributes that you are confident will not change,
you can create attributes for them on the version resource, like so:
paper_trail do
attributes_as_attributes [:organization_id, :author_id]
end
This will make your version resource have foo
and bar
attributes (they will still show up in changes
), i.e
%ThingVersion{foo: "foo", bar: "bar", changes: %{"foo" => "foo", "bar" => "bar"}}
You can record the actor who made the change by declaring one or more resources that can be actors.
paper_trail do
belongs_to_actor :user, MyApp.Accounts.User, api: MyApp.Accounts
belongs_to_actor :news_feed, MyApp.Accounts.NewsFeed, api: MyApp.Accounts
end
Each belongs_to_actor
will create a belongs_to
relationship with the given name destination. 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 a more complex relationship or your actor is not a resource (e.g. String), the actor is always set on Version create and you can store it by adding :on_create
change
in a mixin.
If your resource uses multitenancy, then the strategy, attribute, and parse_attribute options (if any) will be applied to the version resource. If using the attribute strategy you will need to ensure this is also an attribute on the version using the attributes_as_attributes
option (described above) or via a mixin (described below)
If you want to do something like exposing your versions resource over your graphql, you can use the mixin
and version_extensions
options.
For example:
paper_trail do
mixin MyApp.MyResource.PaperTrailMixin
version_extensions extensions: [AshGraphql.Resource]
end
And then you can define a module like so:
defmodule MyApp.MyResource.PaperTrailMixin do
defmacro __using__(_) do
quote do
graphql do
type :my_resource_version
queries do
list :list_versions, action: :read
end
end
end
end
end