AshReferentialActions.Changes.EnsureTargetLive (ash_referential_actions v0.1.0)

Copy Markdown View Source

Runtime guard added to every create and update action of an resource that declares restrict or nilify: writing the foreign key is rejected unless the target exists and is live.

Without this, a user could be pointed at an already-archived (or, on data layers without foreign keys, missing) target — a ghost reference the destroy-side guard can never prevent, since it only fires on the target.

The check runs in two hooks:

  • before_action — catches direct attribute input early, before any work is done.
  • after_action — re-checks the foreign keys that actually changed on the result record, catching values set after before_action (notably manage_relationship, which applies belongs_to keys in its own hooks). On transactional data layers the error rolls the write back; on non-transactional layers (e.g. ETS) the error is returned but the write is not undone — the same caveat as any after-action validation there.

The lookup takes a FOR SHARE lock on the target row where the data layer supports one, so a concurrent archive of that target must wait: either it commits first and this check sees the archived row, or this write commits first and the archive's own guard sees this user. Without lock support (e.g. ETS) the check stays a plain application-level read.

The target is looked up via the referential relationship's declared read_action (or the referenced resource's primary read — a verifier rejects a filtered primary read as the default), so action-level read filters cannot silently hide a live target; archival's global filter still applies, so an archived target counts as not live.