Cells: A Deeper Look into Dependency Injection and Testing

Share this article

Cells: A Deeper Look into Dependency Injection and Testing

In my previous post, you and Scott learned the basic of Cells, a view model layer for Ruby and the Rails framework.

Where there used to be stacks of partials that access controller instances variables, locals, and global helper functions, there’s now stacks of cells. Cells are objects. So far, so good.

Scott now understands that every cell represents a fragment of the final web page. Also, the cell helps by embracing the logic necessary to present that very fragment and by providing the ability to render templates, just as we used to do it in controller views.

Great, but what are we gonna do with all that now?

Back in the days when Cells was a very young project, many developers got intrigued by using cells for sidebars, navigation headers, or other reusable components. The benefit of proper encapsulation and the reusability coming with it is an inevitable plus for these kinds of project.

However, cells can be used for “everything”, meaning they can replace the entire ActionView render stack and provide entire pages, a lot faster and with a better architecture.

This intrigues Scott.

A User Listing

Why not implement a page that lists all users signed up for Scott’s popular web application?

In Rails, this feature normally sits in the UsersController and its #index action. Instead of using a controller view, let’s use Cells for that.

Page Cell Without Layout

For a better understanding, we should start off with the UsersController and see how a page cell is rendered from there.

Please note that we still use the controller’s layout to wrap around the user listing. That’s because we want to learn how to use Cells step-wise. Scott likes that. But Scott needs to keep in mind that Cells can also render the application layout, making ActionView completely redundant! This we will explore at a later point.

class UsersController < ApplicationController
  def index
    render html: cell("user_index_cell"), layout: true
  end
  ...

All the controller does is rendering the UserIndexCell that we have to implement now. Did you notice that there’s no model passed into the cell call? This is because cells can aggregate data on their own, if desired. We’ll shortly learn what’s the best way of handling dependencies.

Using render with the :html option will simply return the passed string to the browser. With layout: true it – surprisingly – wraps that string in the controller’s layout.

This is all Rails specific. Now, let’s get to the actual cell. The new UserIndexCell would go into app/cells/user_index_cell.rb in a conventional setup:

In Trailblazer, cells have a different naming structure and directory layout. Scott’s user cell would be called User::Cell::Index and sit in app/concepts/user/cell/index.rb, but that’s a whole different story for a follow-up post.

class UserIndexCell < Cell::ViewModel
  def show
    @model = User.all
    render
  end
end

With the introductory post in the back of your head, this doesn’t look too new. The cell’s show method will assign the @model instance variable by invoking User.all and then render its view.

Iterations in Views

In the view, we can use the user collection and display a nicely formatted list of users. In conventional Cells, the view resides in app/cells/user_index/show.haml and looks as follows:

%h1 All Users

%ul
  - model.each do |user|
    %li
      = link_to user.email, user
      = image_tag user.avatar

Since we assigned @model earlier, we can now use Cells’ built-in model method in the view to iterate over the collection and render the list.

Scott, being a dedicated and self-appointed software architect, narrows his eyes to slits. Imaginary tumbleweed passes behind his 23″ external monitor. Silence.

There’s two things he doesn’t like right now:

Why does the cell fetch its model? Couldn’t this be a dependency passed in from the outer world, such as, the controller?

And, why is the cell’s view so messy? Didn’t we say that cell views should be logicless? This looks just like a partial from a conventional Rails project.

You’re right, Scott, and your architect intuition has led you to ask the right questions.

It’s not good practice to keep data aggregation knowledge in cells, unless it really makes sense and you understand your cell as a stand-alone widget.

External Dependencies

Whenever you assign @model you must ask yourself: “Wouldn’t it be better to let someone else grab my data?”. Here’s how that is done in the controller:

class UsersController < ApplicationController
  def index
    users = User.all
    render html: cell("user_index_cell", users), layout: true
  end
  ...

Now it’s the controller’s responsibility to find the appropriate input for the cell. Even though Rails MVC is far from the real MVC, this is what a controller is supposed to do.

We can now simplify the cell, too:

class UserIndexCell < Cell::ViewModel
  def show
    render
  end
end

Remember, the first argument passed to cell is always available as model within the cell and its view. Please don’t get confused with the term “model”, though. Rails has misapprehended us that a “model” is always one particular entity. In OOP, a model is just an object, and in our context, this is an array of persistent objects.

Let’s see how we can now polish up the view and have less logic in it. The next version of it is going to use instance methods as “helpers”. A bit better, but not perfect:

%h1 All Users

%ul
  - model.each do |user|
    %li
      = link user
      = avatar user

Instead of keeping model internals in the view, two “helpers” link and avatar now do the job. Since we’re iterating, we still have to pass the iterated user object to the method – a result of a suboptimal object design we will soon fix.

Helpers == Instance Methods

In order to make link and avatar work, we need to add instance methods to the cell:

class UserIndexCell < Cell::ViewModel
  def show
    render
  end

private
  def link(user)
    link_to(user.email, user)
  end

  def avatar(user)
    image_tag(user.avatar)
  end
end

All presentation logic is now nicely encapsulated as instance methods in the cell. The view is tidied up, sort of, and only delegates to “helpers”.

Well, sort of, because neither does Scott like the explicit passing of the user instance to every helper, nor is he a big fan of the manual each loop. He scrunches up his nose…there must be a better way to do this.

Nesting Cells

In OOP, when you start passing around objects in succession, it often is an indicator for a flawed object design.

If we need to pass a single user instance to all those helpers, why not introduce another cell? This cell has the responsibility to present a single user only, and embrace all successive helper calls in one object?

Scott’s puts on his white architect hat, again. “Yes, that sounds like good OOP.”

The logical conclusion is to introduce a new cell for one user. It will live in app/cells/user_index_detail_cell.rb.

We all know, that name is more than odd and a result of Rails’ missing convention of namespacing. Let’s go with it for now, but keep in mind that the next post will introduce Trailblazer cells, where namespaces and strong conventions make this look a lot more pleasant:

class UserIndexDetailCell < Cell::ViewModel
  def show
    render
  end

private
  def link
    link_to(model.email, model)
  end

  def avatar
    image_tag(model.avatar)
  end
end

We removed link and avatar from UserIndexCell (yes, delete that code, good-bye) and moved it to UserIndexDetailCell. Since the latter is supposed to present one user only, we can safely use model here and do not need to pass anything around.

Here’s the view in app/cells/user_index_detail/show.haml – again, Scott, bear with me. The next post will show how this can be done in a much more streamlined structure:

%li
  = link
  = avatar

Scott loves this. Simple views can’t break, can they?

Now that we have implemented two cells (one for the page, one per listed user), how do we connect them? A simple nested cell invocation will do the trick, as illustrated in the following app/cells/user_index/show.haml view:

%h1 All Users

%ul
  - model.each do |user|
    = cell("user_index_detail", user)

Where there was the hardcoded item view, we now dispatch to the new cell. As you might have guessed, this new detail cell is really instantiated and invoked every time this array’s iterated. And it’s still faster than partials!

Do not confuse that with helpers, though. The detail cell does not have any access to the index cell, and visa-versa. Dependencies have to be passed around explicitly, no cell can access another cell’s internals, instance variables or even helper methods.

Anyway, rendering collections is something the Cells authors have thought about already.

Rendering Collections

Cells provides a convenient API to render collections without having to iterate through them manually. Scott likes simple APIs as much as he adores simple, logicless views:

%h1 All Users

%ul
  = cell("user_index_detail", collection: model)

When providing the :collection option, Cells will do the iteration for you! And, good news, in the upcoming Cells 5.0, this will have another massive performance boost, thanks to more simplifications.

Scott is very happy about his new view architecture. He has a sip of his icey-cold beer, a reward for his hard-earned thirst, and freezes. No, it’s not the chilled beverage that makes him turn into a pillar of salt. It’s tests! He has not written a single line of them.

Testing Cells

Cells are objects and objects are very easy to test.

Now, where does one start with so many objects? We could start testing a single detail cell, just for the sake of writing tests. Scott prefers Minitest over Rspec. This doesn’t mean Scott wants to start another religious war over test frameworks, though.

A cell test consists of three steps:

  1. Setup the test environment, e.g. using fixtures.
  2. Invoke the actual cell.
  3. Test the output. Usually, this is done using Capybara’s fine matchers.

Speaking of Capybara, in order to use this gem properly in Minitest, it’s advisable to include the appropriate gem in your Gemfile:

 group :test do
  gem "minitest-rails-capybara"
  ...
 end

In test_helper.rb, some Capybara helpers have to be mixed into your Spec base class. This is to save Scott a terrible headache, or even a migrane:

Minitest::Spec.class_eval do
  include ::Capybara::DSL
  include ::Capybara::Assertions
end

Now for the actual test. This test file could go in test/cells/user_index_detail_test.rb.

class UserCellTest < MiniTest::Spec
  include Cell::Testing
  controller UsersController

  let (:user) { User.create(email: "g@trb.to", avatar: image_fixture) }

  it "renders" do
    html = cell(:user_index_detail, user).()

    html.must_have_css("li a", "g@trb.to")
    html.must_have_css("img")
  end
end

This is, if you have a closer look, really just a unit test. A unit test where you invoke the examined object, and assert the side effects.

The side effects, when rendering a cell, should be emitted HTML, which can be easily tested using Capybara. Scott is impressed.

Cell Test == Unit Test

The fascinating fact here is that no magic is happening anywhere.

Where a conventional Rails helper test and its convoluted magic can trick you into thinking that your code’s working, this test will break if you don’t pass the correct arguments into the cell.

You have to aggregate the correct data, instantiate and invoke the object, and then you can inspect the returned HTML fragment.

Scott scratches his head. He now understands what a cell test looks like. Invocation and assertion is all it needs. However, does it make sense to unit-test every little cell? Wouldn’t it make more sense to test the system as a whole, where we only render the UserIndexCell and see if that runs?

Correct, Scott.

As a rule of thumb, start testing the uppermost cell and try to assert as many details from there as possible. If composed, nested cells yield a high level of complexity, then there’s nothing wrong with breaking down tests to a lower level.

The benefit of the top-down approach is, when changing internals, you won’t have to rewrite a whole bunch of tests. Does this feel familiar from “normal” OOP testing? Yes it does, because cells are just objects.

Here’s how a complete top-bottom test could be written. Instead of worrying about internals, the index cell is rendered directly:

it "renders" do
  html = cell(:user_index, collection: [user, user2]).()

  html.must_have_css("li", count: 2)
  html.must_have_css("li a", "g@trb.to")
  html.must_have_css("li a", "2@trb.to")
  # ..
end

Note how we now render the user collection, and as a logical conclusion, assert an entire list, not just a single item.

Testing view components is no pain. The opposite is the case: it’s identical to using a cell. This behavior comes for free when you write clean, simple objects with a well-defined API.

With a few Capybara assertions, you can quickly write tests that make sure your cells will definitely work in production, making your view layer rock-solid.

What’s Next?

We’re set to write cells for all the small things, embrace them as collections with any level of complexity, and, the most important part, test those objects so it won’t break anymore.

In the next post we will discuss some expert features of Cells, such as packaging CSS and Javascript assets into the Rails assets pipeline, view inheritance, caching, and how Trailblazer::Cell introduces a more intuitive file and naming structure.

Well done, Scott. Keep those objects coming!

Frequently Asked Questions about Dependency Injection and Testing in Ruby

What is the basic concept of dependency injection in Ruby?

Dependency Injection (DI) is a design pattern that allows us to remove hard-coded dependencies and make our applications more flexible, efficient, and modular. It’s all about removing dependencies from your code. In Ruby, this is achieved by passing the dependency (a service) into the client that would use it. It promotes code reusability and testability.

How does dependency injection improve code testing?

Dependency Injection makes unit testing easier. The ability to inject mock objects into a class allows for the testing of how a class behaves in different scenarios, without needing to set up complex test environments. This makes your tests cleaner, more flexible, and easier to maintain.

How does dependency injection relate to the SOLID principles in Ruby?

Dependency Injection is closely related to the SOLID principles of object-oriented design and architecture. It directly relates to the Dependency Inversion Principle (DIP), which states that high-level modules should not depend on low-level modules, but both should depend on abstractions. DI allows us to adhere to this principle by decoupling dependencies and making them interchangeable.

What are the different types of dependency injection?

There are three types of dependency injection: constructor injection, setter injection, and interface injection. Constructor injection is when the injector supplies the service to the client at the creation time. Setter injection is when the injector method injects the dependency to the client after the client is created. Interface injection is when the injector uses an interface to provide the dependency to the client.

How does dependency injection work in Laravel?

Laravel’s service container is a powerful tool for managing class dependencies and performing dependency injection. It provides a unified API to bind various classes into the container, and it can automatically resolve dependencies when a class is “made” from the container.

How can I implement dependency injection in Ruby on Rails?

In Ruby on Rails, you can implement dependency injection through several ways, such as using constructor injection or setter injection. Rails also provides a way to auto-load classes and modules, which can be used to manage dependencies.

What are the benefits of using dependency injection?

Dependency Injection provides several benefits such as increased code reusability, improved code maintainability, and enhanced module testing. It allows for better decoupling of classes and their dependencies, and it can make a system more flexible, easier to debug, and easier to test.

Are there any drawbacks to using dependency injection?

While dependency injection provides many benefits, it can also lead to increased complexity and can make code harder to understand if overused. It’s important to use it judiciously and to ensure that it’s providing value to your application.

How does dependency injection relate to the concept of inversion of control?

Dependency Injection is a form of Inversion of Control (IoC). IoC is a design principle where the control flow of a program is inverted: instead of the programmer controlling the flow of a program, the external sources (like user input, services, etc.) that are being called do. Dependency Injection is a way to implement IoC, by giving the control of dependencies to a container or framework.

Can you provide a simple example of dependency injection in Ruby?

Sure, here’s a simple example of constructor injection in Ruby:

class Client
def initialize(service)
@service = service
end

def perform
@service.call
end
end

class Service
def call
puts "Service called"
end
end

service = Service.new
client = Client.new(service)

client.perform # Outputs: "Service called"

In this example, the Client class is dependent on the Service class. Instead of hardcoding this dependency, we inject it through the constructor. This makes our Client class more flexible and easier to test.

Nick SuttererNick Sutterer
View Author

Whenever Open-Source meets deep and profound debates about architecting software, and there's free beers involved, Nick Sutterer must be just around the corner. Say Hi to him, he loves people.

GlennGRuby on Railstrailblazer
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week