[dry-autoinject] Passing manual dependencies

Hi, I have some problems with understanding how to pass manual dependencies in business logic code.
Can it possible to use them in code when class instance with manual dependency is auto injected dependency to another class instance too?

As example I have 4 classes. RootClass, MyClass, MyNestedClass, MyAnotherNestedClass.
RootClass has dependency which uses MyClass.
MyClass has dependency which uses MyNestedClass or MyAnotherNestedClass.

class MyClass
  include Import[my_nested_class: nested_class]
  
  def call(options)
     nested_class.call(options)
  end
end

MyClass can be called with MyNestedClass or MyAnotherNestedClass as dependency. And I understand how it can be done with usual object creation.

MyClass.new.call(options)
# or
MyClass.new(nested_class: MyAnotherNestedClass.new).call(options)

But when I want to call an instance of MyClass as auto injected dependency of another class instance (RootClass) I can not do it with manual dependencies passing.

class RootClass
  include Import[my_class]
  
  def call(options)
    my_class.call(options)  # and I cannot pass MyAnotherNestedClass as dependency with this syntax
  end
end

Can it be done somehow or I don’t understand some principles of DI usage?

P.S. I know about registering classes instead of instances, but I want to know can I do this with instances or not.

Yes, this can be done but you must change how you’re thinking about the class and instances.

The class here is being used as an Inversion of Control Container whereas the instance is intended to be used as a first-class function.

In regular OOP Ruby, people tend to think of classes and instances as part of the same concern. The goal of dry-auto_inject is to handle the class instantiation for you so you can just think of logic in a functional way.

So, the way to do what you want here is to define multiple identities for RootClass which inject different dependencies.

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'dry-auto_inject'
end

require 'dry/core/container'
require 'dry/auto_inject'

class ExampleContainer
  extend Dry::Core::Container::Mixin

  register "my_class" do
    MyClass.new
  end

  register "my_nested_class" do
    MyNestedClass.new
  end

  register "my_another_nested_class" do
    MyAnotherNestedClass.new
  end

  register "my_class.another" do
    MyClass.new(nested_class: resolve("my_another_nested_class"))
  end

  register "root_class" do
    RootClass.new
  end

  register "root_class.alternate" do
    RootClass.new(my_class: resolve("my_class.another"))
  end
end

Import = Dry::AutoInject(ExampleContainer)

class RootClass
  include Import[:my_class]

  def call = my_class.call
end

class MyClass
  include Import[nested_class: "my_nested_class"]

  def call = nested_class.call
end

class MyNestedClass
  def call = "called from #{self.class.name}"
end

class MyAnotherNestedClass
  def call = "called from #{self.class.name}"
end

puts ExampleContainer["root_class"].call
puts ExampleContainer["root_class.alternate"].call

1 Like