### Basic Versioning Setup Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/INDEX.md This is the most basic configuration for Paper Trail, enabling versioning for a resource. No additional setup is required. ```elixir defmodule MyApp.Post do use Ash.Resource paper_trail do end end ``` -------------------------------- ### Snapshot Builder Output Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Example output of the Snapshot Builder, showing a complete record of attributes. ```elixir %{ title: "Blog Post", content: "Lorem ipsum...", author_id: "550e8400-e29b-41d4-a716-446655440000", status: "published" } ``` -------------------------------- ### Snapshot Change Builder Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md This example shows the output format for the Snapshot change builder, which stores a snapshot of all configured attributes. ```elixir %{ name: "Updated Name", email: "user@example.com", age: 30 } ``` -------------------------------- ### FullDiff Change Builder Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md This example illustrates the detailed diff format produced by the FullDiff change builder, showing before and after values for attributes, including lists and unchanged fields. ```elixir %{ simple_field: %{from: old_value, to: new_value}, unchanged_field: %{unchanged: current_value}, list_field: [ %{index: 0, from: old_item, to: new_item}, %{index: 1, unchanged: current_item} ] } ``` -------------------------------- ### Resource Configuration with Paper Trail Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Example of how to add the paper_trail DSL section to an Ash resource. ```elixir defmodule MyApp.Accounts.User do use Ash.Resource paper_trail do # configuration options here end end ``` -------------------------------- ### Changes Only Builder Output Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Example output of the Changes Only Builder, showing only modified fields. ```elixir %{ title: "Updated Blog Post", status: "draft" } # Fields like `content`, `author_id` omitted if unchanged ``` -------------------------------- ### Example Version Resource with Mapped Attributes Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Illustrates the structure of a version resource when `attributes_as_attributes` is used, showing direct attributes alongside the `changes` map. ```elixir %ThingVersion{foo: "foo", bar: "bar", changes: %{"foo" => "foo", "bar" => "bar"}} ``` -------------------------------- ### Query Domain Versions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/module-structure.md Example of querying versions for a specific domain resource. This demonstrates filtering and reading audit data. ```elixir MyApp.Post.Version |> Ash.Query.filter(...) |> MyApp.audit_domain.read!() ``` -------------------------------- ### Creating a Versioned Resource with Actor Context Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md This example demonstrates how to create or update a resource while associating an actor (user) with the change. It involves setting the actor in the changeset's context and then querying versions to retrieve the associated actor information. ```elixir current_user = MyApp.Accounts.get!(1) user = MyApp.Accounts.User |> Ash.Changeset.for_update(:update, %{name: "Updated Name"}) |> Ash.Changeset.set_context(%{private: %{actor: current_user}}) |> MyApp.Accounts.update!() # Query versions with actor versions = MyApp.Accounts.User.Version |> Ash.Query.filter(version_source_id == user.id) |> Ash.load!([:modifier]) |> MyApp.audit_domain.read!() versions |> Enum.each(fn version -> IO.puts("Modified by: #{version.modifier.name}") end) ``` -------------------------------- ### Minimal AshPaperTrail Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md This snippet shows the most basic setup for AshPaperTrail, enabling automatic version creation on resource changes. A version resource will be created at `MyApp.Accounts.User.Version` and a `has_many :paper_trail_versions` relationship will be added. ```elixir defmodule MyApp.Accounts.User do use Ash.Resource attributes do uuid :id, primary_key?: true attribute :name, :string attribute :email, :string attribute :inserted_at, :datetime attribute :updated_at, :datetime end actions do create :create update :update destroy :destroy end paper_trail do end end ``` -------------------------------- ### Full Diff Mode Example Output Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Full diff mode stores detailed before-and-after comparisons for each attribute, including specialized handling for nested structures and lists. This mode is useful when complete change documentation is required, despite the overhead. ```elixir %{ name: %{from: "Alice", to: "Alice Smith"}, email: %{unchanged: "alice@example.com"}, profile: %{ bio: %{from: "Engineer", to: "Software engineer"}, location: %{unchanged: "Portland"} }, tags: [ %{index: 0, from: "important", to: "critical"}, %{index: 1, unchanged: "reviewed"} ] } ``` -------------------------------- ### Display Mode Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Shows sensitive attributes in full. This is the default behavior. Use when tracking exact values is necessary for compliance or debugging. ```elixir %{ password_hash: "$2b$12$...", ssn: "123-45-6789" } ``` -------------------------------- ### Snapshot Mode Example Output Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md In snapshot mode, the complete current state of all non-ignored attributes is stored. This is useful for reconstructing the full state of a resource at any point in time. ```elixir %{ name: "Alice Smith", email: "alice@example.com", status: "active", profile: %{bio: "Software engineer", location: "Portland"} } ``` -------------------------------- ### ChangesOnly Change Builder Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md This example demonstrates the output of the ChangesOnly change builder, which only includes attributes that were explicitly modified. ```elixir # If only name and age changed: %{name: "Updated Name", age: 31} ``` -------------------------------- ### Configure Paper Trail with Mixin and Version Extensions Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Use the `mixin` and `version_extensions` options to enrich your versions resource, for example, to expose it over GraphQL. Ensure your mixin module and its function are correctly defined. ```elixir paper_trail do mixin {MyApp.MyResource.PaperTrailMixin, :graphql, [:my_resource_version]} relationship_opts public?: true version_extensions extensions: [AshGraphql.Resource] end ``` -------------------------------- ### Get All Versions for a Resource Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Retrieve all historical versions of a user resource, sorted by insertion time in descending order. This is useful for auditing or reconstructing past states. ```elixir user = MyApp.Accounts.get!(user_id) versions = MyApp.Accounts.User.Version |> Ash.Query.filter(version_source_id == user.id) |> Ash.Query.sort(inserted_at: :desc) |> MyApp.audit_domain.read!() Enum.each(versions, fn version -> IO.puts("#{version.inserted_at}: #{version.version_action_type}") end) ``` -------------------------------- ### Configure Paper Trail Domain Extension Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Add the `paper_trail` section to an Ash domain using the `AshPaperTrail.Domain` extension. This example shows how to enable version tracking. ```elixir defmodule MyApp.Audit do use Ash.Domain paper_trail do include_versions? true end end ``` -------------------------------- ### Get Version Resource Module Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Retrieve the specific module used for version resources associated with a given Ash resource. ```elixir iex> AshPaperTrail.Resource.Info.version_resource(MyApp.Accounts.User) MyApp.Accounts.User.Version ``` -------------------------------- ### Ignore Mode Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Omits sensitive attributes entirely from version records. Use when sensitive attributes should not appear in audit logs at all. ```elixir # Only non-sensitive attributes included %{ email: "alice@example.com", status: "active" } ``` -------------------------------- ### Changes Only Mode Example Output Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Changes only mode stores only the attributes that were modified during an action. This provides a compact audit trail, saving storage space. Unchanged fields are omitted. ```elixir %{ name: "Alice Smith", status: "active" } ``` -------------------------------- ### Define Paper Trail Mixin Module for GraphQL Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Define a module to handle Paper Trail mixin logic, specifically for GraphQL integration. This example shows how to define a `graphql` function that sets up queries for listing versions. ```elixir defmodule MyApp.MyResource.PaperTrailMixin do def graphql(type) do quote do graphql do type unquote(type) queries do list :list_versions, action: :read end end end end end ``` -------------------------------- ### Get Belongs To Actor Configurations Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Retrieve all 'belongs_to_actor' configurations for an Ash resource. This is useful for understanding how actors are associated with versions. ```elixir iex> AshPaperTrail.Resource.Info.belongs_to_actor(MyApp.Accounts.User) [%AshPaperTrail.Resource.BelongsToActor{name: :user, destination: MyApp.Accounts.User, ...}] ``` -------------------------------- ### Accessing and Loading Versions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Demonstrates how to load and inspect the paper trail versions associated with a resource. This involves loading the `paper_trail_versions` relationship and accessing attributes like `version_source_id`, `version_action_type`, `changes`, and `inserted_at`. ```elixir user = MyApp.Accounts.read!(1) # Load versions user_with_versions = user |> Ash.load!([:paper_trail_versions]) IO.inspect(user_with_versions.paper_trail_versions) # [ # %MyApp.Accounts.User.Version{ # version_source_id: "...", # version_action_type: :create, # changes: %{name: "Alice", email: "alice@example.com"}, # inserted_at: ~U[2024-01-15 10:00:00Z] # } # ] ``` -------------------------------- ### BelongsToActor Usage in Resource Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Example of how to configure the belongs_to_actor relationship within an Ash.Resource module to track user or admin changes. ```elixir defmodule MyApp.Accounts.User do use Ash.Resource paper_trail do # Track which user made changes belongs_to_actor :creator, MyApp.Accounts.User, domain: MyApp.Accounts, allow_nil?: false # Support polymorphic actors belongs_to_actor :admin, MyApp.Accounts.Admin, domain: MyApp.Accounts, on_delete: :restrict end end ``` -------------------------------- ### Create Resource with Audit Metadata Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md When creating or updating a resource, you can provide audit metadata through the changeset context. This allows you to log specific reasons or contextual information for the change. After the update, the code demonstrates how to query the audit history for the created product. ```elixir product = MyApp.Catalog.Product |> Ash.Changeset.for_update(:update, %{price: Decimal.new("29.99")}) |> Ash.Changeset.set_context(%{ private: %{actor: current_user}, paper_trail_metadata: %{ change_reason: "Seasonal sale", change_ip: "203.0.113.42", change_timestamp: DateTime.utc_now() } }) |> MyApp.Catalog.update!() # Query audit history MyApp.Catalog.Product.Version |> Ash.Query.filter(version_source_id == product.id) |> Ash.Query.sort(inserted_at: :desc) |> MyApp.audit_domain.read!() |> Enum.each(fn version -> IO.puts("Reason: #{version.change_reason}") IO.puts("IP: #{version.change_ip}") end) ``` -------------------------------- ### Configure Formatter for AshPaperTrail Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Add `:ash_paper_trail` to your `.formatter.exs` under `import_deps` to ensure proper code formatting. ```elixir [ import_deps: [ :ash_paper_trail ] ] ``` -------------------------------- ### Add AshPaperTrail Dependency Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Add the AshPaperTrail dependency to your `mix.exs` file. ```elixir {:ash_paper_trail, "~> 0.6.0"} ``` -------------------------------- ### Redact Mode Example Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Replaces sensitive attribute values with 'REDACTED'. Use when you need to know a sensitive field changed but don't want to store its value. ```elixir %{ password_hash: "REDACTED", ssn: "REDACTED" } ``` -------------------------------- ### Add AshPaperTrail Domain Extension Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Include the `AshPaperTrail.Domain` extension in your domain configuration. ```elixir use Ash.Domain, extensions: [ AshPaperTrail.Domain ] ``` -------------------------------- ### Get Change Tracking Mode Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Determine the change tracking mode configured for an Ash resource. This indicates how changes are recorded (e.g., full diff, changes only). ```elixir iex> AshPaperTrail.Resource.Info.change_tracking_mode(MyApp.Accounts.User) :full_diff ``` -------------------------------- ### Specify Actions for Versioning with `on_actions` Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Use `on_actions` to define a specific list of action names that should trigger version creation. This is useful for fine-grained control over which operations are audited. Cannot be used with `ignore_actions`. ```elixir defmodule MyApp.Post do use Ash.Resource actions do create :create update :update update :update_draft update :publish update :unpublish end paper_trail do on_actions [:create, :publish, :unpublish] end end ``` -------------------------------- ### Apply AshPaperTrail Resource Extension Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Apply the `AshPaperTrail.Resource` extension to a resource and configure its change tracking behavior. ```elixir use Ash.Resource, domain: MyDomain, extensions: [ AshPaperTrail.Resource ] paper_trail do primary_key_type :uuid_v7 # default is :uuid 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 ignore_actions [:destroy] # default is [] end ``` -------------------------------- ### Recommended Indexes for Post Versions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Defines recommended indexes for the `Post.Version` resource in Ash, particularly for SQL data layers. These indexes improve query performance for historical lookups and per-resource history. ```elixir defmodule MyApp.Post.Version do use Ash.Resource actions do read :list do pagination offset?: true, keyset?: true, default_limit: 50 end end # For SQL data layers, create indexes: # CREATE INDEX idx_post_versions_source_id ON post_versions(version_source_id); # CREATE INDEX idx_post_versions_inserted_at ON post_versions(inserted_at DESC); end ``` -------------------------------- ### Query Versions within a Time Range Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Filter historical versions of a user resource to retrieve only those inserted within a specified time range, for example, the last 30 days. This is useful for time-bound audits. ```elixir start_date = DateTime.add(DateTime.utc_now(), -30, :day) MyApp.Accounts.User.Version |> Ash.Query.filter(version_source_id == user_id) |> Ash.Query.filter(inserted_at >= ^start_date) |> Ash.Query.sort(inserted_at: :desc) |> MyApp.audit_domain.read!() ``` -------------------------------- ### Include All Version Resources in Domain Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Enable the inclusion of all autogenerated version resources in the domain by setting `include_versions? true`. ```elixir paper_trail do include_versions? true end ``` -------------------------------- ### Comprehensive Ash Paper Trail Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md This snippet demonstrates an extensive configuration for Ash Paper Trail, specifying detailed settings for change tracking, attribute management, actions to track, actor association, metadata, sensitive data, and custom version resources. ```elixir defmodule MyApp.Post do use Ash.Resource attributes do uuid :id, primary_key?: true attribute :title, :string attribute :content, :string attribute :published_at, :datetime attribute :view_count, :integer attribute :author_id, :uuid attribute :inserted_at, :datetime attribute :updated_at, :datetime end paper_trail do # Storage and tracking change_tracking_mode :full_diff primary_key_type :uuid # What to track attributes_as_attributes [:published_at] ignore_attributes [:view_count, :inserted_at, :updated_at] on_actions [:create, :publish, :unpublish, :update] create_version_on_destroy? false # Auditing store_action_name? true store_action_inputs? true store_resource_identifier? true resource_identifier :post # Relationships belongs_to_actor :author, MyApp.Accounts.User, domain: MyApp.Accounts, allow_nil?: false # Metadata metadata :change_reason, :string, allow_nil?: false metadata :change_ip, :string # Sensitive data sensitive_attributes :redact # Customization version_resource MyApp.PostVersion mixin MyApp.PostVersionMixin table_name "post_versions" end end ``` -------------------------------- ### Define Version Creation Context Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Creates a special context for version creation to signal internal processing and preserve shared context. This prevents infinite loops during version creation. ```elixir defp version_context(changeset) do %{ ash_paper_trail?: true, # marks this as internal version creation shared: changeset.context[:shared] || %{} # preserves shared context } end ``` -------------------------------- ### Test Post Creation and Versioning in Elixir Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Tests that creating a post correctly generates a version record. It also shows how to disable versioning for specific operations. ```elixir defmodule MyApp.PostTest do use ExUnit.Case test "creating a post creates a version" do post = MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Test"}) |> MyApp.Catalog.create!() versions = MyApp.Post.Version |> Ash.Query.filter(version_source_id == ^post.id) |> MyApp.audit_domain.read!() assert length(versions) == 1 version = List.first(versions) assert version.version_action_type == :create assert version.changes.title == "Test" end test "disabling versions works" do post = MyApp.Post |> Ash.Changeset.for_create(:create, %{title: "Test"}) |> Ash.Changeset.set_context(ash_paper_trail_disabled?: true) |> MyApp.Catalog.create!() versions = MyApp.Post.Version |> Ash.Query.filter(version_source_id == ^post.id) |> MyApp.audit_domain.read!() assert length(versions) == 0 end end ``` -------------------------------- ### Direct Attribute Creation from Source Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Creates attributes on the version resource directly from the `attributes_as_attributes` configuration of the source resource. This allows specific fields to be mirrored. ```elixir attribute :status, :string # if :status in attributes_as_attributes ``` -------------------------------- ### Configure Domain-Level Paper Trail Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Use the `:paper_trail` DSL extension within an Ash Domain to enable automatic inclusion of all version resources. Set `include_versions?` to `true` to activate this behavior. ```elixir defmodule MyApp.Accounts do use Ash.Domain paper_trail do include_versions? true end end ``` -------------------------------- ### Build Version Input Map Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Constructs the input map for a version record, handling different operation modes and actor relationships. ```elixir defp build_notifications(changeset, result, opts \\ []) :: {Ash.Changeset.t(), map(), any} | map() ``` -------------------------------- ### Tracking Multiple Actor Types with Paper Trail Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Configure a resource to track different types of actors (e.g., users and admins) that modify it. Set context with the appropriate actor and metadata when performing updates. ```elixir defmodule MyApp.Accounts.User do use Ash.Resource paper_trail do belongs_to_actor :modified_by_user, MyApp.Accounts.User, domain: MyApp.Accounts, allow_nil?: true belongs_to_actor :modified_by_admin, MyApp.Accounts.Admin, domain: MyApp.Accounts, allow_nil?: true metadata :actor_type, :string, allow_nil?: false end end # Set context with appropriate actor and metadata user |> Ash.Changeset.for_update(:update, %{name: "New Name"}) |> Ash.Changeset.set_context(%{ private: %{actor: admin}, paper_trail_metadata: %{actor_type: "admin"} }) |> MyApp.Accounts.update!() ``` -------------------------------- ### Full Diff Builder Change Structures Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Illustrates the different structures for representing changes in the Full Diff Builder, including simple changes, unchanged values, list diffs, and embedded record diffs. ```elixir # Simple change (scalar type changed) %{from: old_value, to: new_value} # Unchanged simple value %{unchanged: current_value} # List with per-item tracking [ %{index: 0, from: old_item, to: new_item}, %{index: 1, unchanged: unchanged_item} ] # Embedded record changes (recursive) %{ nested_field: %{from: old_nested, to: new_nested} } ``` -------------------------------- ### Include Specific Version Resources in Domain Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Manually include specific version resources in your domain's resource list. ```elixir resources do resource MyApp.Post resource MyApp.Post.Version # <- add version resource end ``` -------------------------------- ### Full Diff Tracking Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/INDEX.md Enable full diff tracking to capture the complete changes between versions. Note that atomic updates may require `require_atomic? false`. ```elixir paper_trail do change_tracking_mode :full_diff end actions do update :update do require_atomic? false end end ``` -------------------------------- ### Configure Relationship Options Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Pass options to the auto-generated `has_many :paper_trail_versions` relationship on the source resource using `relationship_opts`. Common options include `public?`, `sort`, and `manual`. ```elixir paper_trail do relationship_opts [ public?: true, sort: [inserted_at: :desc], manual: true ] end ``` -------------------------------- ### Configure Paper Trail for Ash Domain Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/module-structure.md Integrate paper_trail into your Ash.Domain to control the auto-inclusion of version resources. Set `include_versions?` to `true` to automatically include version resources. ```elixir use Ash.Domain paper_trail do include_versions? true end ``` -------------------------------- ### Control Version Creation When No Changes Occur Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Configure whether version records are created for actions that do not modify any data. Set to `false` to track every action attempt, not just those with changes. ```elixir paper_trail do only_when_changed? false end ``` -------------------------------- ### Configure GraphQL Integration for Post Versions in Elixir Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Configures a resource to expose its paper trail versions via GraphQL. This involves adding `AshGraphql.Resource` to `version_extensions` and setting `public?: true` for relationship options. ```elixir defmodule MyApp.Catalog.Post do use Ash.Resource paper_trail do version_extensions [ extensions: [AshGraphql.Resource] ] relationship_opts [ public?: true ] end end ``` -------------------------------- ### Configure Full Diff Change Tracking Mode Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Set `change_tracking_mode` to `:full_diff` to store detailed before and after states for each change. This provides a comprehensive audit trail but requires more processing and storage. Note that this mode requires `require_atomic?: false` on actions. ```elixir defmodule MyApp.Post do use Ash.Resource paper_trail do change_tracking_mode :full_diff end end # Version contains: # %{ # title: %{from: "Old Title", to: "New Title"}, # content: %{unchanged: "..."}, # tags: [ # %{index: 0, from: "old-tag", to: "new-tag"}, # %{index: 1, unchanged: "unchanged-tag"} # ] # } ``` -------------------------------- ### Define Custom Behavior with a Mixin Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Use a module to define custom calculations and actions for version resources. This allows for adding specific logic like human-readable action types or read actions. ```elixir defmodule MyApp.VersionMixin do defmacro __using__(_opts) do quote do calculations do calculate :human_action_type, :string, expr( case version_action_type do :create -> "Created" :update -> "Modified" :destroy -> "Deleted" _ -> "Unknown" end ) end actions do read :list end end end end defmodule MyApp.Post do use Ash.Resource paper_trail do mixin MyApp.VersionMixin end end ``` -------------------------------- ### Configuring Paper Trail Metadata in Ash Resource Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Demonstrates how to configure metadata attributes for versioning within an Ash.Resource using the `paper_trail` context. This allows capturing custom audit information. ```elixir defmodule MyApp.Post do use Ash.Resource paper_trail do metadata :edit_reason, :string, allow_nil?: false metadata :editor_ip, :string metadata :change_category, {:one_of, [:feature, :bugfix, :chore]} end end ``` -------------------------------- ### Configure Paper Trail to Store Specific Attributes Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Use `attributes_as_attributes` to specify which attributes should be directly mapped as attributes on the version resource, in addition to being stored in `changes`. ```elixir paper_trail do attributes_as_attributes [:organization_id, :author_id] end ``` -------------------------------- ### Integrate Paper Trail Extension for Domains Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/module-structure.md Use this extension to integrate Ash Paper Trail's versioning capabilities for domains. It allows resource versions to be managed within the domain. ```elixir use Spark.Dsl.Extension, transformers: [ AshPaperTrail.Domain.Transformers.AllowResourceVersions ], sections: [@paper_trail] ``` -------------------------------- ### Optimize Version Creation for Bulk Operations Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Efficiently create multiple versions in a single database operation using Ash's bulk create functionality. This is optimized for performance when dealing with large datasets. ```elixir # Create multiple posts posts_data = [ %{title: "Post 1", content: "..."}, %{title: "Post 2", content: "..."}, %{title: "Post 3", content: "..."} ] result = MyApp.Post |> Ash.bulk_create!( :create, posts_data, context: %{private: %{actor: current_user}}, return_records?: true ) # All versions created in a single bulk insert # (not N individual inserts) result.records |> Enum.each(fn post -> IO.puts("Created post: #{post.id}") end) ``` -------------------------------- ### AshPaperTrail.Resource.Metadata.t() Struct Fields Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Defines the fields for a metadata attribute on a version resource. Use this to understand the structure of metadata configurations. ```elixir defstruct [ :__spark_metadata__, :name, :type, :constraints, :allow_nil? ] ``` -------------------------------- ### Enable Automatic Version Resource Discovery Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md When `include_versions?` is set to `true` within the `paper_trail` configuration, version resources are automatically discovered and managed by the domain. This is useful for centralized audit domains. ```elixir defmodule MyApp.Audit do use Ash.Domain resources do # manually define resources end paper_trail do include_versions? true end end ``` -------------------------------- ### Integrate Paper Trail Extension for Resources Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/module-structure.md Use this extension to integrate Ash Paper Trail's versioning capabilities for resources. It includes transformers for validation, relating, creating, and versioning on changes. ```elixir use Spark.Dsl.Extension, sections: [@paper_trail], transformers: [ AshPaperTrail.Resource.Transformers.ValidateBelongsToActor, AshPaperTrail.Resource.Transformers.RelateVersionResource, AshPaperTrail.Resource.Transformers.CreateVersionResource, AshPaperTrail.Resource.Transformers.VersionOnChange ] ``` -------------------------------- ### Add Custom Behavior to Version Resources with Mixins Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Define custom calculations and actions for version resources by creating a mixin module. This allows for tailored version tracking logic. ```elixir defmodule MyApp.VersionMixin do defmacro __using__(_opts) do quote do calculations do calculate :human_action, :string, expr( case version_action_type do :create -> "Created" :update -> "Modified" :destroy -> "Deleted" _ -> "Unknown" end ) end actions do read :list do pagination offset?: true, keyset?: true, default_limit: 50 end end end end end defmodule MyApp.Post do use Ash.Resource paper_trail do mixin MyApp.VersionMixin end end # Use the mixin's actions and calculations MyApp.Post.Version |> Ash.Query.select([:human_action, :inserted_at]) |> MyApp.audit_domain.read!() ``` -------------------------------- ### Configure Paper Trail to Not Version Destroys Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Set `create_version_on_destroy?` to `false` within the `paper_trail` configuration to prevent versions from being created when a resource is destroyed. ```elixir paper_trail do create_version_on_destroy? false end ``` -------------------------------- ### Capture Action Name and Inputs in Versions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Configure Paper Trail to store the name and inputs of actions performed on a resource. This is crucial for compliance and debugging, providing a detailed audit trail. ```elixir defmodule MyApp.Accounts.User do use Ash.Resource paper_trail do store_action_name? true store_action_inputs? true end end # Version contains: # %{ # version_action_name: :update, # version_action_inputs: %{ # "name" => "Alice Smith", # "email" => "alice@example.com", # "password" => "REDACTED" # auto-redacted for sensitive # } # } ``` -------------------------------- ### AshPaperTrail.Domain Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Configures domain-level paper trail settings. This allows for global configuration of paper trail behavior within a domain. ```APIDOC ## AshPaperTrail.Domain Configuration Options ### Description A Spark DSL extension that adds domain-level paper trail configuration. ### DSL Section `:paper_trail` ### Configuration Options #### Options - **include_versions?** (:boolean) - Optional - Automatically include all version resources in the domain when enabled. Defaults to `false`. ### Example ```elixir defmodule MyApp.Accounts do use Ash.Domain paper_trail do include_versions? true end end ``` ``` -------------------------------- ### AshPaperTrail.Resource.Info Functions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Provides functions to introspect AshPaperTrail.Resource configurations. These functions accept either a compiled resource module or a Spark DSL state. ```APIDOC ## AshPaperTrail.Resource.Info ### Description Introspection helpers for accessing AshPaperTrail.Resource configuration on a resource. ### Functions All functions accept either a compiled resource module or a Spark DSL state. - `primary_key_type/1` - Get the primary key type for the version resource. Returns `:uuid` by default. - `only_when_changed?/1` - Check if versions are only created on actual changes. Returns `true` by default. - `attributes_as_attributes/1` - Get a list of attributes stored as attributes instead of in changes. Returns `[]` by default. - `belongs_to_actor/1` - Get all belongs_to_actor configurations. Returns `[]` by default. - `metadata/1` - Get all metadata configurations. Returns `[]` by default. - `change_tracking_mode/1` - Get the change tracking mode (`:snapshot`, `:changes_only`, `:full_diff`). - `ignore_attributes/1` - Get a list of ignored attributes. Returns `[]` by default. - `ignore_actions/1` - Get a list of ignored actions. Returns `[]` by default. - `mixin/1` - Get the mixin configuration. Returns `nil` by default. - `on_actions/1` - Get actions that trigger versioning. - `reference_source?/1` - Check if a foreign key reference is created. Returns `true` by default. - `relationship_opts/1` - Get options for the paper_trail_versions relationship. Returns `[]` by default. - `store_action_name?/1` - Check if the action name is stored. Returns `false` by default. - `store_action_inputs?/1` - Check if action inputs are stored. Returns `false` by default. - `store_resource_identifier?/1` - Check if the resource identifier is stored. Returns `false` by default. - `sensitive_attributes/1` - Get the sensitive attribute handling mode (`:display`, `:ignore`, `:redact`). - `resource_identifier/1` - Get the custom resource identifier name. - `version_extensions/1` - Get extensions for the version resource. Returns `[]` by default. - `version_resource/1` - Get the version resource module. - `public_timestamps?/1` - Check if version timestamps are public. Returns `false` by default. - `create_version_on_destroy?/1` - Check if versions are created on destroy. Returns `true` by default. ### Example ```elixir iex> AshPaperTrail.Resource.Info.version_resource(MyApp.Accounts.User) MyApp.Accounts.User.Version iex> AshPaperTrail.Resource.Info.change_tracking_mode(MyApp.Accounts.User) :full_diff iex> AshPaperTrail.Resource.Info.belongs_to_actor(MyApp.Accounts.User) [%AshPaperTrail.Resource.BelongsToActor{name: :user, destination: MyApp.Accounts.User, ...}] ``` ``` -------------------------------- ### Apply Extensions to Version Resource Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Use `version_extensions` to apply additional extensions to the version resource, such as `AshGraphql.Resource` for GraphQL exposure or `Ash.Notifiers.PubSub` for change notifications. ```elixir paper_trail do version_extensions [ extensions: [AshGraphql.Resource], notifier: [Ash.Notifiers.PubSub] ] end ``` -------------------------------- ### AshPaperTrail.Domain.Info Functions Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Provides introspection helpers for accessing AshPaperTrail.Domain configuration. ```APIDOC ## AshPaperTrail.Domain.Info ### Description Introspection helper for accessing AshPaperTrail.Domain configuration. ### Functions - `include_versions?/1` - Check if version resources are auto-included in the domain. Returns `false` by default. ``` -------------------------------- ### Snapshot Builder Algorithm Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Builds a complete snapshot of all attributes, serializing values using Ash.Type.dump_to_embedded. Ignores whether attributes actually changed. ```elixir Enum.reduce(attributes, %{}, fn attribute, changes -> value = Map.get(result, attribute.name) {:ok, dumped_value} = Ash.Type.dump_to_embedded(attribute.type, value, attribute.constraints) Map.put(changes, attribute.name, dumped_value) end) ``` -------------------------------- ### Filter Versions by Action Type Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Query for specific historical versions of a user resource that match a particular action type, such as `:update`. This helps in isolating changes of a certain kind. ```elixir MyApp.Accounts.User.Version |> Ash.Query.filter(version_source_id == user_id) |> Ash.Query.filter(version_action_type == :update) |> MyApp.audit_domain.read!() ``` -------------------------------- ### Optimize High-Volume Change Tracking Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Configure PaperTrail for resources with frequent updates to minimize storage and processing overhead. Options include tracking only changes, ignoring specific actions, and disabling versioning on destroy. ```elixir defmodule MyApp.Analytics.Event do use Ash.Resource paper_trail do # Use changes_only to minimize storage change_tracking_mode :changes_only # Skip version for high-frequency updates ignore_actions [:increment_view_count, :refresh_cache] # Don't version on destroy (if events are ephemeral) create_version_on_destroy? false # Minimal metadata # (avoid storing action inputs for each event) end end ``` -------------------------------- ### Store Resource Identifier with `store_resource_identifier?` Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/configuration.md Enable `store_resource_identifier?` to `true` to add a `version_resource_identifier` attribute. This helps in identifying the resource type for versions, especially when auditing across multiple resources. ```elixir paper_trail do store_resource_identifier? true resource_identifier :post end ``` -------------------------------- ### Setting Context Flags for Updates Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/types.md Demonstrates how to set context flags like paper_trail_metadata and sensitive_attributes for an update operation. Also shows how to set private context for the actor. ```elixir user = MyApp.Accounts.User |> Ash.Changeset.for_update(:update, %{name: "New Name"}) |> Ash.Changeset.set_context(%{ paper_trail_metadata: %{reason_for_change: "Admin correction"}, sensitive_attributes: :redact }) |> Ash.Changeset.set_context(%{ private: %{actor: current_user} }) |> MyApp.Accounts.update!() ``` -------------------------------- ### Configure Paper Trail to Ignore Reference Source Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Set `reference_source?` to `false` in the `paper_trail` configuration to prevent Ash Paper Trail from creating a foreign key for the version source attribute, which is not recommended. ```elixir paper_trail do reference_source? false end ``` -------------------------------- ### AshPaperTrail.allow_resource_versions/2 Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/api-reference.md Determines if a given Ash resource is configured to support versioning with AshPaperTrail. It checks for explicit configuration, legacy naming conventions, or custom logic via an MFA tuple. ```APIDOC ## AshPaperTrail.allow_resource_versions/2 ### Description This function determines whether a resource has AshPaperTrail versioning enabled. It supports three detection methods: MFA-based detection, explicit configuration via `AshPaperTrail.Resource` extension, and legacy naming conventions. ### Signature ```elixir def allow_resource_versions({m, f, a}, resource) :: boolean def allow_resource_versions(nil, resource) :: boolean ``` ### Parameters #### Function Arguments - **mfa_tuple** (`{module, function, args}`) - Optional - A module-function-args tuple to call for dynamic version resource detection. - **resource** (`Ash.Resource.t()`) - Required - The Ash resource to check for version capability. ### Return Type `boolean` - Returns `true` if the resource is configured with AshPaperTrail versioning, `false` otherwise. ### Example ```elixir # Direct check AshPaperTrail.allow_resource_versions(nil, MyApp.Users.User.Version) # With MFA tuple for custom logic AshPaperTrail.allow_resource_versions( {MyApp.VersionResolver, :allow_versions, []}, MyApp.Accounts.User.Version ) ``` ### Throws Returns `false` on `ArgumentError` when the resource module cannot be found. ``` -------------------------------- ### Define Paper Trail Mixin for Destroy Actions Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Create a mixin module to configure the underlying reference for versioning, setting `on_delete: :delete` to manage how versions are handled during destroy actions. ```elixir defmodule MyApp.MyResource.PaperTrailMixin do def mixin do # quote here is because we are returning code to be evaluated inside of the # calling module quote do postgres do references do reference :version_source, on_delete: :delete end end end end end ``` -------------------------------- ### Snapshot Change Builder Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/module-structure.md Builds a snapshot of all non-ignored attributes. Use this when you need a complete record of resource state. ```elixir def build_changes(attributes, changeset, result) :: map ``` -------------------------------- ### Apply Paper Trail Mixin in Resource Configuration Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Include the defined Paper Trail mixin in your resource's `paper_trail` configuration to apply the specified destroy action handling. ```elixir paper_trail do mixin {MyApp.MyResource.PaperTrailMixin, :mixin, []} end ``` -------------------------------- ### Conditionally Skip Versioning When No Changes Occur Source: https://github.com/ash-project/ash_paper_trail/blob/main/documentation/tutorials/getting-started-with-ash-paper-trail.md Set the `only_when_changed?` option to `true` in the `paper_trail` DSL to track changes only when they occur. To opt into this conditionally for a specific action, set the context `%{skip_version_when_unchanged?: true}`. ```elixir change set_context(%{skip_version_when_unchanged?: true}) ``` -------------------------------- ### Conditions for Tracking Version Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/implementation-details.md Checks if version tracking is enabled for the current action based on context flags, ignored actions, and action types. ```elixir def valid_for_tracking?(%Ash.Changeset{} = changeset) do !changeset.context[:ash_paper_trail_disabled?] && changeset.action.name not in AshPaperTrail.Resource.Info.ignore_actions(changeset.resource) && (changeset.action_type == :create || (changeset.action_type == :destroy && AshPaperTrail.Resource.Info.create_version_on_destroy?(changeset.resource)) || (changeset.action_type == :update && changeset.action.name in AshPaperTrail.Resource.Info.on_actions(changeset.resource))) end ``` -------------------------------- ### Specify Custom Version Resource Module Source: https://github.com/ash-project/ash_paper_trail/blob/main/_autodocs/usage-patterns.md Define a custom module for storing version history instead of the default. This allows for more control over the version resource's schema and behavior. ```elixir defmodule MyApp.PostVersionHistory do use Ash.Resource # ... custom configuration ... end defmodule MyApp.Post do use Ash.Resource paper_trail do version_resource MyApp.PostVersionHistory end end ```