In everyday development, we face the concept of dependency injection. There are different approaches in the Ruby world to implement this design pattern, such as dry-rb. However, on large and complex projects, working with dry-rb turns into torture, especially debugging. That's why I want to introduce you to gem "aux".
Let's say we have a use case for creating a user
class CreateUserUseCase
include Aux::Pluggable
include Aux::Carriers
# Again we register our initialized and memoized CreateUserUseCase.
# So it will be available as a single instance.
register initialize: true, memoize: true
# And here we inject our dependencies.
# @!attribute [r] user_repository
# @return [UserRepository]
resolve :user_repository, as: :repository
# @param request [CreateUserRequest]
# @return [Success, Failure]
def call(request)
user = repository.create!(request.to_h)
success(user) # Create Aux::Carriers::Success instance
rescue
failure # Create Aux::Carriers::Failure instance
end
endclass UserRepository
include Aux::Pluggable
# Here we register our initialized and memoized UserRepository.
# So it will be available as a single instance.
register initialize: true, memoize: true
# @!method create!(**criteria)
# @param criteria [Hash]
# @raise [ActiveRecord::RecordInvalid]
# @return [User]
delegate :create!, to: :data_gateway
private
# @return [Class]
def data_gateway
@data_gateway ||= User
end
end And the CreateUserRequest for example:
class CreateUserRequest
include Aux::Pluggable
include Aux::Validations
# Here we register request as a Class and
# we can use it later for each given data
register
# @!attribute [rw] username
# @return [String]
# @!attribute [rw] email
# @return [String]
attr_accessor :username, :email
validates :username, presence: true
validates :email, presence: true, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
# @param username [String, nil]
# @param email [String, nil]
# @return [CreateUserRequest]
def initialize(username: nil, email: nil)
@username = username
@email = email
end
endWe see 3 interdependent entities that rely on the common dependency registry Aux::Pluggable.registry, where each of them is registered.
UserRepository and CreateUserUseCase are initialized once before registration in the registry. CreateUserRequest registered as a Class and can be initialized for each given datum.
Now we can put it all together in controller.
class UsersController < ApplicationController
include Aux::Pluggable
# @!attribute [r] create_request
# @return [Class]
resolve :create_user_request, as: :create_request
# @!attribute [r] create_use_case
# @return [CreateUserUseCase]
resolve :create_user_use_case, as: :create_use_case
# POST /users.json
def create
request = create_request.new(**params.to_unsafe_h.symbolize_keys)
return handle_error(request) unless request.valid?
result = create_use_case.call(request)
return handle_failure(result) unless result.success?
render(status: result.code, json: result.payload)
end
end That's it. Now, on occasion, we can easily replace a specific implementation such as UserRepository without problems.
These are not all possibilities. There is support for working with namespaces or registering (initializing) objects in the registry directly. The code base of the gem is pretty small and you can read it quickly.