Skip to content

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

Open
ericproulx wants to merge 10 commits into
inheritable-setting-integrationfrom
inheritable-setting-finale
Open

Make InheritableSetting's raw stores internal (series finale)#2810
ericproulx wants to merge 10 commits into
inheritable-setting-integrationfrom
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 (Data.define with nil defaults, following the RescueOptions / VersionOptions precedent — a mistyped 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

ericproulx and others added 4 commits July 18, 2026 22:00
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>
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

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>
@ericproulx
ericproulx requested a review from dblock July 19, 2026 02:07
ericproulx and others added 5 commits July 18, 2026 22:10
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>
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