# AshPaperPlane

An extension to [AshPaperTrail](https://hexdocs.pm/ash_paper_trail), which is
itself an extension to [Ash](https://hexdocs.pm/ash). It augments the version
resource AshPaperTrail generates, and does two independent things.

- It lifts the action's shared context onto the version, so a version records
  where a change came from rather than only what changed.
- It enqueues an Oban job when a version is created, in the data layer's
  transaction where the data layer opens one.

A resource asks for either or both.

## Installation

This library is experimental and under active development. Its API, code, and
design are subject to change.

```elixir
def deps do
  [
    {:ash_paper_plane, "~> 0.1.0"}
  ]
end
```

The API reference is at [hexdocs.pm/ash_paper_plane](https://hexdocs.pm/ash_paper_plane).

## Usage

Both halves arrive through the `paper_trail` block's `mixin` option.

```elixir
defmodule MyApp.Blog.Post do
  use Ash.Resource,
    domain: MyApp.Blog,
    extensions: [AshPaperTrail.Resource]

  paper_trail do
    mixin {AshPaperPlane.VersionMixin, :extend,
           [[metadata: true, worker: MyApp.VersionCreatedWorker]]}
  end
end
```

`metadata: true` adds a `metadata` attribute to the version and fills it. A
`worker` enqueues a job. Neither implies the other, so a resource wanting one
names one.

Six options shape the two halves, and `AshPaperPlane.VersionMixin` documents
each with its default.

## The metadata half carries what you stamp

The keys come from `source_context.shared`, which is the application's to fill.
A key absent from the context, or carried with a `nil` value, is left out.

Stamping it is the caller's job, and the natural place is wherever a verified
value already exists:

    post
    |> Ash.Changeset.for_update(:update, params)
    |> Ash.Changeset.set_context(%{shared: %{origin: "web", correlation_id: id}})
    |> Ash.update!()

That version records `origin` and `correlation_id` because they were set, and
omits `causation_id` because it was not.

## The Oban half rides your transaction, if you have one

The job is inserted in an `after_action` hook. Where the resource's data layer
opens a transaction on the repo Oban is configured with, the insert joins it. A
job cannot then survive an action that rolled back, and a version cannot commit
while its job is lost.

Both conditions are yours to meet rather than the library's to guarantee. A
resource whose data layer opens no transaction, or an Oban pointed at another
repo, still enqueues its job, with nothing tying the two together.

## What it leaves to you

- Running the worker. What the job does when it runs is yours.
- Deciding what belongs in `shared`. The mixin carries the keys you name.
- The version resource itself, which is AshPaperTrail's.

## Testing this library

The suite needs PostgreSQL, because Oban's assertions read its jobs table. Point
it at one with `POSTGRES_HOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, and
`POSTGRES_DB`, or take the defaults, and run:

```
mix test
```
