Skip to content

Items

An item is a row in items with one item_references row per place its bytes live. The bytes themselves stay in the resource they came from.

app/models/item.rb · app/models/reference.rb

kind what it is — pdf, email, image… Required
title display name, usually the basename of the locator key
parent_id set on an item extracted from another, such as an email attachment

Item#analyzed_at is the maximum analyzed_at across its references, not a column.

resource_id which resource holds this copy
locator_key the key within it — unique per (tenant_id, resource_id)
locator the type-specific address, as JSON
version what the resource last reported, usually an ETag
changed_at set when version changes, which also clears analyzed_at
analysis the step machine’s record — see Analysis

Reference.discover! is the sync path: it finds or creates the reference, creates an item if there is not one yet, and calls note_version!. A changed version re-opens analysis.

Reference.record! is the export path: it attaches a reference to an item you already have, and records source_version so stale_against? can tell whether the copy has fallen behind.

Assigned from the file extension by Kind.for_filename (app/models/kind.rb), defaulting to file.

kind extensions
pdf pdf
image png, jpg, jpeg, gif, webp, heic, tif, tiff
text txt, md, rtf
data csv, tsv, json, xml
xlsx xlsx, xls, ods
doc doc, docx, odt
calendar ics
contact vcf, vcard
pkpass pkpass
email eml
file anything else

Kind is not resource type. pdf is a kind; s3 is a type.

An analyzer that declares has_children? returns child bodies from children_of, and Analyzer::Base writes them into a Resource::Database resource keyed children before analyzing the parent. Item::DEPTH caps the chain at 4.

Item#body_text is the union of the strings extracted from an item’s own references and its children’s, so an email matches on a word that only appears in an attached PDF.

Item#children_ready? is true when every child has an analyzed_at. A parent that found children returns without analyzing; the last child to finish re-enqueues it (AnalyzeItemJob#wake_parent).

Item#merge! moves every reference off another item onto this one. Blocking groups candidates, ProposeMergesJob writes MergeProposal rows, and the GraphQL mutations mergeItems, settleMergeProposal and splitReference act on them.

Deleting an item and deleting the bytes it points at are separate operations.

Does Reversible by
destroy the item drops the row, its references and its analysis re-syncing the resource
delete the object removes the bytes from the resource nothing

Item dependent: :destroy cascades to references and children. Nothing in the catalog deletes a resource’s objects.

One table, cursor pagination through Page.of, OpenSearch as the query path. count(*) is avoided on catalog-sized scopes.