From c510ede018b1a3bf3cb354f5e6210434877623b5 Mon Sep 17 00:00:00 2001 From: Tim Riley Date: Fri, 17 May 2024 22:30:06 +1000 Subject: [PATCH] Allow provider sources to specify provider options (#271) When registering a provider source, you can now provider a `provider_options:` containing a hash of default options for providers to be registered using that source. The one provider option currently supported is `namespace:`. Allowing a provider source to specify e.g. `provider_options: {namespace: true}` is useful if the source will register multiple components at different keys. In such a case, the provider will typically want to have a `namespace: true` provider option. Specifying this option at the time of provider source registration then makes for a simpler and less error-prone experience when the user eventually registers a provider using that source. The user now no longer needs to remember to provide their own explicit `namespace: true`. --- lib/dry/system.rb | 16 ++++++-- lib/dry/system/provider_registrar.rb | 24 +++++++----- lib/dry/system/provider_source_registry.rb | 27 +++++++++---- .../provider_sources/provider_options_spec.rb | 39 +++++++++++++++++++ 4 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 spec/integration/container/providers/provider_sources/provider_options_spec.rb diff --git a/lib/dry/system.rb b/lib/dry/system.rb index 1707f627..809a2548 100644 --- a/lib/dry/system.rb +++ b/lib/dry/system.rb @@ -30,15 +30,25 @@ def self.register_provider_sources(path) # Registers a provider source, which can be used as the basis for other providers # # @api public - def self.register_provider_source(name, group:, source: nil, &block) + def self.register_provider_source(name, group:, source: nil, provider_options: {}, &block) if source && block raise ArgumentError, "You must supply only a `source:` option or a block, not both" end if source - provider_sources.register(name: name, group: group, source: source) + provider_sources.register( + name: name, + group: group, + source: source, + provider_options: provider_options + ) else - provider_sources.register_from_block(name: name, group: group, &block) + provider_sources.register_from_block( + name: name, + group: group, + provider_options: provider_options, + &block + ) end end diff --git a/lib/dry/system/provider_registrar.rb b/lib/dry/system/provider_registrar.rb index 4ab6d178..ebafb359 100644 --- a/lib/dry/system/provider_registrar.rb +++ b/lib/dry/system/provider_registrar.rb @@ -46,7 +46,7 @@ def freeze # @see Container.register_provider # @api private - def register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block) + def register_provider(name, from: nil, source: nil, if: true, **provider_options, &block) raise ProviderAlreadyRegisteredError, name if providers.key?(name) if from && source.is_a?(Class) @@ -63,13 +63,18 @@ def register_provider(name, namespace: nil, from: nil, source: nil, if: true, &b if from build_provider_from_source( name, - namespace: namespace, source: source || name, group: from, + options: provider_options, &block ) else - build_provider(name, namespace: namespace, source: source, &block) + build_provider( + name, + source: source, + options: provider_options, + &block + ) end providers[provider.name] = provider @@ -205,25 +210,26 @@ def provider_paths } end - def build_provider(name, namespace:, source: nil, &block) + def build_provider(name, options:, source: nil, &block) source_class = source || Provider::Source.for(name: name, &block) Provider.new( + **options, name: name, - namespace: namespace, target_container: target_container, source_class: source_class ) end - def build_provider_from_source(name, source:, group:, namespace:, &block) - source_class = System.provider_sources.resolve(name: source, group: group) + def build_provider_from_source(name, source:, group:, options:, &block) + provider_source = System.provider_sources.resolve(name: source, group: group) Provider.new( + **provider_source.provider_options, + **options, name: name, - namespace: namespace, target_container: target_container, - source_class: source_class, + source_class: provider_source.source, &block ) end diff --git a/lib/dry/system/provider_source_registry.rb b/lib/dry/system/provider_source_registry.rb index e75899b2..bc04cd49 100644 --- a/lib/dry/system/provider_source_registry.rb +++ b/lib/dry/system/provider_source_registry.rb @@ -6,6 +6,17 @@ module Dry module System # @api private class ProviderSourceRegistry + # @api private + class Registration + attr_reader :source + attr_reader :provider_options + + def initialize(source:, provider_options:) + @source = source + @provider_options = provider_options + end + end + attr_reader :sources def initialize @@ -18,19 +29,19 @@ def load_sources(path) end end - def register(name:, group:, source:) - sources[key(name, group)] = source + def register(name:, group:, source:, provider_options:) + sources[key(name, group)] = Registration.new( + source: source, + provider_options: provider_options + ) end - def register_from_block(name:, group:, &block) + def register_from_block(name:, group:, provider_options:, &block) register( name: name, group: group, - source: Provider::Source.for( - name: name, - group: group, - &block - ) + source: Provider::Source.for(name: name, group: group, &block), + provider_options: provider_options ) end diff --git a/spec/integration/container/providers/provider_sources/provider_options_spec.rb b/spec/integration/container/providers/provider_sources/provider_options_spec.rb new file mode 100644 index 00000000..4a8a785d --- /dev/null +++ b/spec/integration/container/providers/provider_sources/provider_options_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.describe "Providers / Provider sources / Provider options" do + let(:container) { Class.new(Dry::System::Container) } + + specify "provider_options registered with provider sources are used when creating corresponding providers" do + Dry::System.register_provider_source(:db, group: :my_framework, provider_options: {namespace: true}) do + start do + register "config", "db_config_here" + end + end + + # Note no `namespace:` option when registering provider + container.register_provider :db, from: :my_framework + + # Also works when using a different name for the provider + container.register_provider :my_db, from: :my_framework, source: :db + + container.start :db + container.start :my_db + + expect(container["db.config"]).to eq "db_config_here" + expect(container["my_db.config"]).to eq "db_config_here" + end + + specify "provider source provider_options can be overridden" do + Dry::System.register_provider_source(:db, group: :my_framework, provider_options: {namespace: true}) do + start do + register "config", "db_config_here" + end + end + + container.register_provider :db, from: :my_framework, namespace: "custom_db" + + container.start :db + + expect(container["custom_db.config"]).to eq "db_config_here" + end +end