A Host Finds Its Frame by Owner and Key
Context
A host that shows a frame on one of its own pages has to find that frame from code. ADR 012 made frames rows an analyst edits, and ADR 040 made one frame usable across many records, so the host needs a way to say “the frame for this” without a deploy creating it and without remembering an id.
The owner looked like the answer. It is a nullable polymorphic
reference that Janela never reads (ADR 014, ADR 019), so a host can
point it at the record the frame is for, and
Janela::Frame.find_or_create_by!(owner: queue) is a frame’s dom_id:
two columns, nothing added to the host’s schema.
It holds for exactly one frame per owner, and owners rarely have one. A
tenant owns every frame an analyst makes from the engine’s New button,
through janela_frame_owner, and a host wants frames of its own for
particular pages on top of those. find_by(owner: account) then returns
whichever row the database gives first. On a real installation it
returned the wrong frame: the first the tenant had ever made, not the
one the page was for (#59).
What was considered
name as the key was rejected. It is the analyst’s to rename, so a
host looking a frame up by name loses it the day someone edits the
title.
A column in the host’s table pointing at the frame was rejected as the answer the owner was meant to save a host from. It needs a migration on every table whose records want a frame, and a second one for each extra frame per record.
A slug, which ADR 013 turned down, is not what is being asked for, and the difference is the reason this is allowed. ADR 013 rejected a slug as a way to address a frame: in a URL a person reads, derived from the name, needing uniqueness rules, reserved words and regeneration on rename, and a second lookup path to keep working. What a host needs is none of those. It is an identifier the host writes in its own code, never in a URL, never derived from the name, never shown to an analyst, and never changed once set.
Janela choosing the frame for a record, a registry mapping host classes to frames, was rejected as configuration for something one line of the host’s code already says.
Decision
A frame may carry a key the host sets. A frame is found by its
owner and its key together, and each owner has at most one frame per
key.
Janela::Frame.for(account, :overview) # the tenant's overview
Janela::Frame.for(queue, :analytics) # this queue's frame
Janela::Frame.for(queue, :analytics) { |frame| frame.name = "#{queue.name} analytics" }
for finds the frame with that owner and key, or creates it. The block
runs only when it creates one, to set what the host wants a new frame to
start as; a new frame is named after its key unless the block says
otherwise. The owner may be nil for a host with one tenant.
The key is the host’s and nobody else’s. Janela reads it only in
for. It is not in any route and not in the engine’s forms, so an
analyst can rename, regrid and recompose a keyed frame without being
able to detach it from the page that finds it. A frame without a key,
which is every frame an analyst makes, is unchanged.
Uniqueness is per owner, in the database. A unique index on owner
and key, so two requests creating the same frame at once end with one
row, and for retries the find when the insert loses that race. Frames
with no key are not constrained by it.
key is a short lowercase string matching /\A[a-z0-9_]+\z/, the
shape of the symbols a host will pass, so :analytics and "analytics"
are the same key and nothing that looks like a URL or a name gets in.
Consequences
- A host gives any record any number of frames with no column of its
own: owner and key are the
dom_id, and the key is the suffixdom_id(queue, :analytics)would carry. janela_framesgainskeyand a unique index, so a migration and anUPGRADING.mdentry (ADR 015). Existing frames keep a nil key.- The owner now does two jobs for a host that uses
for: tenancy for the policy scope, and which record a frame belongs to. ADR 019 said Janela never reads the owner;forqueries by it, and only there. A host whose owner is a record rather than its tenant has to make its policy scope see frames owned by its own records, which the multi-tenancy guide shows. - A frame deleted from the engine’s pages is created again, empty, the next time its page is visited. That is the right answer for a frame a page depends on, and it is worth a line in the README so it is not a surprise.
- What would change this decision: a host needing to find a frame by something that is not stable in its own code. That is the addressing problem ADR 013 decided, and it would be decided there again.