Module: Axn::Configurable
- Includes:
- PerClassOverrides
- Defined in:
- lib/axn/configurable.rb
Overview
A small DSL for declaring configuration on a module (e.g. a satellite gem namespace like Axn::MCP), so each one doesn't hand-roll its own config object, yielder, validation, and test reset.
module Axn::MCP
extend Axn::Configurable
setting :mcp_text_content, default: :structured, one_of: %i[structured message]
end
Axn::MCP.config.mcp_text_content # => :structured
Axn::MCP.configure { |c| c.mcp_text_content = :message }
Axn::MCP.reset_config! # primarily for test isolation
Defined Under Namespace
Modules: ClassConfigWriter, PerClassOverrides, Settings Classes: Config, NamespaceWriter, Setting
Constant Summary collapse
- UNSET =
Sentinel distinguishing "no argument given" from an explicit nil in the generated class-level override accessors.
Object.new.freeze
- RESERVED_SETTING_NAMES =
Names the DSL installs itself, so a setting cannot be declared with one. Either direction of the collision silently breaks something: a generated
reset!reader would replace the per-setting reset helper (leavingreset!(:other)an arity error), and in the module-singleton flavorConfig#reset!wins method lookup so the declared setting becomes unreadable. Raise when the class is defined instead. %i[reset!].freeze
Class Method Summary collapse
-
.canonical_setting_name!(name) ⇒ Object
Canonicalizes a setting name to a Symbol and rejects the reserved ones, RETURNING the canonical form so every downstream use — the registry key, the ivar, the generated methods — derives from one
to_sym. -
.config_source_for(klass, namespace) ⇒ Object
The config source that owns
namespaceonklassor any ancestor, or nil. -
.declared_settings_for(klass) ⇒ Object
Every Setting declared anywhere in
klass's own ancestry (viaSettings#setting), merged into one name => Setting map.
Instance Method Summary collapse
- #config ⇒ Object
- #configure {|config| ... } ⇒ Object
- #reset_config! ⇒ Object
- #setting(name, default: nil, one_of: nil, validate: nil, overridable: false) ⇒ Object
Methods included from PerClassOverrides
#_validate_override_setter!, #config_namespace, #overrides, #resolve_override_for
Class Method Details
.canonical_setting_name!(name) ⇒ Object
Canonicalizes a setting name to a Symbol and rejects the reserved ones, RETURNING the canonical
form so every downstream use — the registry key, the ivar, the generated methods — derives from
one to_sym. Calling to_sym again after the check would let a name whose to_sym answers
differently each time pass the guard as one name and install itself as another, which is not
only a way past the reserved list but a way to register a setting under a name whose reader and
ivar disagree with it.
40 41 42 43 44 45 46 47 |
# File 'lib/axn/configurable.rb', line 40 def self.canonical_setting_name!(name) canonical = name.to_sym return canonical unless RESERVED_SETTING_NAMES.include?(canonical) raise ArgumentError, "setting #{canonical.inspect} is reserved: Axn::Configurable defines #{canonical} on every " \ "config object. Pick another name." end |
.config_source_for(klass, namespace) ⇒ Object
The config source that owns namespace on klass or any ancestor, or nil. Walks the same
superclass chain the override store uses, so the duplicate-owner guard and the configure
writer agree on which source (if any) governs a namespace for a given class.
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/axn/configurable.rb', line 52 def self.config_source_for(klass, namespace) while klass.is_a?(Module) if klass.instance_variable_defined?(:@_axn_config_sources) registry = klass.instance_variable_get(:@_axn_config_sources) return registry[namespace] if registry.key?(namespace) end break unless klass.is_a?(Class) && klass.superclass klass = klass.superclass end nil end |
.declared_settings_for(klass) ⇒ Object
Every Setting declared anywhere in klass's own ancestry (via Settings#setting), merged
into one name => Setting map. Each class in the chain keeps its declarations in its own
@_declared_settings ivar rather than one shared registry, so a subclass that declares
additional settings — without re-extending Settings — still needs its instances to see
both its own and every ancestor's. Reads the ivar directly (not through _declared_settings)
so walking the chain never mints an empty registry on a class that never declared anything.
A name declared on more than one class in the chain resolves to the most specific (deepest)
declaration, since that class's Setting is merged in last.
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/axn/configurable.rb', line 73 def self.declared_settings_for(klass) chain = [] current = klass while current.is_a?(Class) chain << current current = current.superclass end chain.reverse_each.with_object({}) do |k, settings| settings.merge!(k.instance_variable_get(:@_declared_settings)) if k.instance_variable_defined?(:@_declared_settings) end end |
Instance Method Details
#config ⇒ Object
466 467 468 |
# File 'lib/axn/configurable.rb', line 466 def config @_axn_config ||= Config.new(_axn_config_settings) end |
#configure {|config| ... } ⇒ Object
470 471 472 473 |
# File 'lib/axn/configurable.rb', line 470 def configure yield(config) if block_given? config end |
#reset_config! ⇒ Object
475 476 477 |
# File 'lib/axn/configurable.rb', line 475 def reset_config! @_axn_config = nil end |
#setting(name, default: nil, one_of: nil, validate: nil, overridable: false) ⇒ Object
458 459 460 461 462 463 464 |
# File 'lib/axn/configurable.rb', line 458 def setting(name, default: nil, one_of: nil, validate: nil, overridable: false) name = Axn::Configurable.canonical_setting_name!(name) setting = Setting.new(name:, default:, one_of:, validate:, overridable:) _axn_config_settings[name] = setting _define_override_methods(setting, -> { config.public_send(setting.name) }) if overridable nil end |