Skip to content

Make InheritableSetting's raw stores internal (series finale)#2810

Open
ericproulx wants to merge 13 commits into
masterfrom
inheritable-setting-finale
Open

Make InheritableSetting's raw stores internal (series finale)#2810
ericproulx wants to merge 13 commits into
masterfrom
inheritable-setting-finale

Conversation

@ericproulx

@ericproulx ericproulx commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

The finale of the InheritableSetting encapsulation series (targets the #2809 integration branch; #2795#2808 built the accessor catalog): the raw stores become internal where the ecosystem allows. namespace_inheritable is now protected and namespace_stackable_with_hash private — every inheritable key is reachable only through its dedicated accessor. namespace_stackable deliberately stays public but deprecated: grape-swagger reads it directly (see below), so external reads warn through Grape.deprecator with a pointer to the semantic accessors, while Grape itself reads the underlying ivar (peer access in inherit_from / copy_state_from goes through a protected raw reader) and never warns. The reader gets removed or narrowed in a future major once grape-swagger migrates; a spec pins the deprecation. The route / namespace / global scratch stores (backing route_setting / namespace_setting / global_setting) also stay public.

Commits

  1. path_settings — the last raw consumer, Endpoint#prepare_default_path_settings, merged full dumps of both stores per endpoint while Router::Pattern::Path reads exactly six values (full mount-path stack, root prefix, format, raw content-types stack, version, version options). InheritableSetting#path_settings snapshots precisely those into a Grape::Util::InheritableSetting::PathSettings value object (a plain Data.define, no keyword defaults — unlike RescueOptions/VersionOptions, it has no bare-default production caller, since its sole constructor always supplies all six members; a mistyped or omitted member fails loudly instead of returning nil), to_routes uses it, the old method is deleted, and Path reads typed members with nil-tolerant guards replacing the key?-presence checks (equivalent under always-present members — verified case by case). Two semantics deliberately preserved: mount_path is the full stack (unlike the scalar #mount_path accessor, which is .first for the versioner), via .presence so an unmounted API still contributes no path part; and content_types stays the raw registration stack because Array(...).size == 1 counts registrations, not distinct formats — a latent duplicate-registration quirk flagged for a separate fix rather than silently changed here.
  2. Visibility narrowingnamespace_inheritable moves to protected (peer access in inherit_from / copy_state_from keeps working), the deep-merge helper to private. No spec reaches around the visibility: the storage-engine specs exercise the inheritable store's semantics (inheritance, nearest-wins, point-in-time copies, to_hash) through public accessors (root_prefix, format, version_options) instead of raw keys, and an unused captured-settings hash in inside_route_spec's endpoint dummy is removed.
  3. Catalog regroup + class doc — the rescue family reunites (its two halves had been separated by merge insertion order), path_settings closes the catalog as the one cross-cutting projection, and the class doc now describes what the class has become: the settings registry whose accessor naming encodes semantics (add_* = stacking, = = nearest-wins scalar, !/? = flag).
  4. mount_paths + docs — a public reader for the full mount-path stack (used by path_settings, and the migration target for grape-swagger below), plus CHANGELOG/UPGRADING.

grape-swagger (found by code search — the reason namespace_stackable stays public)

grape-swagger's current master reaches into namespace_stackable in three files. Nothing breaks with this PR — the reads now emit a deprecation warning pointing at the accessors; the table below is the migration:

File Today Migration
swagger_documentation_adder.rb namespace_stackable[:namespace] / [:mount_path] inheritable_setting.namespaces.last / inheritable_setting.mount_paths.join('/')
token_owner_resolver.rb namespace_stackable[:helpers] behind respond_to? inheritable_setting.helpers
request_param_parsers/route.rb walks the StackableValues#inherited_values chain manually for per-scope params needs real rework against public accessors (or a purpose-built per-scope read on InheritableSetting) — not a mechanical rename

Books

  • Metrics/ClassLength 330 → 340 (path_settings + mount_paths land the class at 331 lines).
  • Full rubocop clean and full suite green (2363 examples, 0 failures) at every commit.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx requested a review from dblock July 19, 2026 02:07
ericproulx and others added 10 commits July 21, 2026 08:32
Router::Pattern::Path reads exactly six values; the merged dump of both
full stores in Endpoint#prepare_default_path_settings carried everything
else as dead weight. The nil-tolerant guards in Path replace the
key?-presence checks (absent and nil are equivalent under the always-
present-keys snapshot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r private

namespace_stackable and namespace_inheritable are no longer public API:
every semantic key is reachable only through its dedicated accessor.
inherit_from's peer access continues to work through protected. The
storage-engine specs reach in explicitly via __send__.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… doc

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er impact

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
grape-swagger reads the stackable store directly (documentation adder,
token owner resolver, and the route param parser that walks the
StackableValues chain), so only namespace_inheritable narrows to
protected; namespace_stackable stays public, documented as read-only
with the dedicated accessors preferred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
root_prefix and format stand in for the arbitrary keys the storage
specs used; the api_spec recompile regression keeps its intent through
the version_options reader; inside_route_spec's captured settings hash
was never consumed and is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External reads warn with a pointer to the semantic accessors so
grape-swagger gets a migration signal; Grape itself reads the ivar
(with a protected raw reader for inherit_from/copy_state_from peer
access) and never warns. Storage specs move to add_helper/helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the stackable side: internal access reads the ivar, the
protected reader exists purely for inherit_from/copy_state_from peer
access. Also repairs the deprecation message, which an earlier
conversion had mangled into Setting#@namespace_stackable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Data.define with nil defaults, following the RescueOptions /
VersionOptions precedent: Path reads typed members instead of hash
keys, so a mistyped member fails loudly instead of returning nil.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Producer-side placement following the RescueOptions / VersionOptions
precedent, and it fixes the dependency direction: Util no longer
constructs a Router-namespace class; Router::Pattern::Path depending
on Util is the normal direction. Building the snapshot must stay on
InheritableSetting regardless, since only it may touch the raw stacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the inheritable-setting-finale branch from b008c25 to 4aec25e Compare July 21, 2026 12:32
@ericproulx
ericproulx changed the base branch from inheritable-setting-integration to master July 21, 2026 12:32
ericproulx and others added 3 commits July 21, 2026 08:38
Unlike RescueOptions/VersionOptions, whose defaults back a genuine
runtime fallback (Middleware::Error's rescue_options ||=, Versioner's
DEFAULT_OPTIONS), PathSettings has no bare-default production caller —
InheritableSetting#path_settings always supplies all six members. Plain
Data.define is the more idiomatic fit; the spec-only convenience of
partial construction moves into path_spec's own path_settings helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Grape has no established pattern of value objects owning their own
file/directory when they're this small and single-purpose; RescueOptions
and VersionOptions both live directly in their owning file. Placed right
above its sole producer, #path_settings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It had been inserted right after #2796 by an early scripted edit,
landing ahead of #2797-#2808 instead of after them. Also drops a
duplicate #2807 entry left over from the integration branch's history
(one plain, one with the vestigial-hash-copy note added later).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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