Ramal.Preparation (Ramal v0.2.0)

Copy Markdown View Source

The read preparation Ramal.Resource installs on every read action.

Inside Ramal.run/2 it makes a read see the base data patched with the branch's overlay. Outside a branch it returns the query untouched, so the action behaves as if the extension were absent.

How it works, confirmed against Ash 3.37 (lib/ash/actions/read/read.ex):

  • Ash.Query.before_action/2 hooks run after all preparations and before the data-layer query is built, so a hook there can still remove limit and offset. The base query keeps its filter, so Postgres still narrows the rows.
  • Ash.Query.after_action/2 hooks receive the base rows after the data layer and before relationship loads, runtime calculations and the not-found check of Ash.get/3. Patching there means loads, calculations and Ash.get/3 all see the patched rows.
  • A relationship load runs a read on the related resource through its own preparation, in a task that does not inherit the process dictionary. The branch id therefore also travels in the query's shared context, which Ash copies into related queries (lib/ash/actions/read/relationships.ex).
  • Filters are evaluated on overlay records with Ash.Filter.Runtime.filter_matches/4 and sorting uses Ash.Actions.Sort.runtime_sort/3.

Every base row the read returns is also fingerprinted (Ramal.Resource.fingerprint/1) for the staleness check at merge time, unless the query selected only some attributes.

Known limits in 0.1, documented rather than hidden:

  • aggregates and calculations the data layer computes do not see the overlay; reading one emits [:ramal, :read, :unpatched_aggregate]. Ramal.aggregate/4 is the exact alternative for the common kinds;
  • overlay records are added without running the action's authorize_results checks, though the query's filter, including any policy filter, is applied to them;
  • paginated reads (page:) are paged by the database and patched afterwards, so pages can be short or miss rows created in the branch, and count is the database's.

Example

# With a branch current, the same call returns rows created in the branch.
Ramal.run!(branch, fn -> Ash.read!(MyApp.Farm.Herd) end)

Summary

Functions

init(opts)

Callback implementation for Ash.Resource.Preparation.init/1.

supports(opts)

Callback implementation for Ash.Resource.Preparation.supports/1.

temporal_safe?(opts)

Callback implementation for Ash.Resource.Preparation.temporal_safe?/1.