Dependency Injection is not Scary! — Refactoring Rails the Clean Way

Elijah Goh
2 min readFeb 26, 2019

--

Yes it’s a stock image. And it’s PHP.

Hello! We’ll be looking into Dependency Injection (DI) and how it makes your code cleaner, and much more portable. And why DI isn’t as scary as you think.

After spending some time in Hanami Land, I’ve come to use a lot if dependency injection in the codebase and transferring the knowledge to our Rails codebase and appreciating the flexibility it brings.

What does Dependency Injection look like in Ruby?

I’m sure you may have used or stumbled across DI in Ruby. The most common is in your Mailers.

class UserMailer < ApplicationMailer
# Look, I injected a user!
def welcome(user)
@user = user
mail to: @user.email, subject: "Welcome new person!"
end
end

You must be thinking, DI is so easy, and yes it is. But we can expand on the concept and takes things a little further from here.

What else is there?

The advantage of DI is the flexibility from injecting objects rather than instantiating from within the class. Let’s look at a simple presenter to be used in a controller in an e-commerce codebase.

class ShopPresenter
attr_reader :shop, :products
def initialize(shop)
@shop = shop
@products = shop.products
end
def products_count
@products.count
end
end

This presenter looks pretty good, we can call products_count in our views to show how many products is presently in the shop page.

Now the scope changed and you are required to create a “Sold Page” to display products sold by the shop and both pages are pretty similar. We can create another presenter to do this but this is where DI comes in handy. Let’s refactor.

class ShopPresenter
attr_reader :shop, :products
def initialize(shop, products = [])
@shop = shop
@products = products || shop.products
end
...
end

With the updated code, we can inject products into the presenter in the controller to make it much more flexible, at the same time, fallback on the defaults.

And testing it becomes much simpler.

describe ShopPresenter do
let(:shop) { Shop.new(name: "Gift shop") }
let(:products) { [Product.new(name: "Thing 3000", price: 2999)] }
subject(:presenter) { ShopPresenter.new(shop, products) }
describe "#products_count" do
it "returns # of products" do
expect(presenter.products_count).to eq(products.count)
end
end
end

Other use cases of DI

DI is used in many parts of Rails codebase as well, an example include Rails cache, thus injecting the middleware depending on which cache storage is injected which contains same method name and can be used interchangeably.

Now go out there an DI!

--

--

Elijah Goh

Elijah is a programmer, gamer, and cat daddy, who enjoys writing once in awhile.