diff --git a/lib/dry/system/auto_inject.rb b/lib/dry/system/auto_inject.rb new file mode 100644 index 00000000..badcc69d --- /dev/null +++ b/lib/dry/system/auto_inject.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'dry-auto_inject' + +module Dry + module System + # @api private + module AutoInject + # @api private + ForbiddenInjectionError = Class.new(StandardError) + + # Dry system specific injector builder class + # + # @api private + class Builder < Dry::AutoInject::Builder + def initialize(container, options = {}) + super + @pattern = options.fetch(:pattern) { nil } + end + + # @api private + def allow(pattern) + ::Dry::System::AutoInject::Builder.new(@container, pattern: pattern, strategies: @strategies) + end + + # @api private + def [](*dependency_names) + unless match_by_pattern?(dependency_names) + ::Kernel.raise AutoInject::ForbiddenInjectionError, + "injecting #{dependency_names} dependencies forbidden for injector pattern #{@pattern}" + end + + super + end + + private + + def match_by_pattern?(dependency_names) + @pattern.nil? || dependency_names.all? { |dependency| dependency.match(@pattern) } + end + end + end + end +end diff --git a/lib/dry/system/container.rb b/lib/dry/system/container.rb index da7bfe53..95a68a2f 100644 --- a/lib/dry/system/container.rb +++ b/lib/dry/system/container.rb @@ -14,6 +14,7 @@ require 'dry/system/loader' require 'dry/system/booter' require 'dry/system/auto_registrar' +require 'dry/system/auto_inject' require 'dry/system/manual_registrar' require 'dry/system/importer' require 'dry/system/component' @@ -455,6 +456,9 @@ def auto_register!(dir, &block) # An injector is a useful mixin which injects dependencies into # automatically defined constructor. # + # Also, it's possible to use `allow` method for build injector + # with specific pattern. + # # @example # # Define an injection mixin # # @@ -472,11 +476,33 @@ def auto_register!(dir, &block) # # MyApp['user_repo].db # instance under 'persistence.db' key # + # @example with allow pattern + # # Define an injection mixin + # # + # # system/import.rb + # Import = MyApp.injector + # PersistanceImport = Import.allow(/\Apersistence\.\w+/) + # + # # Use it in your auto-registered classes + # # + # # lib/user_repo.rb + # require 'import' + # + # class UserRepo + # include PersistanceImport['persistence.db'] # okay + # end + # + # class WrongInjection + # include PersistanceImport['operations.service'] # will raise error + # end + # + # MyApp['user_repo].db # instance under 'persistence.db' key + # # @param options [Hash] injector options # # @api public def injector(options = { strategies: strategies }) - Dry::AutoInject(self, options) + Dry::System::AutoInject::Builder.new(self, options) end # Requires one or more files relative to the container's root diff --git a/spec/unit/container/injector_spec.rb b/spec/unit/container/injector_spec.rb index 29999e8f..670aae25 100644 --- a/spec/unit/container/injector_spec.rb +++ b/spec/unit/container/injector_spec.rb @@ -3,7 +3,7 @@ require 'dry/system/container' RSpec.describe Dry::System::Container, '.injector' do - context 'default injector' do + context 'with default injector' do it 'works correct' do Test::Foo = Class.new @@ -26,6 +26,37 @@ end end + context 'with allow option' do + it 'works correct' do + Test::Foo = Class.new + + Test::Container = Class.new(Dry::System::Container) do + register 'foo', Test::Foo.new + end + + Test::Inject = Test::Container.injector.allow(/\Afo\w+/) + + injected_class = Class.new do + include Test::Inject['foo'] + end + + obj = injected_class.new + expect(obj.foo).to be_a Test::Foo + + another = Object.new + obj = injected_class.new(foo: another) + expect(obj.foo).to eq another + + Test::ForbiddenInject = Test::Inject.allow(/\Aoperations\.\w+/) + + expect do + Class.new do + include Test::ForbiddenInject['foo'] + end + end.to raise_error Dry::System::AutoInject::ForbiddenInjectionError + end + end + context 'injector_options provided' do it 'builds an injector with the provided options' do Test::Foo = Class.new