Skip to content

Commit

Permalink
Use target_container alias to pass to providers (#270)
Browse files Browse the repository at this point in the history
In `ProviderRegistrar`, create a new `target_container` (an alias of its `container` attribute), and use this `target_container` when creating providers.

This makes it possible for users of dry-system to provide a subclass of the `ProviderRegistrar` where they can customise the target_container that their providers receive.

We need this so that providers in Hanami can receive their respective slice as the `target`, making it possible for code inside the provider to interact naturally with the Hanami slice itself, as opposed to its internal container (which itself knows nothing about the broader Hanami app).

As part of this change, make `ProviderRegistrar` and `ProviderRegistrar#target_container` both public API (with everything else about the provider registrar still remaining private).
  • Loading branch information
timriley authored May 12, 2024
1 parent 7a7b5a3 commit f553c22
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 3 deletions.
15 changes: 12 additions & 3 deletions lib/dry/system/provider_registrar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ module System
# provider registrar is responsible for loading provider files and exposing an API for
# running the provider lifecycle steps.
#
# @api private
# @api public
# @since 1.1.0
class ProviderRegistrar
# @api private
attr_reader :providers

# @api private
attr_reader :container

# Returns the container exposed to providers as `target_container`.
#
# @return [Dry::System::Container]
#
# @api public
# @since 1.1.0
alias_method :target_container, :container

# @api private
def initialize(container)
@providers = {}
Expand Down Expand Up @@ -202,7 +211,7 @@ def build_provider(name, namespace:, source: nil, &block)
Provider.new(
name: name,
namespace: namespace,
target_container: container,
target_container: target_container,
source_class: source_class
)
end
Expand All @@ -213,7 +222,7 @@ def build_provider_from_source(name, source:, group:, namespace:, &block)
Provider.new(
name: name,
namespace: namespace,
target_container: container,
target_container: target_container,
source_class: source_class,
&block
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

RSpec.describe "Providers / Custom provider registrar" do
specify "Customizing the target_container for providers" do
# Create a provider registrar that exposes a container _wrapper_ (i.e. something resembling a
# Hanami slice) as the target_container.
provider_registrar = Class.new(Dry::System::ProviderRegistrar) do
def self.for_wrapper(wrapper)
Class.new(self) do
define_singleton_method(:new) do |container|
super(container, wrapper)
end
end
end

attr_reader :wrapper

def initialize(container, wrapper)
super(container)
@wrapper = wrapper
end

def target_container
wrapper
end
end

# Create the wrapper, which has an internal Dry::System::Container (configured with our custom
# provider_registrar) that it then delegates to.
container_wrapper = Class.new do
define_singleton_method(:container) do
@container ||= Class.new(Dry::System::Container).tap do |container|
container.config.provider_registrar = provider_registrar.for_wrapper(self)
end
end

def self.register_provider(...)
container.register_provider(...)
end

def self.start(...)
container.start(...)
end
end

# Create a provider to expose its given `target` so we can make expecations about it
exposed_target = nil
container_wrapper.register_provider(:my_provider) do
start do
exposed_target = target
end
end
container_wrapper.start(:my_provider)

expect(exposed_target).to be container_wrapper
end
end

0 comments on commit f553c22

Please sign in to comment.