Module: Axn::Configurable::Settings
Overview
Class-level flavor: declare validated instance settings on a class,
reusing the same Setting kernel (defaults, one_of:/validate:).
Used to dogfood Axn's own Configuration without contorting the
module-singleton DSL above. overridable: true mints the same per-class
override accessors (via PerClassOverrides), resolving their library-level
fallback from a live singleton the extending class registers.
class Configuration
extend Axn::Configurable::Settings
overridable_config_source { Axn.config }
setting :log_level, default: :info
setting :sidekiq_job_tag_sources, default: [...], overridable: true
end
Class Method Summary collapse
-
.extended(base) ⇒ Object
reset!is an INSTANCE method on the extending class (a config object), so it is installed here rather than declared in this module's body.
Instance Method Summary collapse
-
#_declared_settings ⇒ Object
Declared Setting objects by name, on THIS class only — not merged with an ancestor's.
-
#overridable_config_source(&block) ⇒ Object
Registers the live singleton whose values are the library-level fallback for per-class overrides (e.g.
Axn.config). - #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
.extended(base) ⇒ Object
reset! is an INSTANCE method on the extending class (a config object), so it is installed
here rather than declared in this module's body. It resolves the settings it operates on
from self.class up through its ancestry, so an instance of a subclass sees both settings
declared on the subclass and settings declared on any ancestor that extended Settings —
regardless of whether the subclass re-extends Settings itself.
A reset! the class already provides — its own, or one inherited from a non-axn ancestor —
wins: axn generates this one, so it defers rather than replacing behavior the author wrote,
leaving a debug breadcrumb instead. Settings still reset through the flat <name>= writers.
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 |
# File 'lib/axn/configurable.rb', line 576 def self.extended(base) if base.method_defined?(:reset!) || base.private_method_defined?(:reset!) if defined?(Axn.config) owner = base.instance_method(:reset!).owner Axn::Extensions.best_effort("logging a reset! collision", action: base) do Axn.config.logger.debug do "[Axn] #{base.name || base}: instance method `reset!` is already defined by #{owner}, so the " \ "Configurable settings DSL leaves it alone. Per-setting reset is unavailable on this class." end end end return end # INCLUDED as a module rather than defined on the class. A method defined directly on the class # outranks every module in the lookup chain, so it would beat a `reset!` the author includes # LATER — making the deferral depend on whether their include sits above or below the `extend`. # From a module, the class's own definition still wins (as it should) and so does anything # included after axn, while the pre-check above still covers what was already there. generated = Module.new base.include(generated) generated.send(:define_method, :reset!) do |*names| # Deferral has to be decided HERE, not only at extend time. The pre-check above covers a # `reset!` that already existed, and being a module covers one included later on this class — # but neither covers one that arrives later on an ANCESTOR, since the generated module sits # ahead of the superclass in a subclass's chain and would silently win. `super` resolves to # exactly the ancestors below this module, so asking for it makes the deferral independent of # whether the author's include ran before or after the extend. # Arguments explicit: `define_method` forbids implicit-argument `super`. return super(*names) if defined?(super) # Not `self.class`: a setting may be named `class`, and its generated reader would shadow the # real one — leaving reset! resolving its targets from a setting value. declared = Axn::Configurable.declared_settings_for(Axn::Internal::Identity.class_of(self)) targets = names.empty? ? declared.keys : names.map(&:to_sym) # Validate the WHOLE list before touching anything, so `reset!(:real, :typo)` leaves the # config exactly as it was instead of half-reset behind the raise. unknown = targets.reject { |name| declared.key?(name) } if unknown.any? raise ArgumentError, "reset! got unknown setting #{unknown.first.inspect}. Declared settings are: " \ "#{declared.keys.map(&:inspect).join(', ')}." end targets.each do |name| ivar = :"@#{name}" remove_instance_variable(ivar) if instance_variable_defined?(ivar) end self end end |
Instance Method Details
#_declared_settings ⇒ Object
Declared Setting objects by name, on THIS class only — not merged with an ancestor's. The
class flavor otherwise keeps no registry (only overridable settings are tracked, by
PerClassOverrides). reset! reads across the full ancestry via
Axn::Configurable.declared_settings_for, not through this method, so that walk never
mints an empty registry on a class that never declared anything.
565 |
# File 'lib/axn/configurable.rb', line 565 def _declared_settings = @_declared_settings ||= {} |
#overridable_config_source(&block) ⇒ Object
Registers the live singleton whose values are the library-level fallback
for per-class overrides (e.g. Axn.config). Read lazily on each
resolution, so a swapped singleton is picked up. Must be declared before
any overridable: true setting.
632 633 634 |
# File 'lib/axn/configurable.rb', line 632 def overridable_config_source(&block) @_overridable_config_source = block end |
#setting(name, default: nil, one_of: nil, validate: nil, overridable: false) ⇒ Object
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 |
# File 'lib/axn/configurable.rb', line 636 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:) _declared_settings[setting.name] = setting ivar = :"@#{name}" define_method(name) do return instance_variable_get(ivar) if instance_variable_defined?(ivar) return setting.default.call if setting.dynamic_default? # A literal default IS memoized: mutating it in place (`config.some_list << :x`) is a # supported way to extend one, which a fresh dup per read would silently discard. instance_variable_set(ivar, setting.dup_default) end define_method(:"#{name}?") { !!public_send(name) } define_method(:"#{name}=") do |value| setting.validate!(value) instance_variable_set(ivar, value) end return unless overridable raise ArgumentError, "setting #{name}: overridable: true requires overridable_config_source to be declared first" unless @_overridable_config_source source = @_overridable_config_source _define_override_methods(setting, -> { source.call.public_send(setting.name) }) end |