Skip to content

Commit

Permalink
Allow multiple includes of Dry::Configurable
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
timriley committed Jun 17, 2024
1 parent f619cc8 commit 8097a38
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 11 deletions.
1 change: 0 additions & 1 deletion lib/dry/configurable/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ module Dry
module Configurable
Error = Class.new(::StandardError)

AlreadyIncludedError = Class.new(Error)
FrozenConfigError = Class.new(Error)
end
end
6 changes: 2 additions & 4 deletions lib/dry/configurable/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ def extended(klass)

# @api private
def included(klass)
raise AlreadyIncludedError if klass.include?(InstanceMethods)

super

klass.class_eval do
Expand All @@ -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

Expand Down
44 changes: 38 additions & 6 deletions spec/integration/dry/configurable/included_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 8097a38

Please sign in to comment.