Skip to content

Derive associations from the server schema - #33

Merged
hasumikin merged 3 commits into
masterfrom
derived-associations
Oct 12, 2026
Merged

hasumikin merged 3 commits into
masterfrom
derived-associations

Conversation

@hasumikin

@hasumikin hasumikin commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Background

Funicular::Schema.build reads the ActiveRecord reflections of the model and sends each belongs_to whose foreign key is an exposed attribute.
Model.load_schema defines the reader from it (comment.post) and, for a conventional foreign key, the inverse has_many on the target (post.comments), so the client classes need no declarations.

Rules:

  • Both ends must be replica models the client carries, matched by class name. A storage :local or :ephemeral model never takes part, and an app without the local database derives nothing.
  • A derived association yields to a hand-written declaration, to an attribute, and to a method of the same name, public or private.
  • An entry of the model's own schema outranks an inverse derived from another model, whichever schema arrives first.
  • A polymorphic belongs_to and a composite foreign key do not derive.
  • The development console says why an association did not derive.

associations: false turns the derivation off, a nil value hides one, and a hand-written { kind:, class_name:, foreign_key: } entry adds one; Schema.build rejects a malformed entry.

hasumikin and others added 3 commits October 5, 2026 16:15
Funicular::Schema.build reads the ActiveRecord reflections of the model
and sends each belongs_to whose foreign key is an exposed attribute.
Model.load_schema defines the reader from it (comment.post) and, for a
conventional foreign key, the inverse has_many on the target
(post.comments), so the client classes need no declarations.

The rules:

- Both ends must be replica models the client carries, matched by class
  name. A storage :local or :ephemeral model never takes part, and an
  app without the local database derives nothing.
- A derived association yields to a hand-written declaration, to an
  attribute, and to a method of the same name, public or private.
- An entry of the model's own schema outranks an inverse derived from
  another model, whichever schema arrives first.
- A polymorphic belongs_to and a composite foreign key do not derive.
- The development console says why an association did not derive.

`associations: false` turns the derivation off, a nil value hides one,
and a hand-written { kind:, class_name:, foreign_key: } entry adds one;
Schema.build rejects a malformed entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1XudATNo5yA1EwJ8De9KR
Derived readers now live in a module that each model includes, so a
hand-written declaration, an attribute accessor, or a def on the class
wins by method lookup in any order. A separate registry records who
derived what, which replaces the :derived and :origin special cases.

- A schema that loads again retracts what its previous load derived,
  inverses on other models included, so a changed or removed
  association no longer leaves a stale reader.
- Every association that does not derive, or that another replaces,
  says why on the development console.
- The name check uses method_defined? instead of building method lists.

On the Rails side:

- A belongs_to to a column other than id, or to a class that does not
  load, does not derive; the client reader looks the target up by id.
- class_name is the class ActiveRecord resolves (Blog::Post), not the
  raw name.
- A hand-written belongs_to must name an exposed foreign key, and
  associations: takes only false, nil, or a Hash.
- Endpoints and Associations share one override merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VaokYGa9iWeyJSe6w3EeS
@hasumikin
hasumikin merged commit 18ed25e into master Oct 12, 2026
1 check passed
@hasumikin
hasumikin deleted the derived-associations branch October 12, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant