Skip to content

Dependency injection

Jeffrey Peckham edited this page Jul 2, 2013 · 2 revisions

Dependency injection, Ruby style

The goal is to allow objects to acquire dependencies while still preserving the ability for tests to substitute mock implementations. Plus the test harness needs to be able to "reset the world" and replace application-scoped objects in between test runs.

In the past, Director relied on the Config object to maintain all application-scoped state. Migrating to DI, we use the App object to maintain all application-scoped state (preferably in decomposed objects). Follow this pattern to initialize an object with dependencies:

class Something
  def initialize(mandatory_param, options={})
    @mandatory_param = mandatory_param
    @dependent_scoped_thing = options.fetch(:cheap_thing) { CheapThing.new }
    @application_scoped_thing = options.fetch(:expensive_thing) { App.instance.thing_service.thing }
  end
end

Initializers should have a hash param which allows for dependencies (and possibly other options) to be specified as named options. In normal application use, these dependencies are not specified in the new call and their lazily-evaluated fetch() values are used instead. In unit tests, mocks can be passed in, presenting a nice set of let statements instead of a series of ugly stubs on class methods.

The App object serves as a repository of application scoped objects. It can be reset between tests by creating a new App object; each App.new resets the singleton instance.

Clone this wiki locally