Skip to content

Carburetor/carb-inject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

70 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

carb-inject

carb-inject is an utility library for automated dependency injection. Together with a generic container (even a simple Hash!), it will cover all the needs for an IoC container. Check out carb-container for more advanced ones.

Installation

Add this line to your application's Gemfile:

gem 'carb-inject'

And then execute:

$ bundle

Or install it yourself as:

$ gem install carb-inject

Glossary

Term Meaning
Dependency The actual Object a dependency is (a number for example). Can be extracted from the container with container[dependency_name]
Dependency name An object which allows extracting a Dependency from the container
Dependency alias A symbol representing Dependency name, must be a valid method name
Array of dependency names An array of Dependency name. When passed to the injector, every object must support to_s and the returned String must be a valid method name
Hash of dependency aliases (or hash of aliases) A hash consisting of Dependency alias => Dependency name

Usage

First you'll need a container object. carb-container provides a simple RegistryContainer which you can use. Alternatively, you can use a simple ruby hashmap, or use carb-inject in an entirely containerless fashion

container = { name: "john", age: 30 }

Create an injector that you'll use across your application. Usually you want to put this in a constant

require "carb-inject"
Inject = Carb::Inject::Injector.new(container)

Then, create a class you want dependencies injected automatically

class JohnPerson
  include Inject[:name, :age]

  def initialize(**deps)
    inject_dependencies!(deps)
  end

  def hello
    "Hello I'm #{name}, #{age} years old"
  end
end

And finally, use the class!

john = JohnPerson.new
john.hello # => Hello I'm john, 30 years old

You can overwrite dependencies on the fly

john = JohnPerson.new(age: 20)
john.hello # => Hello I'm john, 20 years old

You can still require different arguments in the constructor

class JohnPerson
  include Inject[:name, :age]

  def initialize(last_name, **dependencies)
    inject_dependencies!(dependencies)
    @last_name = last_name
  end

  def hello
    "Hello I'm #{name} #{@last_name}, #{age} years old"
  end
end

john = JohnPerson.new("snow", age: 20)
john.hello # => Hello I'm john snow, 20 years old

Finally, you can alias dependencies

class JohnPerson
  include Inject[special_name: :name, a_number: :age]

  def initialize(**deps)
    inject_dependencies!(deps)
  end

  def hello
    "special_name is #{special_name}, a_number is #{a_number}"
  end
end

john = JohnPerson.new(a_number: 20)
john.hello # => special_name is john, a_number is 20

Be aware, you can't pass on-the-fly dependencies that were not defined on that class. If you do, you must be the one taking care of them!

class JohnPerson
  include Inject[:name]

  def initialize(**deps)
    inject_dependencies!(deps)
  end

  def hello
    "Hello I'm #{name}, #{age} years old"
  end
end

john = JohnPerson.new(age: 20)
john.hello # => NameError: undefined local variable or method `age'

Auto invoke inject_dependencies!

Instead of manually calling inject_dependencies!, you can invoke the injector with true as second argument. This has the downsides of including a module which creates an initializer, with all the consequences it creates (some issues with inheritance). It's not recommended, but if you don't use inheritance, it does the trick.

require "carb-inject"

container = { name: "john", age: 30 }
Inject = Carb::Inject::Injector.new(container, true)

class JohnPerson
  include Inject[:name, :age]

  def hello
    "Hello I'm #{name}, #{age} years old"
  end
end

john = JohnPerson.new
john.hello # => Hello I'm john, 30 years old

Passing lambdas

There is an alternative way to use the library in a containerless fashion. You will pass a list of dependency as usual, but instead of aliasing them, pass a lambda and it will be resolved when used

require "carb-inject"

container = { name: "John", last_name: "Snow" }
Inject = Carb::Inject::Injector.new(container, true)

class JohnPerson
  include Inject[:last_name, foo: :name, age: -> { 30 }]

  def hello
    "Hello I'm #{foo} #{last_name}, #{age} years old"
  end
end

john = JohnPerson.new
john.hello # => Hello I'm John Snow, 30 years old

Gotchas

  • Alias hash must have symbols as keys
  • Straight dependency names, when used in array and not as values for alias hash, must support to_s and the resulting String must be a valid method name (an exception is raised otherwise)

Features

  • Supports inheritance
  • Can write your own injector if you don't like the syntax of the existing one
  • Can alias dependencies
  • Supports any container which responds to [] and has_key?
  • Can write your own initializer with your own arguments

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Carburetor/carb-inject.