Cody A. Ray

Dependency Injection in Sinatra

November 8, 2013 · updated July 6, 2014

Dependency injection (DI) is a very common development practice in many languages, but its never been huge in Ruby. Part of that is because Ruby is dynamic enough that it doesn’t really need dependency injection like, say, Java. But I argue that Ruby can greatly benefit from DI. Do you use a singleton configuration object? Or worse, other singleton objects, especially those with mutable state?

def some_method(*args)<br />
  foo = MyApp.configuration.foo<br />
end<br />

Mutable singletons have ripple effects across the app and make it very difficult (and scary) to evolve. Even mostly-read configuration objects introduce tight and often invisible/forgotten coupling between objects. Have you ever written a Rackup file that conditionally decides which app to mount based on some configuration?

if configuration1<br />
  use AppMode1<br />
else<br />
  use AppMode2<br />
end<br />

The issue here is coupling between the routes/resources and the underlying services. Both apps must respond to the same requests, parse the arguments in the same manner, but may have inherently different behaviors. By the single responsibility principle, a standard resource layer should handle the routing and delegate the different service behaviors to a service layer. All of these are common in the Ruby community and, alas, poor development practices. But the beauty of Ruby is that the heavyweight DI containers that many languages need are overkill for most Ruby apps. Standard DI will solve these problems beautifully. So now you’re hopefully convinced that DI is the right thing to do. You may initially try

class MyApp < Stathub::Base<br />
  def initialize(service)<br />
    @service = service<br />
  end<br />
end<br />

but this will fail. If you realize how Sinatra is built atop Rack (e.g., by looking at the source), you realize that Sinatra apps are passed an “app” object as the first argument to the initializer. So, instead, you should do something like this:

class MyApp < Stathub::Base<br />
  def initialize(app, service)<br />
    super app<br />
    @service = service<br />
  end</p>

<p>get("/some/route/:arg1/:arg2") do |arg1, arg2|<br />
    @service.some_service(arg1, arg2).to_json<br />
  end<br />
end<br />

If you prefer to use a raw Rack app:

class MyRackApp<br />
  def initialize(app, service)<br />
    @app = app<br />
    @service = service<br />
  end<br />
end<br />

Now your Rackup file will look more like

service = MyService.configure(MyApp.configuration)<br />
use MyApp, service<br />

And that’s all there is to it. Go forth and inject your dependencies!

© 2009–2026 Cody A. Ray
RSSGitHubLinkedIn