Inspired by the Stop hating Java post by my friend Andrzej I started to identify the pros and cons of a pattern called Dependency Injection in the Ruby/Rails world. Before I explain the pattern, let me discuss the problem – a dependency.

What is a Dependency?

Let’s say we’re working in a casual Rails controller instance. Whenever we access an object (or class) instance different to the current controller instance (i.e. self), we’re creating a dependency. Dependency here means the controller needs to know class and method names in another domain. Here are some typical dependencies found in many controller actions.

def show
  @comments = Comment.find(:all)

Here, the action knows both where and how to find the comments. It’s a dependency to the model layer.

def update
  # ...
  logger.notice "Comment #{@text} updated."

In this example, we have a strong dependency to the logger component – looking into the Rails code we can see that the #logger method in turn creates a logger instance, so we have to know about instantiation and usage of the logger.

%h1 Welcome, #{current_user.name}

This partial uses a helper method #current_user, which in turn queries controller and request in order to find the current user instance.

What’s the Problem with Dependencies?

Now, the examples above are code you’re gonna find in almost any Rails application – and there’s nothing wrong with that! However, problems might appear as soon as you want to test components separately.

For instance, what if I’d like to test my logging code within the controller, like asserting that the correct message is logged when something special happens?

it "logs correctly" do
  put :update
  assert_equal "Comment Yo! updated.", 
    @controller.logger.last_notice

I’d have to mock the logger instance to make it “testable”, here, to provide the #last_notice method.

Let’s say the original #logger method looks like this.

  def logger
    @logger ||= Rails::SmokeSignalLogger.new
  end

To mock the logger, people overwrite the respective method in the test case.

ActionController.class_eval do
  def logger
    @logger ||= Test::MockedLogger.new
  end

Code like this changes the logger for the entire test suite and might break other tests. A common solution is to reset the original method after each test. In other words, we change a global property for a local test – and this is wrong.

Another Problem: Configurability

In addition to the testing problems we get with dependencies, what if my colleagues love the way I program and plan to use one of my modules in their software (highly improbable). However, they don’t want the SmokeSignalLogger since they’re not speaking Indian, but they want to use another logging layer JungleDrumsLogger?

As soon as my component is imported in their application they’d probably monkey-patch my code to “configure” it.

NicksControllerMethods.class_eval do
  def logger
    @logger ||= 
      JungleDrumsLogger.new(:flavor => :coconut)
  end

I personally consider monkey-patching a code smell.

What’s a component?

Ok. I showed a couple of examples about dependencies and I used the word component a lot. The reason for this is: The Dependency Injection pattern is only applicable in software systems consisting of separated components. The idea is that outer components inject dependencies into smaller components.

A component as it might be a piece of software like a controller instance, an ActiveRecord row instance or a logger object. I see three attributes to make something a component.

  • First, a component has a limited scope of interest. It’s field of work is bounded to some special duty (logging messages, rendering a comments box, …).
  • Second, a component instance cannot access properties of other components, unless you’re providing a way to do so. For example, the logger may not access variables of the controller and vice-versa.

Dependency Injections in Rails

To get back to our examples, let’s see how we could apply DI to get rid of the logger dependency. Don’t let the controller create it itself, but do that on the outside and pass – “inject” – the object into the controller.

One flavor of DI is called Constructor Injection and would imply we inject the logger in the constructor.

class ActionController < ...
  def initialize(..., logger)
    @logger = logger
  end

Obviously, the outer framework would have to take care of creating and passing the logger.

  logger = BushDrumsLogger.new
  controller = ActionController.new(..., logger)

A second form is called Setter Injection where the controller exposes a writer for the injected attribute.

class ActionController < ...
  attr_writer :logger

Both the using frameworks or the test case can set the logger without overriding any code at all.

  controller.logger = TubeMailLogger.new

Cells and DI

One huge problem I see with Rails and DI is: currently, there are not enough components. We got one monolithic ActionController instance, a couple of row models and a global view instance – that’s it. This doesn’t make Rails a bad thing, or thousands of well-running apps out there!

However, the missing components in Rails make it hard to write reusable software and test these in isolation. That’s why we got the Cells gem – it provides reusable view components for Rails and goes perfectly together with DI.

Say we have a sidebar widget that displays the recent comments from your blog. A cell encapsulates that part of the page, the data aggregation and the rendering (read this post if you need a quick introduction to Cells).

Injecting the logger, ouch!

If we needed a special logging mechanism for the widget we could pass it into the component. I don’t know why a widget displaying comments would need to log, but let’s assume it.

%h1 My great blog
 
#sidebar
  = render_cell(:sidebar, :comments, logger)

The cell could then use the external logger without creating a dependency.

class SidebarCell < Cell::Base
  def comments(logger)
    logger.notice "Displaying comments at #{Time.now}!"
 
    render
  end

The additional arguments from the #render_cell invocation are directly passed as method arguments – this is what we call state-args in Cells.

It’s obvious that another project using that sidebar component could inject a completely different logger instance.

Testing with DI

The limited scope of a cell makes it pretty easy to test. Not only can we test that object-oriented “partial” in complete isolation but also can we pass in a mocked logger easily.

it "should render beautifully" do
  render_cell(:sidebar, :comments, mock_logger).
    should have_selector("ul")
end
 
it "should log correctly" do
  logger = ArrayLogger.new
  render_cell(:sidebar, :comments, logger)
 
  logger.last.should match /Logging/
end

No need to change or reset any global here, just throw-away instances and we’re done!

Conclusion

I can’t think of a cleaner solution for dissolving dependencies like these. The DI pattern makes it easy to keep your components dumb. The problem is that you first have to identify your dependencies, then refactor to components and inject instances from the controller (or wherever else).

Especially in Rails, Cells help to build a real MVC application with a data-aggregating ActionController (I call this FrontController) and fine-grained view components that get additional dependencies from the outside.

Tags: , ,

17 Responses to “Rails Misapprehensions: The Dependency Injection Pattern”


  1. Colin Gemmell

    I’ve had similar thoughts for awhile. Coming from a C# background DI was a big deal to me and something i’ve come to miss gradually when working with rails.

    I did a quick spike a while ago that really just uses the service locator pattern (https://github.com/pythonandchips/depends-on) but for me it a starting point to get back to DI with its benefits I enjoyed in C#


  2. nick

    @Colin: Cool! I think it’s not a matter of the language, whether it’s C#, Java, or Ruby, but a matter of framework design – and Rails is definitely not made up for DI, yet. As I said, it’s lacking components, but I like the path Rails 3 is taking and we’ll try to incorporate more ideas.

  3. @colin: before going down the dependency injection framework route, you should checkout Jamis Buck’s presentation “Recovering from the Enterprise” http://rubyconf2008.confreaks.com/recovering-from-enterprise.html

  4. This is exactly what I’ve been looking for. Grails offers a layer called services which provide this funtionality. It is a huge missing hole in Rails. Great post!!!

  5. Hey Nick, good post!

    I fully agree that exposing dependencies in the constructor (or through the setter) makes the dependencies more explicit and the whole thing more testable.

    Good to see how Cells makes it easy to use DI.

    BTW, logging is an interesting concern in software projects. It’s often called a crosscutting concern as it’s so invasive in many places in our code. There are several ways how to deal with it:

    1. Make logger a global. That’s the rails way. Ugly.
    2. Use DI, as in your example. Nice, but can be exhausting if you like to log a lot.
    3. Use Aspect Oriented Programming and create a LoggingAspect. This way you hide the logging from your code and put it in a separate component (aspect). I used AOP a lot with Java and have just started with Aquarium in Ruby. The results are promising. You can easily extract all the logging into one place. Will blog about it soon.

    Anyway, great that you bring the topic of DI up! Cells are awesome :)

  6. Hi Nick,

    One reason we haven’t imported this pattern into the Ruby world is that Ruby’s dynamic nature means we can easily setup expectations on fine grained interactions with real objects.

    See https://gist.github.com/1161372 for how I would mock logging a notice log statement on a controller with RSpec. Mocha and other mocking frameworks would be the same. This lets RSpec handle cleaning up the redefinition of any mocked or stubbed methods between tests. In practice this works very well. If ‘logger’ were not exposed as a method on the controller then you could setup the expectation on the Rails.logger singleton that it interacts with by default.

    As for something like a model interaction, it is very common to let that hit the database in tests and verify what went in there (probably with ActiveRecord calls). However, you could setup the same kind of mock heavy test there as well. I would not advise doing so though as it tends to lead to brittle tests.

    In the case of wanting to provide a different kind of logger in a different system then the general approach would be to do some dependency wiring in a configure method of the class in question. The ‘Rails way of doing things’ would dictate that this dependency wiring and configure method calling be optional if at all possible…sane defaults and all that.

    James

  7. I use constructor injection with a default argument, so that I can override the dependency for testing, but it keeps the code easier to read than injecting the dependencies in all the way from the top of the app. Middle ground!

  8. As a former long-time C++/C# dev, I can appreciate the sentiment toward DI, but I’m not convinced it’s needed in dynamic languages like Ruby.

    However, when I find that I wish my tests could inject a dependency, I almost always find that I can use mocks and expectations instead. Sometimes I just needed to refactor my code to make that more convenient, which is a good thing anyway.

    So I believe we should not be too quick to start using the same approach in Ruby that we’re forced to take in static languages like Java. The Ruby language already provides ways to implement the same patterns without having to dirty up every class with artificial constructors, etc. That just adds noise to your code and forever introduces a new layer of complexity that can never be designed away later.


  9. nick

    @James: Thanks, I like your example of specifying expectations. However, how does your testing framework mock the logger instance? It must somehow access and replace the object, which in your case happens through the global logger instance. With DI, this wouldn’t be necessary as the test would inject a mocked logger instance.

    @Javier: Welcome back, bro! Default args work and I don’t have a problem with the class having some default behaviour. It should be easy, though, to inject another instance from outside, which is usually not the case in Rails.

    @Jeff: Ok, I get your points but I think DI doesn’t get useless just because the type system is dynamic. And as I said two comments earlier, the testing/mocking fw still needs to access whatever you wanna mock. This could happend by setter DI if you don’t like the long constructors (neither do I). Pushing all that setup code (like logger instantiation) into one single class (aka the Rails controller) makes me think of the blob anti-pattern.

  10. So the claim is that DI removes the dependency of one component on another. But I contend that in fact, you’ve INCREASED the number of dependencies. In your example, the callee still depends on a logger — it’s just that you’re able to switch out WHICH logger it uses. But now the caller also has a direct dependency on a logger. When writing the caller, I now have to decide what kind of logger I want the callee to use, and have to create it myself. That’s extra stuff that I don’t want to have to think about when all I really want is for the callee to do its job.

    Providing a default argument for the logger in the constructor helps, but not enough. What happens when the callee needs a logger and 4 other dependencies? A better way is to have an optional “options” argument, passing in a hash, so I can say :logger => ArrayLogger.new without having to worry about passing in the other dependencies. This is more idiomatic Ruby, as far as dependency injection goes, and feels so natural that we typically don’t even think that it’s special enough to be thought of as a pattern. (Just like we don’t think of function/method calls as patterns these days, unless we’re writing in machine language.)

    But even this is often too much, as we apply the YAGNI principal. Until we come up with a case where we need a class to be able to have its own local logger, we’re not going to bother writing code to allow swapping it out. Most often in Java, it’s the tests that require swapping out the dependency, but Ruby is dynamic enough that we don’t need to swap the object out, since we can usually just stub/mock out a single method of the dependency.


  11. nick

    @Craig: Of course the callee depends on a logger! Since it is its intention to log something, a responsible instance to do so is needed. However, DI moves the conception of that instance to the outside, which removes the dependency in the callee (I like the term callee, thanks!).

    When doing so, it is completely open to you how you inject objects into another instance (options hash, default args, etc),

    However, I absolutely disagree on your YAGNI point. DI forces you to think about both caller and callee (I still love the term, isn’t that great?) as well as it forces you to think about separating concerns into different classes. I agree that you don’t need to apply that concept everywhere, but DI applied right makes your code easier to test and easier to follow, which is a good thing. Thanks for your comment, Craig!

  12. [...] 事实上,你要解决的问题也许并不是你想像的那样例外。你思想里的这种拜占庭模式只是远古时代那些使用跟Ruby类似语言的人留下来的遗产。 [...]

  13. [...] 事实上,你要解决的问题也许并不是你想像的那样例外。你思想里的这种拜占庭模式只是远古时代那些使用跟Ruby类似语言的人留下来的遗产。 [...]

  14. [...] 事实上,你要解决的问题也许并不是你想像的那样例外。你思想里的这种拜占庭模式只是远古时代那些使用跟Ruby类似语言的人留下来的遗产。 [...]

  15. [...] 事实上,你要解决的问题也许并不是你想像的那样例外。你思想里的这种拜占庭模式只是远古时代那些使用跟Ruby类似语言的人留下来的遗产。 [...]


  16. EdCh

    I’m a Ruby/Rails newby, so excuse the novice question. If I want to use DI in Rails how do I get Rails to inject my dependencies into my Controller? Wouldn’t one need to wire up DI into the Rails mapping of routes to controllers?


  17. nick

    EdCh: You usually wouldn’t inject stuff into your controller but in your domain objects (or, in a more railsish environment, your AR models).

    However, you’re right, there’s need for a dispatching instance that injects the right concerns into the right objects.

Leave a Reply