From d973260fa15e4b9d9433f8efbb7d2394862c53ac Mon Sep 17 00:00:00 2001 From: Tim Riley Date: Mon, 31 Jan 2022 23:23:45 +1100 Subject: [PATCH] Add injector mixin plugin --- lib/dry/system/plugins.rb | 3 + lib/dry/system/plugins/injector_mixin.rb | 65 ++++++++++++++ .../container/plugins/injector_mixin_spec.rb | 84 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 lib/dry/system/plugins/injector_mixin.rb create mode 100644 spec/integration/container/plugins/injector_mixin_spec.rb diff --git a/lib/dry/system/plugins.rb b/lib/dry/system/plugins.rb index e05f6fc36..ccb5b0601 100644 --- a/lib/dry/system/plugins.rb +++ b/lib/dry/system/plugins.rb @@ -134,6 +134,9 @@ def enabled_plugins require "dry/system/plugins/zeitwerk" register(:zeitwerk, Plugins::Zeitwerk) + + require_relative "plugins/injector_mixin" + register(:injector_mixin, Plugins::InjectorMixin) end end end diff --git a/lib/dry/system/plugins/injector_mixin.rb b/lib/dry/system/plugins/injector_mixin.rb new file mode 100644 index 000000000..d37535302 --- /dev/null +++ b/lib/dry/system/plugins/injector_mixin.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Dry + module System + module Plugins + # @api private + class InjectorMixin < Module + MODULE_SEPARATOR = "::" + + attr_reader :name + + def initialize(name: "Deps") + @name = name + end + + def extended(container) + container.after(:configure, &method(:define_mixin)) + end + + private + + def define_mixin(container) + inflector = container.config.inflector + + name_parts = name.split(MODULE_SEPARATOR) + + if name_parts[0] == "" + name_parts.delete_at(0) + root_module = Object + else + root_module = container_parent_module(container) + end + + mixin_parent_mod = define_parent_modules( + root_module, + name_parts, + inflector + ) + + mixin_parent_mod.const_set( + inflector.camelize(name_parts.last), + container.injector + ) + end + + def container_parent_module(container) + if container.name + parent_name = container.name.split(MODULE_SEPARATOR)[0..-2].join(MODULE_SEPARATOR) + container.config.inflector.constantize(parent_name) + else + Object + end + end + + def define_parent_modules(parent_mod, name_parts, inflector) + return parent_mod if name_parts.length == 1 + + name_parts[0..-2].reduce(parent_mod) { |parent_mod, mod_name| + parent_mod.const_set(inflector.camelize(mod_name), Module.new) + } + end + end + end + end +end diff --git a/spec/integration/container/plugins/injector_mixin_spec.rb b/spec/integration/container/plugins/injector_mixin_spec.rb new file mode 100644 index 000000000..2058f6ccb --- /dev/null +++ b/spec/integration/container/plugins/injector_mixin_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +RSpec.describe "Plugins / Injector mixin" do + + describe "default options" do + it "creates a 'Deps' mixin in the container's parent module" do + module Test + class Container < Dry::System::Container + use :injector_mixin + configured! + end + end + + component = Object.new + Test::Container.register "component", component + + depending_obj = Class.new do + include Test::Deps["component"] + end.new + + expect(depending_obj.component).to be component + end + end + + describe "name given" do + it "creates a mixin with the given name in the container's parent module" do + module Test + class Container < Dry::System::Container + use :injector_mixin, name: "Inject" + configured! + end + end + + component = Object.new + Test::Container.register "component", component + + depending_obj = Class.new do + include Test::Inject["component"] + end.new + + expect(depending_obj.component).to be component + end + end + + describe "nested name given" do + it "creates a mixin with the given name in the container's parent module" do + module Test + class Container < Dry::System::Container + use :injector_mixin, name: "Inject::These::Pls" + configured! + end + end + + component = Object.new + Test::Container.register "component", component + + depending_obj = Class.new do + include Test::Inject::These::Pls["component"] + end.new + + expect(depending_obj.component).to be component + end + end + + describe "top-level name given" do + it "creates a mixin with the given name in the top-level module" do + module Test + class Container < Dry::System::Container + use :injector_mixin, name: "::Deps" + configured! + end + end + + component = Object.new + Test::Container.register "component", component + + depending_obj = Class.new do + include ::Deps["component"] + end.new + + expect(depending_obj.component).to be component + end + end +end