Supporting reference · Working design and evidence, not a promise of complete support.

On this page

Rails Field lowering

Status: Implemented for ten scalar kinds and required enums. Other Field kinds and modifiers are evidence-backed directions.

Each emitted Field becomes one PostgreSQL column plus the model declarations that Rails needs to read, write, validate, and display it. The Compiler emits ten ordinary scalar kinds and qualifying required enums; other Field kinds are omitted and listed in the reviewed GapSet. Modifiers lower one by one: immutability becomes attr_readonly, log redaction filters the attribute, normalization becomes Rails normalizes, and case-insensitive comparison becomes citext. A modifier the target cannot realize is listed on its own. Encryption, attachments, JSON values, and derived numbers remain directions. The Field catalog states what each kind and modifier means, and the support inventory (repository-only) records which kinds generate.

On this page

Realized defaults and modifiers

Defaults are realized only for exact required-datetime current_time on emitted Fields and in-domain literal keys on admitted required enums. On emitted ordinary scalar and admitted required enum Fields, log redaction adds the attribute to self.filter_attributes and registers only the model-qualified request parameter. The same key under another model remains visible; arbitrary application, provider, and external logs are outside this boundary. Authored normalizations are realized only on emitted scalar Fields. Immutability is realized on emitted scalar Fields and admitted required enum Fields; the encompassing gap for an unsupported Field shape covers that Field and its authored modifiers.

Required datetime defaults

Support: see the support inventory row (repository-only).

The current Compiler lowers the exact { "kind": "environment", "name": "current_time" } default on an emitted required datetime Field to a model-visible attribute :published_at, default: -> { Time.current } declaration. Omitting a type preserves the schema-derived PostgreSQL timestamp(6) precision. Active Record materializes the callable on a new model before ordinary presence validation; an explicit nil remains invalid and an explicitly supplied timestamp remains unchanged. The Domain migration and canonical schema retain NOT NULL with no database default, so direct and bulk inserts must supply the value. This does not realize current_date, non-enum literal defaults, or defaults on other Field kinds. The separate enum rule below owns its one literal-key exception. The callable follows Active Record's new-record attribute lifecycle rather than database-commit time.

Required enum Fields

Support: see the support inventory row (repository-only).

The current Compiler admits a required enum with its canonical nonempty ordered domain. The reviewed Plans contribute Oscar Party's ordinal bookmark.priority and non-ordinal credit.role; Case Chat's case.status, participant.role, participant.notification_level, and notification.kind; and Photogram's feed_occurrence.reason. Each emits one PostgreSQL string column with null: false and an explicit stable-key Rails enum mapping with validate: true and collision-aware native helper names, plus ordinary presence validation for the required Field and generated schema-quality check. The target admits that enum only when Rails' pluralized class mapping method is safe in both the frozen profile and selected dependencies, unique among sibling enums, and distinct from authored Predicate and Ordering scope names. A collision omits the Field and records the exact foundation_plan.gap.field_kind.not_generated reason. It emits no native PostgreSQL enum or database CHECK. Direct SQL and validation-bypassing writes such as update_all therefore remain outside membership proof. Any admitted required enum accepts a valid in-domain literal-key default. Oscar's bookmark.priority emits default: "normal"; Case Chat's case.status and participant.notification_level emit default: "open" and default: "all_activity" in the model declaration, migration, and schema. Active Record materializes the model default, while an explicit compatible key wins. A nonliteral default or a literal outside the closed domain remains a precise foundation_plan.gap.field_modifier.default record. An enum Field the Domain does not emit keeps its encompassing Field gap. Optional enums remain ungenerated and yield exact foundation_plan.gap.field_kind.not_generated gaps. Required enums rejected by the mapping-name rule above use that same encompassing Field gap. Outside the descending ordinal pattern in Named Orderings, other enum Primary Descriptors, Orderings, Field-owned enum Validations, and ordinal rank comparisons likewise remain ungenerated and are disclosed through the reviewed GapSet. Admitted enum Field projections and form labels use the same enums.<model>.<field>.<key> Rails I18n entries seeded from authored names. Form values remain stable keys in authored order; display labels do not imply ordinal rank semantics. Native helper naming follows the model helper allocation rules. Admitted storage feeds the existing generic local ExpressionSql rule, but an ordinal comparison lowers only when it does not depend on authored rank. An independently admitted predicated direct Association may consume a non-ordinal scope under the relationship rule's existing gates; the reviewed Plans add no Association consumer.

An admitted required enum also emits an ordinary string column with null: false. Its model uses an explicit stable-key Rails enum mapping with validate: true and collision-aware native helper names, plus ordinary presence validation. The explicit presence validator lets the generated schema-quality check recognize the required Field; active_record_doctor 2.0.1 misses Rails 8.1's string-keyed enum inclusion validator. Admission requires the plural mapping name to be target-safe, unique among sibling enums, and distinct from authored Predicate and Ordering scope names. It emits no native PostgreSQL enum or database CHECK; raw SQL and validation-bypassing writes remain outside the membership proof. Every admitted required enum accepts an in-domain literal-key default. Oscar's bookmark.priority emits default: "normal"; Case Chat's case.status and participant.notification_level emit default: "open" and default: "all_activity" in the model declaration, migration, and schema. Active Record materializes the model default on a new record, while an explicit compatible key wins. Nonliteral and out-of-domain defaults remain exact modifier gaps. An enum Field the Domain does not emit keeps its encompassing Field gap. This includes a required enum whose plural mapping name conflicts with the target class namespace, a sibling enum, or an authored Predicate or Ordering scope. Outside the descending ordinal Ordering pattern, optional enums, Field-owned enum Validations, descriptors, other Orderings, and ordinal rank comparisons remain unsupported or omitted. A required non-ordinal enum may participate only as the Field member in Credit's exact admitted Entity-owned uniqueness tuple. Admitted enum Field projections and form option labels share Rails I18n entries seeded from authored value names. Forms submit stable keys in authored order. Editable display labels do not change stored keys or imply ordinal rank semantics. The ordinary attribute may feed the existing generic local Predicate rule and, after independent admission, the existing predicated direct Association rule. The reviewed Plans add two non-ordinal Predicate scopes and no Association consumer. Optional enums yield exact foundation_plan.gap.field_kind.not_generated gaps. On an admitted enum, the retained plural mapping method and collision-aware native value helpers are target realization details and add no GapSet record.

The model provenance includes the Field and every contributing FieldValue; migration and schema provenance need only the Field because their bytes do not encode membership.

Immutable Fields

An emitted scalar or admitted enum Field with immutable: true is included in the model's attr_readonly declaration. Creation may assign the value, use a model-visible current_time default, or use an admitted required-enum literal default carried by both model and column. After persistence, ordinary changed, same-value, and nil assignments raise ActiveRecord::ReadonlyAttributeError under the pinned Rails 8.1 defaults. This is model-level write-once behavior, not a database trigger or a fence against direct SQL. Current generated-app proof covers case.reference_number, case.opened_at, and post.published_at, plus immutable notification.kind and feed_occurrence.reason; a modifier on an ungenerated Field kind remains a target-support gap.

An emitted scalar or admitted enum Field with immutable: true joins that model's attr_readonly declaration. Its subject UUID joins the generated model's provenance. Creation still accepts an explicit value, the model-visible current_time default, or an admitted required-enum literal default carried by model and column. After persistence, ordinary changed, same-value, and nil assignments raise ActiveRecord::ReadonlyAttributeError. Like immutable Reference enforcement, this is a model-level write-once boundary rather than a database trigger or a fence against direct SQL. An immutable modifier on a Field shape the Domain does not emit remains in the reviewed GapSet.

Sensitive Fields

Use Active Record Encryption as the first direction for encrypted_at_rest. On emitted ordinary scalar and admitted required enum Fields, redact_from_logs appends the attribute to self.filter_attributes and registers the model-qualified request parameter. Case Chat filters notification.deduplication_key; the same key under another model remains visible. This boundary does not encrypt the value or cover arbitrary application, provider, or external logs. The modifier remains a reviewed gap on every other or unemitted Field. A Predicate, uniqueness rule, or future lookup that reads an encrypted Field still requires a supported query realization rather than silently enabling deterministic encryption.

Text normalization

Lower an explicit Field pipeline with Rails normalizes, grouping Fields with identical pipelines under one readable inline lambda. The Field catalog owns character semantics and the cleanup-before-blank admission rule. Use the public StripAttributes.strip(value) formatter for adjacent trim, blank_to_null; trim alone passes allow_empty: true. The generated dependency contributor pins strip_attributes to 2.0.1 only when a retained normalization pipeline includes trim. Do not install its model callback. Native Rails value.squish realizes collapse_whitespace, and Rails value.presence realizes blank_to_null. Append presence only when authored. Ruby String#downcase supplies the full Unicode lowercase mapping. Preserve operation order and nil, including after an intermediate blank-to-null result. No type-wide default or application formatter wrapper is added. Loading StripAttributes adds its strip_attributes method to every model. Reserve that name only when retained trim actually selects the dependency. If colliding Fields are the only trim selectors, retain their storage and decline each complete normalization pipeline with the existing modifier gap. Otherwise, independently retained trim makes the collision real: omit the affected member and dependent consumers through ordinary gaps. Native enum prefixes and AASM namespaces resolve generated convenience-method collisions where available. The collision qualification (repository-only) records the library behavior, selection-cycle rationale, and runtime boundary. An authored Entity whose model constant is StripAttributes makes the formatter unavailable application-wide. Preserve that Entity and its storage, omit every complete trim-containing pipeline with a normalization modifier gap, and select no formatter gem. Rails-only pipelines remain eligible. This handles the actual optional-module collision without reserving the Entity name in applications that do not need the formatter. Trim removes Unicode whitespace and six additional invisible edge characters while preserving NUL. Collapse preserves those invisibles but removes edge NUL; both retain interior joiners. These policies intentionally differ. Ordinary scalar hash equality receives normalization and handles an operand that normalizes to nil. Typed Arel casts non-null normalized values, but its null operator and hash-array membership are selected before normalization. Generated Arel, array, untyped, and raw-SQL consumers must call Model.normalize_value_for first and branch on nil.

Text comparison

The current Compiler lowers an emitted short_text Field with case_insensitive comparison to PostgreSQL citext. One conditional migration installs the extension before every Domain table, and db/schema.rb declares both the extension and t.citext. Entered case remains stored unless an authored normalization changes it, while ordinary Active Record hashes, typed Arel equality, ordering, and B-tree indexes compare case-insensitively. The generated-app qualification verifies that the extension's schema is on the current search_path. Its pattern operators and several string functions are also case-insensitive; cast to text for a deliberate exact operation. Pin or verify a supported database LC_CTYPE, or disclose it as an external prerequisite, because that creation-time setting controls case folding. The bounded uniqueness slice inherits each admitted tuple member's storage equality, including compound mixtures of citext, exact short_text, date, and foreign-key members. Entity and Field comparison Validations with Field operands likewise require compatible comparison semantics rather than relying on PostgreSQL's mixed citext/text resolution. Exclusion must inherit the Field's comparison behavior instead of Ruby String membership accidentally becoming case-sensitive. Field-to-Field Expressions require the same compatible comparison semantics before lowering. Nondeterministic ICU collations remain a future profile strategy if representative multilingual identifiers require more complete Unicode case behavior.

JSON values

Support: see the support inventory row (repository-only).

Lower the semantic json Field to PostgreSQL jsonb, which PostgreSQL recommends for most applications and Active Record maps to ordinary Ruby values. Do not add a json versus jsonb Plan choice. Keep stable application facts as Fields, References, or Entities. If exact source bytes matter, store the raw input separately from the parsed JSON value.

Attachments

Support: see the support inventory row (repository-only).

Active Storage is the first-profile lowering for explicit uploaded Fields and Action Text embeds. Cloudinary is the intended production storage and image-delivery service; local and test may use an inert local service. Generated image delivery uses Cloudinary transformations with f_auto/q_auto, not the unsupported Active Storage variant path. These are profile consequences rather than per-Field choices. Shrine remains deferred until a demonstrated requirement such as resumable uploads justifies a second profile. See the attachment direction (repository-only).

Derived numeric values

The Plan can declare sum, average, minimum, and maximum over a named Association and source Field. A derived Field is not directly writable and must reflect changes to both source values and Association membership. Every derived Field needs a structured consumer. A detail-only average can remain a fresh Association AVG. An average used by a Scaffold collection, Predicate, Ordering, or Predicate-filtered Association needs a maintained queryable value. One isolated Compiler projection records a Counter Culture-maintained sum and count plus their quotient as a stored PostgreSQL-generated average. It explicitly records production_wiring_complete: false; neither part is selected profile output or generated-app behavior. Decision 0024 requires the target to compare a conventional Rails or common-gem result-column path and either keep that behavior there or record a narrow database-owned exception before generation. Counter Culture is not yet a profile pin. The semantic Field remains one Plan subject and adds no realization choice. A compatible index can follow from actual Predicate and Ordering use. Nullable sources, scoped membership, bulk writes, repair, and minimum or maximum still need proof before the Compiler claims support.

References marked “repository-only” name implementation or internal material outside this public guide. They are intentionally not links. Publishing a design does not prove its implementation.