Skip to content

Object-REST Mapper, revisited against the REST constraints - #32

Merged
hasumikin merged 6 commits into
masterfrom
rest-revisited
Oct 5, 2026
Merged

hasumikin merged 6 commits into
masterfrom
rest-revisited

Conversation

@hasumikin

Copy link
Copy Markdown
Member

Uniform interface: Funicular::Schema derives a model's endpoint table
from the Rails routes to its controller (resources :posts gives
posts#index as "all", posts#show as "find"; other actions keep their
names). Escape hatches: controller:, endpoints: (a "controller#action"
alias, a hand-written entry, or nil to hide one), routes: false, and a
nil model_class. On the client, path placeholders fill from the call
(Comment.all(post_id: 3) -> /posts/3/comments) or the record's
attributes; one unfilled segment takes the id whatever the route calls
it. A missing endpoint or segment raises instead of a silent no-op.

Self-descriptive messages: a 422 with { errors: record.errors } lands
on the record's errors and is yielded as a Funicular::Model::Errors,
like a client-side validation failure.

Cache: a replica model remembers each GET's ETag in the replica
database and sends If-None-Match; a 304 is answered from the replica
rows the last response listed, with no write and no change event.
Replica rows now merge per attribute so summary and detail
representations build one row. Model.absorb seeds the replica from
SSR state.

Version 0.6.0.

hasumikin and others added 6 commits October 5, 2026 15:37
Object-REST Mapper, revisited against the REST constraints.

Uniform interface: Funicular::Schema derives a model's endpoint table
from the Rails routes to its controller (`resources :posts` gives
posts#index as "all", posts#show as "find"; other actions keep their
names). Escape hatches: controller:, endpoints: (a "controller#action"
alias, a hand-written entry, or nil to hide one), routes: false, and a
nil model_class. On the client, path placeholders fill from the call
(Comment.all(post_id: 3) -> /posts/3/comments) or the record's
attributes; one unfilled segment takes the id whatever the route calls
it. A missing endpoint or segment raises instead of a silent no-op.

Self-descriptive messages: a 422 with { errors: record.errors } lands
on the record's errors and is yielded as a Funicular::Model::Errors,
like a client-side validation failure.

Cache: a replica model remembers each GET's ETag in the replica
database and sends If-None-Match; a 304 is answered from the replica
rows the last response listed, with no write and no change event.
Replica rows now merge per attribute so summary and detail
representations build one row. Model.absorb seeds the replica from
SSR state.

Version 0.6.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018W7QSXsvaDqYZvjG3pptyf
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018W7QSXsvaDqYZvjG3pptyf
The record id filled any single empty placeholder, so a comment without
post_id sent its own id as the parent (/posts/7/comments/7). Only the
last placeholder takes the id now; an empty parent raises ArgumentError.

update assigned the pending attributes before it built the path, so
update(slug: "new") requested /pages/new. The setter keeps the value
from before the first pending change, and __path_source uses it.

Move the 0.6.0 entries out of the dated 0.5.2 section of the CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VaokYGa9iWeyJSe6w3EeS
- Schema.build accepts an explicit endpoints: {} for a model with no
  REST side; it raises only when endpoints: is omitted and no route
  matches.
- Route derivation drops optional groups such as (/:locale) and skips
  glob routes, so no literal "(" or "*" reaches a client path.
- find sends the params the path does not take as the query string,
  like all. destroy raises ArgumentError on such a keyword, because a
  DELETE carries no query.
- The ETag cache key includes the table, so two models that GET one
  path no longer share an entry.
- A 304 reads its rows with one IN query instead of one per id.
- create no longer writes server errors onto an unreachable candidate.
- Document that the server must send a cleared attribute as null, and
  rewrite passive sentences in the conditional GET docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VaokYGa9iWeyJSe6w3EeS
@hasumikin
hasumikin merged commit 12d663a into master Oct 5, 2026
1 check passed
@hasumikin
hasumikin deleted the rest-revisited branch October 5, 2026 07:12
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