From d6176827d2506d649c4a0de1d7274120db6ecfa3 Mon Sep 17 00:00:00 2001 From: Anton Sokolov Date: Fri, 13 Feb 2026 16:08:25 +0300 Subject: [PATCH 1/3] Refactor attribute access and protect internal state - Replace `instance_variable_get` with `attr_reader` for protected attributes in `Setting`, `RegistrySettings`, `Collection`, and `Hooks::Collection` classes to ensure safer and cleaner access. - Update `initialize_dup` methods to use the new protected attribute readers. - Add protected visibility for attribute readers to encapsulate internal state. --- lib/stroma/hooks/collection.rb | 6 +++++- lib/stroma/settings/collection.rb | 6 +++++- lib/stroma/settings/registry_settings.rb | 6 +++++- lib/stroma/settings/setting.rb | 6 +++++- lib/stroma/state.rb | 4 ++-- sig/lib/stroma/hooks/collection.rbs | 4 ++++ sig/lib/stroma/settings/collection.rbs | 4 ++++ sig/lib/stroma/settings/registry_settings.rbs | 4 ++++ sig/lib/stroma/settings/setting.rbs | 4 ++++ 9 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/stroma/hooks/collection.rb b/lib/stroma/hooks/collection.rb index 297df1e..1b1cdcc 100644 --- a/lib/stroma/hooks/collection.rb +++ b/lib/stroma/hooks/collection.rb @@ -59,7 +59,7 @@ def initialize(collection = Set.new) # @return [void] def initialize_dup(original) super - @collection = original.instance_variable_get(:@collection).dup + @collection = original.collection.dup end # Adds a new hook to the collection. @@ -96,6 +96,10 @@ def before(key) def after(key) @collection.select { |hook| hook.after? && hook.target_key == key } end + + protected + + attr_reader :collection end end end diff --git a/lib/stroma/settings/collection.rb b/lib/stroma/settings/collection.rb index 531d4d4..8ce2bba 100644 --- a/lib/stroma/settings/collection.rb +++ b/lib/stroma/settings/collection.rb @@ -61,7 +61,7 @@ def initialize(storage = {}) # @return [void] def initialize_dup(original) super - @storage = original.instance_variable_get(:@storage).transform_values(&:dup) + @storage = original.storage.transform_values(&:dup) end # Accesses or creates RegistrySettings for a registry key. @@ -83,6 +83,10 @@ def [](registry_key) def to_h @storage.transform_values(&:to_h) end + + protected + + attr_reader :storage end end end diff --git a/lib/stroma/settings/registry_settings.rb b/lib/stroma/settings/registry_settings.rb index 71127ba..8e5ba8f 100644 --- a/lib/stroma/settings/registry_settings.rb +++ b/lib/stroma/settings/registry_settings.rb @@ -59,7 +59,7 @@ def initialize(storage = {}) # @return [void] def initialize_dup(original) super - @storage = original.instance_variable_get(:@storage).transform_values(&:dup) + @storage = original.storage.transform_values(&:dup) end # Accesses or creates a Setting for an extension. @@ -81,6 +81,10 @@ def [](extension_name) def to_h @storage.transform_values(&:to_h) end + + protected + + attr_reader :storage end end end diff --git a/lib/stroma/settings/setting.rb b/lib/stroma/settings/setting.rb index 3875b9a..a8e3449 100644 --- a/lib/stroma/settings/setting.rb +++ b/lib/stroma/settings/setting.rb @@ -69,7 +69,7 @@ def initialize(data = {}) # @return [void] def initialize_dup(original) super - @data = deep_dup(original.instance_variable_get(:@data)) + @data = deep_dup(original.data) end # Converts to a plain Hash. @@ -93,6 +93,10 @@ def fetch(key, *args, &block) @data.fetch(key.to_sym, *args, &block) end + protected + + attr_reader :data + private # Recursively duplicates nested Hash and Array structures. diff --git a/lib/stroma/state.rb b/lib/stroma/state.rb index ad8d788..3b59301 100644 --- a/lib/stroma/state.rb +++ b/lib/stroma/state.rb @@ -60,8 +60,8 @@ def initialize # @return [void] def initialize_dup(original) super - @hooks = original.instance_variable_get(:@hooks).dup - @settings = original.instance_variable_get(:@settings).dup + @hooks = original.hooks.dup + @settings = original.settings.dup end end end diff --git a/sig/lib/stroma/hooks/collection.rbs b/sig/lib/stroma/hooks/collection.rbs index 53d8c17..1f276e0 100644 --- a/sig/lib/stroma/hooks/collection.rbs +++ b/sig/lib/stroma/hooks/collection.rbs @@ -22,6 +22,10 @@ module Stroma def empty?: () -> bool def size: () -> Integer + + protected + + attr_reader collection: Set[Hook] end end end diff --git a/sig/lib/stroma/settings/collection.rbs b/sig/lib/stroma/settings/collection.rbs index fa20d7e..7e219b2 100644 --- a/sig/lib/stroma/settings/collection.rbs +++ b/sig/lib/stroma/settings/collection.rbs @@ -22,6 +22,10 @@ module Stroma def empty?: () -> bool def to_h: () -> Hash[Symbol, Hash[Symbol, Hash[Symbol, untyped]]] + + protected + + attr_reader storage: Hash[Symbol, RegistrySettings] end end end diff --git a/sig/lib/stroma/settings/registry_settings.rbs b/sig/lib/stroma/settings/registry_settings.rbs index ecf2a2a..f0f2a55 100644 --- a/sig/lib/stroma/settings/registry_settings.rbs +++ b/sig/lib/stroma/settings/registry_settings.rbs @@ -22,6 +22,10 @@ module Stroma def empty?: () -> bool def to_h: () -> Hash[Symbol, Hash[Symbol, untyped]] + + protected + + attr_reader storage: Hash[Symbol, Setting] end end end diff --git a/sig/lib/stroma/settings/setting.rbs b/sig/lib/stroma/settings/setting.rbs index 506f2dc..9fd4d08 100644 --- a/sig/lib/stroma/settings/setting.rbs +++ b/sig/lib/stroma/settings/setting.rbs @@ -32,6 +32,10 @@ module Stroma | (Symbol key, untyped default) -> untyped | (Symbol key) { (Symbol) -> untyped } -> untyped + protected + + attr_reader data: Hash[Symbol, untyped] + private def deep_dup: (untyped obj) -> untyped From 4018c77918cd9fdfbac27d63260de03f8bd116eb Mon Sep 17 00:00:00 2001 From: Anton Sokolov Date: Fri, 13 Feb 2026 16:13:08 +0300 Subject: [PATCH 2/3] Clarify protected attribute visibility for Steep support - Add comments explaining the use of protected `attr_reader` declarations in `Setting`, `RegistrySettings`, `Collection`, and `Hooks::Collection` classes for Steep visibility. - Retain protected visibility to encapsulate internal state while enabling use in `initialize_dup`. --- sig/lib/stroma/hooks/collection.rbs | 3 +-- sig/lib/stroma/settings/collection.rbs | 3 +-- sig/lib/stroma/settings/registry_settings.rbs | 3 +-- sig/lib/stroma/settings/setting.rbs | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/sig/lib/stroma/hooks/collection.rbs b/sig/lib/stroma/hooks/collection.rbs index 1f276e0..733c753 100644 --- a/sig/lib/stroma/hooks/collection.rbs +++ b/sig/lib/stroma/hooks/collection.rbs @@ -23,8 +23,7 @@ module Stroma def size: () -> Integer - protected - + # Protected in Ruby; declared here for Steep visibility in initialize_dup attr_reader collection: Set[Hook] end end diff --git a/sig/lib/stroma/settings/collection.rbs b/sig/lib/stroma/settings/collection.rbs index 7e219b2..163f886 100644 --- a/sig/lib/stroma/settings/collection.rbs +++ b/sig/lib/stroma/settings/collection.rbs @@ -23,8 +23,7 @@ module Stroma def to_h: () -> Hash[Symbol, Hash[Symbol, Hash[Symbol, untyped]]] - protected - + # Protected in Ruby; declared here for Steep visibility in initialize_dup attr_reader storage: Hash[Symbol, RegistrySettings] end end diff --git a/sig/lib/stroma/settings/registry_settings.rbs b/sig/lib/stroma/settings/registry_settings.rbs index f0f2a55..a46dc1f 100644 --- a/sig/lib/stroma/settings/registry_settings.rbs +++ b/sig/lib/stroma/settings/registry_settings.rbs @@ -23,8 +23,7 @@ module Stroma def to_h: () -> Hash[Symbol, Hash[Symbol, untyped]] - protected - + # Protected in Ruby; declared here for Steep visibility in initialize_dup attr_reader storage: Hash[Symbol, Setting] end end diff --git a/sig/lib/stroma/settings/setting.rbs b/sig/lib/stroma/settings/setting.rbs index 9fd4d08..c4520dd 100644 --- a/sig/lib/stroma/settings/setting.rbs +++ b/sig/lib/stroma/settings/setting.rbs @@ -32,8 +32,7 @@ module Stroma | (Symbol key, untyped default) -> untyped | (Symbol key) { (Symbol) -> untyped } -> untyped - protected - + # Protected in Ruby; declared here for Steep visibility in initialize_dup attr_reader data: Hash[Symbol, untyped] private From f81594da18e0b1996e2868dac3d719f7c9e82899 Mon Sep 17 00:00:00 2001 From: Anton Sokolov Date: Fri, 13 Feb 2026 17:00:39 +0300 Subject: [PATCH 3/3] Enforce protected attribute encapsulation in specs - Add tests to ensure internal attributes (`collection`, `storage`, and `data`) in `Hooks::Collection`, `Settings::Collection`, `RegistrySettings`, and `Settings::Setting` remain inaccessible via public interface. - Verify `NoMethodError` is raised when attempting public access to these protected attributes. --- spec/stroma/hooks/collection_spec.rb | 6 ++++++ spec/stroma/settings/collection_spec.rb | 6 ++++++ spec/stroma/settings/registry_settings_spec.rb | 6 ++++++ spec/stroma/settings/setting_spec.rb | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/spec/stroma/hooks/collection_spec.rb b/spec/stroma/hooks/collection_spec.rb index 5356d51..6f005c8 100644 --- a/spec/stroma/hooks/collection_spec.rb +++ b/spec/stroma/hooks/collection_spec.rb @@ -110,4 +110,10 @@ expect(hooks.map(&:itself)).to all(be_a(Stroma::Hooks::Hook)) end end + + describe "protected interface" do + it "does not expose collection publicly" do + expect { hooks.collection }.to raise_error(NoMethodError) + end + end end diff --git a/spec/stroma/settings/collection_spec.rb b/spec/stroma/settings/collection_spec.rb index c6cf432..0f92cac 100644 --- a/spec/stroma/settings/collection_spec.rb +++ b/spec/stroma/settings/collection_spec.rb @@ -138,4 +138,10 @@ expect(settings[:actions][:transactional][:class]).to eq("ActiveRecord::Base") end end + + describe "protected interface" do + it "does not expose storage publicly" do + expect { settings.storage }.to raise_error(NoMethodError) + end + end end diff --git a/spec/stroma/settings/registry_settings_spec.rb b/spec/stroma/settings/registry_settings_spec.rb index 6c135bf..c612d02 100644 --- a/spec/stroma/settings/registry_settings_spec.rb +++ b/spec/stroma/settings/registry_settings_spec.rb @@ -109,4 +109,10 @@ expect(settings[:authorization].key?(:new_key)).to be(false) end end + + describe "protected interface" do + it "does not expose storage publicly" do + expect { settings.storage }.to raise_error(NoMethodError) + end + end end diff --git a/spec/stroma/settings/setting_spec.rb b/spec/stroma/settings/setting_spec.rb index 8e8b614..1579b17 100644 --- a/spec/stroma/settings/setting_spec.rb +++ b/spec/stroma/settings/setting_spec.rb @@ -146,4 +146,10 @@ expect(setting[:list][1][0]).to eq(2) end end + + describe "protected interface" do + it "does not expose data publicly" do + expect { setting.data }.to raise_error(NoMethodError) + end + end end