Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Provider Sources to choose a custom superclass #275

Merged
merged 2 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/dry/system/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ class Provider
attr_reader :source

# @api private
def initialize(name:, namespace: nil, target_container:, source_class:, &block) # rubocop:disable Style/KeywordParametersOrder
# rubocop:disable Layout/LineLength, Style/KeywordParametersOrder
def initialize(name:, namespace: nil, target_container:, source_class:, source_options: {}, &block)
@name = name
@namespace = namespace
@target_container = target_container
Expand All @@ -137,11 +138,13 @@ def initialize(name:, namespace: nil, target_container:, source_class:, &block)
@step_running = nil

@source = source_class.new(
**source_options,
provider_container: provider_container,
target_container: target_container,
&block
)
end
# rubocop:enable Layout/LineLength, Style/KeywordParametersOrder

# Runs the `prepare` lifecycle step.
#
Expand Down
27 changes: 16 additions & 11 deletions lib/dry/system/provider/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,20 @@ class << self
# @see Dry::System::Provider::SourceDSL
#
# @api private
def for(name:, group: nil, &block)
Class.new(self) { |klass|
def for(name:, group: nil, superclass: nil, &block)
superclass ||= self

Class.new(superclass) { |klass|
klass.source_name name
klass.source_group group

name_with_group = group ? "#{group}->#{name}" : name
klass.instance_eval <<~RUBY, __FILE__, __LINE__ + 1
def name
"#{superclass.name}[#{name_with_group}]"
end
RUBY
alassek marked this conversation as resolved.
Show resolved Hide resolved
timriley marked this conversation as resolved.
Show resolved Hide resolved

SourceDSL.evaluate(klass, &block) if block
}
end
Expand All @@ -58,14 +68,6 @@ def inherited(subclass)
end
end

# @api private
def name
source_str = source_name
source_str = "#{source_group}->#{source_str}" if source_group

"Dry::System::Provider::Source[#{source_str}]"
end

# @api private
def to_s
"#<#{name}>"
Expand Down Expand Up @@ -117,7 +119,10 @@ def inspect
#
# @api public
attr_reader :target_container
alias_method :target, :target_container

# @see #target_container
# @api public
def target = target_container

# @api private
def initialize(provider_container:, target_container:, &block)
Expand Down
39 changes: 37 additions & 2 deletions lib/dry/system/provider_registrar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ def provider_files
}.first
end

# Extension point for subclasses to customize their
# provider source superclass. Expected to be a subclass
# of Dry::System::Provider::Source
#
# @api public
# @since 1.1.0
def provider_source_class = Dry::System::Provider::Source

# Extension point for subclasses to customize initialization
# params for provider_source_class
#
# @api public
# @since 1.1.0
def provider_source_options = {}

# @api private
def finalize!
provider_files.each do |path|
Expand Down Expand Up @@ -196,25 +211,45 @@ def provider_paths
end

def build_provider(name, options:, source: nil, &block)
source_class = source || Provider::Source.for(name: name, &block)
source_class = source || Provider::Source.for(
name: name,
superclass: provider_source_class,
&block
)

source_options =
if source_class < provider_source_class
provider_source_options
else
{}
end

Provider.new(
**options,
name: name,
target_container: target_container,
source_class: source_class
source_class: source_class,
source_options: source_options
)
end

def build_provider_from_source(name, source:, group:, options:, &block)
provider_source = System.provider_sources.resolve(name: source, group: group)

source_options =
if provider_source.source == provider_source_class
provider_source_options
else
{}
end

Provider.new(
**provider_source.provider_options,
**options,
name: name,
target_container: target_container,
source_class: provider_source.source,
source_options: source_options,
&block
)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

RSpec.describe "Providers / Custom provider superclass" do
let!(:custom_superclass) do
module Test
class CustomSource < Dry::System::Provider::Source
attr_reader :custom_setting

def initialize(custom_setting:, **options, &block)
super(**options, &block)
@custom_setting = custom_setting
end
end
end

Test::CustomSource
end

let!(:custom_registrar) do
module Test
class CustomRegistrar < Dry::System::ProviderRegistrar
def provider_source_class = Test::CustomSource
def provider_source_options = {custom_setting: "hello"}
end
end

Test::CustomRegistrar
end

subject(:system) do
module Test
class Container < Dry::System::Container
configure do |config|
config.root = SPEC_ROOT.join("fixtures/app").realpath
config.provider_registrar = Test::CustomRegistrar
end
end
end

Test::Container
end

it "overrides the default Provider Source base class" do
system.register_provider(:test) {}

provider_source = system.providers[:test].source

expect(provider_source.class).to be < custom_superclass
expect(provider_source.class.name).to eq "Test::CustomSource[test]"
expect(provider_source.custom_setting).to eq "hello"
end

context "Source class != provider_source_class" do
let!(:custom_source) do
module Test
class OtherSource < Dry::System::Provider::Source
attr_reader :options

def initialize(**options, &block)
@options = options.except(:provider_container, :target_container)
super(**options.slice(:provider_container, :target_container), &block)
end
end
end

Test::OtherSource
end

specify "External source doesn't use provider_source_options" do
Dry::System.register_provider_source(:test, group: :custom, source: custom_source)
system.register_provider(:test, from: :custom) {}

expect {
provider_source = system.providers[:test].source
expect(provider_source.class).to be < Dry::System::Provider::Source
expect(provider_source.options).to be_empty
}.to_not raise_error
end

specify "Class-based source doesn't use provider_source_options" do
system.register_provider(:test, source: custom_source)

expect {
provider_source = system.providers[:test].source
expect(provider_source.class).to be < Dry::System::Provider::Source
expect(provider_source.options).to be_empty
}.to_not raise_error
end
end
end
Loading