Dependency Injection in Ruby

Dependency injection is a software design pattern that allows a program's dependencies (i.e., the objects it relies on to perform its tasks) to be supplied externally, rather than being hard-coded or constructed internally. This can help improve the flexibility, modularity, and testability of the code, by allowing dependencies to be easily swapped or mocked for testing purposes.

In Ruby, dependency injection can be implemented using a variety of techniques and tools, depending on the specific needs and requirements of the code. Some common approaches include the following:

  • Using keyword arguments in method signatures to specify dependencies. For example, a User class could define a #create method that accepts a database argument, which specifies the database object to use for storing the user data:
class User
  def create(name:, email:, database:)
    database.insert(name: name, email: email)
  end
end

Using a dependency injection framework, such as Dry::Container or Inversion of Control (IoC), to manage the dependencies between objects and inject them automatically. This can help you decouple your code and make it easier to test and maintain. For example, using the Dry::Container framework, you could define the dependencies for a User class as follows:

require 'dry/container'

class User
  extend Dry::Container::Mixin

  register(:database) { Database.new }
  register(:logger) { Logger.new }

  def create(name:, email:)
    database.insert(name: name, email: email)
    logger.info("Created user: #{name}")
  end
end

Using constructor injection, where dependencies are specified as arguments to the object's constructor (i.e., the initialize method). This can help you enforce the dependencies for an object, and make it clear which dependencies are required for the object to function properly. For example, the User class could define an initialize method that accepts a database and logger argument:

class User
  def initialize(database, logger)
    @database = database
    @logger = logger
  end

  def create(name:, email:)
    @database.insert(name: name, email: email)
    @logger.info("Created user: #{name}")
  end
end

Overall, using dependency injection in Ruby can help you write flexible, modular code that is easier to test and maintain. It can allow you to swap or mock dependencies as needed, and provide a clear, explicit way to specify and manage the dependencies between objects in your application.

Did you find this article valuable?

Support Matheus Puppe Blog by becoming a sponsor. Any amount is appreciated!