From 8097a387c0346ac26f79dd1279f6738affff0b84 Mon Sep 17 00:00:00 2001 From: Tim Riley Date: Tue, 18 Jun 2024 00:12:36 +1000 Subject: [PATCH] Allow multiple includes of Dry::Configurable Instead of raising AlreadyIncludedError (now removed), update the method undef code in `.included` to handle already-undefined methods gracefully. Allowing Dry::Configurable to be included multiple times across subclasses now allows for uses cases like a subclass adjusting various extension settings, e.g. `config_class`. --- lib/dry/configurable/errors.rb | 1 - lib/dry/configurable/extension.rb | 6 +-- .../dry/configurable/included_spec.rb | 44 ++++++++++++++++--- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/dry/configurable/errors.rb b/lib/dry/configurable/errors.rb index 37962f8..620866e 100644 --- a/lib/dry/configurable/errors.rb +++ b/lib/dry/configurable/errors.rb @@ -7,7 +7,6 @@ module Dry module Configurable Error = Class.new(::StandardError) - AlreadyIncludedError = Class.new(Error) FrozenConfigError = Class.new(Error) end end diff --git a/lib/dry/configurable/extension.rb b/lib/dry/configurable/extension.rb index 8e6bddc..f3e8803 100644 --- a/lib/dry/configurable/extension.rb +++ b/lib/dry/configurable/extension.rb @@ -26,8 +26,6 @@ def extended(klass) # @api private def included(klass) - raise AlreadyIncludedError if klass.include?(InstanceMethods) - super klass.class_eval do @@ -36,8 +34,8 @@ def included(klass) prepend(Initializer) class << self - undef :config - undef :configure + undef :config if method_defined?(:config) + undef :configure if method_defined?(:configure) end end diff --git a/spec/integration/dry/configurable/included_spec.rb b/spec/integration/dry/configurable/included_spec.rb index cb427f9..8c454e6 100644 --- a/spec/integration/dry/configurable/included_spec.rb +++ b/spec/integration/dry/configurable/included_spec.rb @@ -12,12 +12,6 @@ .to include(Dry::Configurable::InstanceMethods) end - it "raises when Dry::Configurable has already been included" do - expect { - configurable_klass.include(Dry::Configurable) - }.to raise_error(Dry::Configurable::AlreadyIncludedError) - end - it "ensures `.config` is not defined" do expect(configurable_klass).not_to respond_to(:config) end @@ -77,4 +71,42 @@ def finalize! expect(instance.finalized).to be(true) end end + + context "with deep class hierarchy" do + let(:configurable_class) do + Class.new do + include Dry::Configurable + end + end + + it "allows a subclass also to include Dry::Configurable" do + subclass = Class.new(configurable_class) do + include Dry::Configurable + end + + expect(subclass.new.config).to be_a(Dry::Configurable::Config) + end + + it "allows a subclass to reconfigure the behavior" do + custom_config_class = Class.new(Dry::Configurable::Config) do + def db + super + "!!" + end + end + + subclass = Class.new(configurable_class) do + setting :db + end + + subsubclass = Class.new(subclass) do + include Dry::Configurable(config_class: custom_config_class) + end + + obj = subsubclass.new + expect(obj.config).to be_a(custom_config_class) + + obj.config.db = "sqlite" + expect(obj.config.db).to eq "sqlite!!" + end + end end