Project

diftw

0.01
No commit activity in last 3 years
No release in over 3 years
A small dependency injection library for Ruby
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

DiFtw Build Status

Dependency Injection For The Win! A small, yet surprisingly powerful, dependency injection library for Ruby.

Why DI in Ruby?

If your only concern is testing, mocks/stubs and webmock might be all you need. But if you're working on a large project that hooks into all kinds of enterprisey services, and those services aren't always available in dev/testing/staging, a dash of DI might be just the thing.

Features

  • DI container w/dead-simple registration
  • Inject singletons or factories
  • Lazy injection (by default)
  • Inject into each of a class's instances, a single instance, a class itself, or a module
  • Uses parent-child injectors for max flexibility
  • Threadsafe, after registration

Dead-simple registration API

# Create your root injector/container
DI = DiFtw::Injector.new do
  singleton :foo do
    OpenStruct.new(message: "Everyone will get this same object")
  end

  factory :bar do
    OpenStruct.new(message: "Everyone will get a NEW version of this object")
  end

  # inject dependencies into your dependency
  factory :foobar, [:foo, :bar] do
    OpenStruct.new(message: foo.message + bar.message)
  end
end

# Or register things out here
DI.singleton :zorp do
  OpenStruct.new(message: "Bar")
end

Lazy injection (by default)

class Widget
  include DI.inject :foo, :bar
end

widget = Widget.new
# foo isn't actually injected until it's first called
puts widget.foo.message
=> "Foo"

Lazy injection is usually fine. But if it isn't, use inject!:

class Widget
  include DI.inject :foo, :bar
  
  def initialize
    inject!
  end
end

# foo and bar are immediately injected
widget = Widget.new

Inject into all instances, a single instance, a class, or a module

# Inject :baz into all Widget instance objects
class Widget
  include DI.inject :baz
end
puts Widget.new.baz.message
=> 'Baz'

# Inject :baz into one specific instance
x = SomeClass.new
DI.inject_instance x, :baz
puts x.baz.message
=> 'Baz'

# Inject :baz as a class method
class SomeClass
  extend DI.inject :baz
end
puts SomeClass.baz.message
=> 'Baz'

# Inject :baz as a module method 
module SomeModule
  extend DI.inject :baz
end
puts SomeModule.baz.message
=> 'Baz'

Parent-Child injectors

This is maybe the coolest part. Each time you call inject (or inject_instance) you're creating a fresh, empty child DiFtw::Injector. It will recursively look up dependencies through the parent chain until it finds the nearest registration of that dependency.

This means you can re-register a dependency on a child injector, and it will be injected instead of whatever is registered above it in the chain.

# Create your root injector and register :foo
DI = DiFtw::Injector.new
DI.singleton(:foo) { 'Foo' }

class Widget
  include DI.inject :foo
end

class Spline
  include DI.inject :foo
end

# Widget and Spline each get a new injector instance
Widget.injector.object_id != DI.object_id
=> true
Widget.injector.object_id != Spline.injector.object_id
=> true

# Each Widget instance gets a new injector instance. Same for Spline.
w1 = Widget.new
w1.injector.object_id != Widget.injector.object_id
=> true

w2 = Widget.new
w1.injector.object_id != w2.injector.object_id
=> true

# But all those child injectors are empty. They'll all resolve :foo
# to whatever is in DI[:foo]
Widget.new.foo
=> 'Foo'
Spline.new.foo
=> 'Foo'
Widget.new.foo.object_id == Spline.new.foo.object_id
=> true

# But we could re-register/override :foo in Spline.injector, and all new
# Spline instances would resolve :foo differently.
Spline.injector.singleton(:foo) { 'Bar' }
Spline.new.foo
=> 'Bar'
# But DI and Widget.injector would be unchanged
Widget.new.foo
=> 'Foo'

# We can go even further and override :foo in just one specific instance of Spline
# NOTE This only works if you're using lazy injection (the default) AND if you haven't called #foo yet
s = Spline.new
s.injector.singleton(:foo) { 'Baz' }
s.foo
=> 'Baz'
# Other Spline instances will still get their override from Spline.injector
Spline.new.foo
=> 'Bar'
# While Widget instances will all still get the original value from DI
Widget.new.foo
=> 'Foo'

DI in testing/local dev/staging/etc.

To inject different dependencies in these environments, you have several options. You can simply re-register dependencies in your root injector:

DI.singleton(:foo) { OpenStruct.new(message: 'Test Foo') }

And/Or you can use the parent-child injector features described above to great effect:

before :each do
  # Give all MyService instances 'Test foo' as #foo
  MyService.injector.singleton(:foo) {
    'Test foo'
  }
end

after :each do
  # Remove the override & fallback to whatever was registered in the root injector
  MyService.injector.delete :foo
end