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]
# ...
endA 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
endThe 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 withAsh.Resource.Builder.add_change/3for create, update and destroy actions; require_atomic? falseon update and destroy actions, so that Ash can fall back from its atomic path (which goes straight to the data layer) to the oneRamal.Capturehooks into when a branch is current. Seedocs/adr/0005-write-capture.md.
Example
iex> Ramal.Resource.branchable?(Ramal.Test.Farm.Herd)
true
Summary
Functions
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{}).
Whether resource uses Ramal.Resource and has not opted out with branchable? false.
The resources of domain that are branchable.
Refuses a tenant: {:error, %Ramal.Error{reason: :multitenancy_unsupported}} when resource uses multitenancy and
tenant is set, :ok otherwise.
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.
The overlay key of record: the value of its primary key attribute, or a tuple of the values for a composite key.
Whether notifications for writes to resource made inside a branch are sent when Ramal.run/2 returns.
Functions
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}
Whether resource uses Ramal.Resource and has not opted out with branchable? false.
Example
iex> Ramal.Resource.branchable?(Ramal.Test.Farm.Herd)
true
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
]
@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
@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
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"
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