Skip to content

raindeer-rb/providers

Repository files navigation

Dependency Expression

Providers

Gem version GitHub repo Codeberg repo

Automatic Dependency Injection where you get to see and keep control of the constructor.

Injectors

There are 3 ways to inject a dependency.

1. Dependency Expression

Place a Dependency expression as the default value of your dependency:

class MyClass
  include LowType

  def initialize(logger: Dependency)
    @logger = logger # => "logger" is injected.
  end
end

ℹ️ This method requires LowType in order to use the def(dependency: Dependency) syntax.

2. Constructor Include

Or you may like to use the more traditional include syntax:

class MyClass
  include Dependencies[:logger]

  def my_method
    @logger # => "@logger" is injected.
  end
end

This method hides and creates the constructor on your behalf.

3. Providers Hash

logger = Providers[:my_provider]

ℹ️ Use this method only when necessary, it's the least "in the spirit" of dependency injection and takes more lines of code to stub in a test.

Providers

Provide the dependency with:

Providers.define(:logger) do
  Logger.new
end

Namespaced string keys are fine too:

Providers.define('billing.payment_provider') do
  PaymentProvider.new
end

Mixing dependency types

Providers lets you do something special; mix "classical" dependency injection (passing an arg to new) with "provider" style dependency injection (via a framework):

Providers.define(:provider_dependency) do
  ProviderDependency.new
end

# Define both a "provider" and a "classical" dependency:
class MyClass
  include LowType

  def initialize(provider_dependency: Dependency, classical_dependency:)
    @provider_dependency = provider_dependency
    @classical_dependency = classical_dependency
  end
end

# Then call without "provider_dependency":
MyClass.new(classical_dependency: ClassicalDependency.new)

The omitted provider_dependency argument will automatically be injected from the provider_dependency provider by Providers!

Now you get to have your classical dependency cake 🍰 and eat it too with an automatically injected dependency spoon 🥣

API

Dependency Expression

A Dependency Expression defines the dependency to be injected, the provider that will inject it and the name of the local variable that it will be made available as. The def(dependency: Dependency) syntax is an Expression; an object composed via a query builder like interface.

ℹ️ The value after the pipe | becomes the provider key. When the provider key is omitted then the positional/keyword argument becomes the provider key.

To define a provider with a different name to that of the local variable do:

def initialize(dependency_one: Dependency | :provider_one)
  dependency_one # => Dependency injected from :provider_one.
end

For providers with string keys do:

def initialize(dependency_two: Dependency | 'billing.provider_two')
  dependency_two # => Dependency injected from 'billing.provider_two'.
end

Dependencies on multiple lines:

def initialize(
  dependency_one: Dependency | :provider_one,
  dependency_two: Dependency | 'billing.provider_two',
)
  dependency_one # => Dependency injected from :provider_one.
  dependency_two # => Dependency injected from 'billing.provider_two'.
end

Constructor Include

The include style syntax supports the same functionallity as the Dependency Expression syntax.

Multiple dependencies:

class MyClass
  include Dependencies[:dependency_one, :dependency_two]
end

Dependency with differing dependency and provider key:

class MyClass
  include Dependencies[dependency_one: :provider_one]
  # Instance variable @dependency_one is now available on instantiation.
end

Namespaced dependency:

class MyClass
  include Dependencies['billing.provider_two']
  # Dependency injected without the namespace as @provider_two.
end

Separating dependencies on multiple lines:

class MyClass
  include Dependencies[
    :dependency_one,
    'billing.provider_two',
    dependency_three: :provider_three,
    dependency_four: 'billing.provider_four',
    :five_dependencies_is_still_quite_reasonable_yeah,
  ]
end

Providers Hash

logger = Providers['namespace.my_provider']

Multiple dependencies:

dependency_one, dependency_two = Providers[:provider_one, 'provider_two']

Examples

Boot file

A boot sequence taken from the boot file of Raindeer:

require 'low_event' # Defines 'low.event.pool' provider.

Providers.define('rain.router') do
  require_relative 'router/router'
  Rain::Router.new
end

Providers.define('rain.matrix') do
  require_relative 'matrix/matrix'
  Rain::Matrix.new(event_pool: Providers['low.event.pool'])
end

Providers.define('low.loop') do
  require 'low_loop'
  LowLoop.new(router: Providers['rain.router'], renderer: Providers['rain.matrix'])
end

Installation

Add gem 'providers' to your Gemfile then:

bundle install

About

Dependency Injection in crisp and clear syntax

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors