# `Ramal.Resource`
[🔗](https://github.com/agustinrojas1/ramal/blob/v0.2.0/lib/ramal/resource.ex#L1)

The Spark extension that makes an Ash resource branchable.

Add it to a resource and every read action of that resource is patched with the current branch's changes:

    defmodule MyApp.Farm.Herd do
      use Ash.Resource,
        domain: MyApp.Farm,
        data_layer: AshPostgres.DataLayer,
        extensions: [Ramal.Resource]

      # ...
    end

A resource opts out with a `ramal` section. A table that must never be branched, such as the one Ramal stores
branches in, should do so:

    ramal do
      branchable? false
    end

The extension only installs hooks. They do nothing unless the calling process is inside `Ramal.run/2`, so a
resource behaves exactly as before outside a branch.

What the transformer installs, confirmed against the locked Ash 3.37 source:

  * a global preparation, appended with `Ash.Resource.Builder.add_preparation/2`, which Ash applies to every read
    action of the resource;
  * a global change, `Ramal.Capture`, appended with `Ash.Resource.Builder.add_change/3` for create, update and
    destroy actions;
  * `require_atomic? false` on update and destroy actions, so that Ash can fall back from its atomic path (which
    goes straight to the data layer) to the one `Ramal.Capture` hooks into when a branch is current. See
    `docs/adr/0005-write-capture.md`.

## Example

    iex> Ramal.Resource.branchable?(Ramal.Test.Farm.Herd)
    true

# `attributes`

```elixir
@spec attributes(struct()) :: %{optional(atom()) =&gt; term()}
```

The attribute values of an Ash record as a plain map, leaving out relationships, calculations, aggregates and any
attribute that was not loaded (a read with `select` leaves the others as `%Ash.NotLoaded{}`).

## Example

    iex> herd = struct(Ramal.Test.Farm.Herd, id: "r7", name: "R7", head: 30)
    iex> Ramal.Resource.attributes(herd) |> Map.take([:id, :name, :head])
    %{id: "r7", name: "R7", head: 30}

# `branchable?`

```elixir
@spec branchable?(module()) :: boolean()
```

Whether `resource` uses `Ramal.Resource` and has not opted out with `branchable? false`.

## Example

    iex> Ramal.Resource.branchable?(Ramal.Test.Farm.Herd)
    true

# `branchable_resources`

```elixir
@spec branchable_resources(module()) :: [module()]
```

The resources of `domain` that are branchable.

## Example

    iex> Ramal.Resource.branchable_resources(Ramal.Test.Farm) |> Enum.sort()
    [
      Ramal.Test.Farm.Herd,
      Ramal.Test.Farm.HerdVet,
      Ramal.Test.Farm.Move,
      Ramal.Test.Farm.Paddock,
      Ramal.Test.Farm.Vet
    ]

# `ensure_untenanted`

```elixir
@spec ensure_untenanted(module(), term()) :: :ok | {:error, Ramal.Error.t()}
```

Refuses a tenant: `{:error, %Ramal.Error{reason: :multitenancy_unsupported}}` when `resource` uses multitenancy and
`tenant` is set, `:ok` otherwise.

The overlay is keyed by `{resource, primary key}`, so two tenants' rows would share one entry, and the staleness read
of `Ramal.merge/2` has no tenant to read with (`docs/adr/0009-multitenancy-refused.md`). A resource that is
multitenant but `global? true` can still be used without a tenant.

## Example

    iex> Ramal.Resource.ensure_untenanted(Ramal.Test.Farm.Herd, "acme")
    :ok
    iex> {:error, error} = Ramal.Resource.ensure_untenanted(Ramal.Test.Tenanted.Task, "acme")
    iex> error.reason
    :multitenancy_unsupported

# `fingerprint`

```elixir
@spec fingerprint(struct()) :: non_neg_integer() | nil
```

The fingerprint of a record: `:erlang.phash2/1` of its attributes, or `nil` when some attribute is not loaded,
because a hash of part of a row cannot be compared with a hash of the whole row.

## Example

    iex> a = struct(Ramal.Test.Farm.Herd, id: "r7", name: "R7", head: 30)
    iex> b = struct(Ramal.Test.Farm.Herd, id: "r7", name: "R7", head: 31)
    iex> Ramal.Resource.fingerprint(a) == Ramal.Resource.fingerprint(a)
    true
    iex> Ramal.Resource.fingerprint(a) == Ramal.Resource.fingerprint(b)
    false

# `key`

```elixir
@spec key(module(), map()) :: term()
```

The overlay key of `record`: the value of its primary key attribute, or a tuple of the values for a composite key.

## Example

    iex> Ramal.Resource.key(Ramal.Test.Farm.Herd, %{id: "r7"})
    "r7"

# `notify_in_branch?`

```elixir
@spec notify_in_branch?(module()) :: boolean()
```

Whether notifications for writes to `resource` made inside a branch are sent when `Ramal.run/2` returns.

## Example

    iex> Ramal.Resource.notify_in_branch?(Ramal.Test.Farm.Herd)
    false

# `ramal`
*macro* 

---

*Consult [api-reference.md](api-reference.md) for complete listing*
