Skip to content

Define InheritableSetting's scalar settings and scope flags by key - #3031

Merged
ericproulx merged 1 commit into
masterfrom
refactor/inheritable-setting-accessors
Oct 19, 2026
Merged

ericproulx merged 1 commit into
masterfrom
refactor/inheritable-setting-accessors

Conversation

@ericproulx

@ericproulx ericproulx commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Grape::Util::InheritableSetting hand-wrote 28 methods that differ only in their key:

  • ten nearest-wins settings (format, default_format, default_error_formatter, default_error_status, version, version_options, root_prefix, cascade, build_params_with, auth), each a reader calling inheritable(key) and a writer calling set_inheritable(key, value);
  • four scope flags (do_not_route_head, do_not_route_options, do_not_document, lint), each a key! setting true and a key? comparing with true.

Change

Two private class macros declare them:

def self.inheritable_property(*keys)
  keys.each do |key|
    define_method(key) { inheritable(key) }
    define_method(:"#{key}=") { |value| set_inheritable(key, value) }
  end
end

def self.inheritable_flag(*keys)
  keys.each do |key|
    define_method(:"#{key}!") { set_inheritable(key, true) }
    define_method(:"#{key}?") { inheritable(key) == true }
  end
end
private_class_method :inheritable_property, :inheritable_flag

and the declarations sit together, the properties first, then the flags:

# Versioning state recorded by the routing DSL (see DSL::Routing): ...
inheritable_property :version, :version_options, :root_prefix

# ...
inheritable_property :auth

# Scope flags flipped by the routing DSL's bang methods ...
inheritable_flag :do_not_route_head, :do_not_route_options, :do_not_document, :lint
  • Each group keeps the comment it had above its methods. The contract every group shares (nearest-wins, a plain = writer rather than add_*, nil or false when never set) is documented once on its macro.
  • The macros are private (private_class_method, as in Util::MediaType), so the class's public surface is unchanged.
  • cascade_defined?, the rescue handler readers (each set together with other keys by its add_* writer) and the stackable registrations keep their own methods.
  • The file is 87 lines shorter, and InheritableSetting is 359 lines by Metrics/ClassLength. The limit stays at 425; Keep a logger set on one API from replacing every other API's #3024 raises it, so it can come down once both are in.

Equivalence

A script dumped on master and on this branch: public, protected and private instance methods; the class's own public methods; each accessor's arity; writer and bang return values; inheritance through a parent; an explicit nil shadowing an inherited value; and ArgumentError on a reader given an argument. The dumps are identical.

Tests

No spec changes. bundle exec rake passes. Mutation-checked through the generated methods: a reader that ignores the parent chain fails 87 examples, a writer that stores nothing 656, a predicate stuck at false 12.

Performance

define_method methods cost a little more per call than def in the interpreter. Ruby 4.0.7, master and this branch run alternately:

master branch
reader on a three-deep chain, interpreter 6.17M/s 6.00M/s (−2.6%, ~4 ns)
reader, YJIT 19.42M/s 19.20M/s (−1.1%)
request answered by error! (reads default_error_status), interpreter, 4 rounds 125.2k 124.6k (−0.5%)
same, YJIT, 2 rounds 249.0k 247.9k (−0.5%)
200-route API build, interpreter 40.1/s 40.5/s (+0.9%)
200-route API build, YJIT 46.3/s 45.3/s (−2.2%)

The request and build rows are within their ±2–5% run-to-run error. The error! interpreter rounds ranged from −3.3% to +2.8%.

With #3024

#3024 adds a hand-written logger/logger= pair. Whichever lands second should replace it with inheritable_property :logger under its comment.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the refactor/inheritable-setting-accessors branch from c45770c to 1208fe5 Compare October 10, 2026 23:22
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx force-pushed the refactor/inheritable-setting-accessors branch from 1208fe5 to d083798 Compare October 10, 2026 23:26

@dblock dblock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Ruby way of doing this is a DSL, declare something like inheritable_property, then use it like inheritable_property :auth.

@ericproulx
ericproulx force-pushed the refactor/inheritable-setting-accessors branch 2 times, most recently from a531033 to 203b73a Compare October 16, 2026 18:46
@ericproulx

Copy link
Copy Markdown
Contributor Author

Thanks @dblock, done: inheritable_property and inheritable_flag are now private class macros, and the settings are declared with them, e.g. inheritable_property :auth. Each group sits under the comment it had, and the shared contract is documented once on each macro.

@ericproulx
ericproulx requested a review from dblock October 16, 2026 18:46
@ericproulx
ericproulx force-pushed the refactor/inheritable-setting-accessors branch 4 times, most recently from 18969d7 to feb7195 Compare October 17, 2026 19:28
Ten nearest-wins settings were each a hand-written reader calling
inheritable(key) and a writer calling set_inheritable(key, value), and
four scope flags each a bang method and a predicate built the same way:
28 methods, all the same two bodies with a different key.

Two private class macros now declare them: inheritable_property defines
the reader and writer of each key it is given, inheritable_flag the bang
method and predicate. Each group of keys is declared under the comment it
had before, and the shared contract is documented once on the macro.
cascade_defined?, the rescue handler readers and the stackable
registrations keep their own methods, since each differs from these.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the refactor/inheritable-setting-accessors branch from feb7195 to 5556220 Compare October 17, 2026 20:42
@ericproulx
ericproulx merged commit 42ef31c into master Oct 19, 2026
69 checks passed
@ericproulx
ericproulx deleted the refactor/inheritable-setting-accessors branch October 19, 2026 11:40
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.

2 participants