Introduction to Cells: A Better View Layer for Rails

Share this article

Introduction to Cells: A Better View Layer for Rails

“No! I did it again! Don’t kill me! Please! Dooonn’t!” A normal day at a random Rails shop. One developer, Scott, just wanted to run a simple database migration. Not only did he update the database schema, he also sent half a million “Welcome” emails to the existing users of the app.

Scott forgot about the after_save callback in his models that got triggered by his code. Scott lowers his head in shame, and grabs a glass of water to gulp down his anger.

Scott, it is not only your fault.

More and more Ruby on Rails developers are struggling to implement complex web applications following the vanilla Rails Way™. They are looking for alternative patterns, techniques or approaches to control growing complexity.

One take on alternative patterns is the Trailblazer project, that’s a collection of layers sitting on top of existing web frameworks such as Rails, Grape, or Hanami.

In this series of posts I want to introduce you to those alternative patterns, their integration into real applications, and advanced techniques that will help you coping with complex architectural problems you encounter every day in Rails.

Speaking of problems – there are many in Rails and the applications using it.

And the biggest one is clearly the lack of abstraction layers, manifested in the reoccurring question “Where do I put this code?” Rails developers like Scott all over the world ask themselves, with a desperate look on their faces and cold sweat on their forehead.

Let’s focus on how to improve this framework. Why not start with the view layer?

Rails’ View Layer: The Presentation Dinosaur

Rails claims to be very simple, which allows to explain the view layer briefly.

A controller action aggregates data for presentation. Data often is assigned to instance variables, and those are then pushed to the so called “template”.

Templates are files written in languages such as Haml or ERB. They contain placeholders that are replaced by the controller’s data, turning the whole thing into static HTML.

Rendering a template from a controller is called a view. To provide a convenient way to reuse certain fragments in a view, Rails offers you partials that are exactly like views but only implement a small part of the page. Partials can then be rendered from within a view.

Rails also allows the encapsulation of Ruby code into methods called “helpers” that can be used in the view to reduce logic in the view.

This is because you are not supposed to have logic in your views – and this is emphasized everywhere in Rails. “Do not have complicated code in your views!” will be the tenor. The funny thing is, in most Rails views you will find massive code chunks, making it incredibly hard to understand what a view actually does.

Now, why is that?

Rails Views == PHP 4

The view layer in Rails is strongly inspired by PHP. According to the charismatic inventor, this is on purpose and brings all the good things from PHP while leaving out the bad parts.

This is very true. True when comparing Rails views with a PHP 4 application built 10 years ago.

And that’s exactly what Rails views are: PHP scripts with zero encapsulation, accessing global state and global functions called helpers. Those of you who have worked in antiquated PHP 4 applications will see the similarity of both concepts.

There is nothing wrong with keeping things simple. It is a concept new developers instantly understand and empowers them to implement dynamic web pages. Nevertheless, when an abstraction starts leaking and developers struggle to maintain clean, predicable view code, isn’t that a sign to introduce higher-level concepts?

View Models To The Rescue!

Every web framework other than Rails out there, like Django, Symfony, or Phoenix, comes with a certain form of view models: an abstraction where a separate object represents a fragment of your page – instead of relying on a globally acting “PHP script”.

Why don’t we have this in Rails? Because we don’t need it!

But didn’t I just say the opposite?

Yes, I did. Nevertheless, Rails core still makes people like Scott think that its view layer is absolutely sufficient. Instead of integrating a higher abstraction, the introduction of a global ApplicationController::render method in Rails 5 is a desperate attempt to revive Rails’ Jurassic view layer.

Soon, we will have hundreds of different implementations for “decorator” or “presenter” objects floating around in our beautiful MVC stack or, even simpler, render views straight from models! A horrifying thought and totally defeating the purpose of a convention-driven framework.

And in the far distance, on the horizon, where a storm is gathering, and black clouds start hiding the sky, you can hear the last roar of the dying ActionView T-Rex.

Once the rain ends, a ray of sun hits the wet ground. The alternative view layer for Rails is called Cells. It has matured over many years, having been first introduced with Rails 1.2.3.

The Cells gem is about to crack one million downloads for many happy users. It implements a helpful variation of the view model pattern that allows you to map HTML fragments to objects.

Anatomy Of A Cell

So what is a cell? A cell is an object that renders a view fragment. Nothing more.

People new to Cells often struggle to understand what a cell’s job is, though. Is it supposed to render the entire page, or only some components in the view, or what’s the deal with Cells?

The answer is as simple as the gem’s concept: Cells can render anything you want. They can be used to embrace a sidebar, a login form, or an entire page. Or, all of the above, by nesting your cells!

Many developer teams use cells to render pages, and in those pages, more cells implement smaller components. Having worked with numerous teams, I’ve often heard people saying “Ah, this is a bit like a React component in Ruby!”. Even though Cells doesn’t bring you interactivity, this is a nice comparison.

Another nicety is that you can render cells just anywhere. Mostly, this will happen in a controller action, in a view, or in mailers. The cell itself has no knowledge about the outer world and every dependency has to be handed into the cell. The result is a very robust and reusable component that is absolutely not to be compared with Rails 5’s new global render feature.

To give you a primitive example, here’s how you would render a collection of comments in a Rails controller view using a cell:

app/views/comments/index.html.haml

%h1 All comments

%ul
- @comments.each do |c|
  %li
    = cell(:comment, c)

Ironically, a cell can be rendered using a helper, the cell helper. By looping over the collection of comments and calling the cell for each model, we compile a HTML list of comments.

So, invoking a cell from a view boils down to the following code:

comment = Comment.new(body: "Fantastic!")

cell(:comment, comment) #=> "<div>... HTML"

Internally, this helper call literally does one single thing.

CommentCell.new(comment).call

Interesting. It constantizes the cell class name to CommentCell, instantiates an object, passes in the comment model, and invokes the cell’s rendering via the call method. The cell will return an HTML fragment, and we’re done.

Cells Are Objects

To fully understand view models, we need to take a stab at the class implementing the cell. And that class is where you, dear Scott, will spend half of your time when writing cells.

Per convention, cells are organized in a new app/cells directory.

app/cells/comment_cell.rb

class CommentCell < Cell::ViewModel
  def show
    "Hello! I feel #{model.body}"
  end
end

When invoking the cell via the cell helper, its call method will be called, which in turn, and now don’t lose me, calls the show method automatically.

Per convention, the show method returns the cell’s content, or the fragment the cell represents. In the above class snippet, all this method does is return a string. In the string, a mysterious model method is used. This is simply the comment you passed into the cell call earlier.

Let’s put these two ends together. Here’s the top-most call, again, along with the result:

comment = Comment.new(body: "Fantastic!")

cell(:comment, comment)
#=> "Hello! I feel Fantastic!"

Does that make sense? The cell helper will instantiate the CommentCell, pass in the comment object, and call the cell’s show method. In the cell, whatever you passed in is available via the model method. The return value of show is what the cell “looks like”.

You might think that this is a lot of code for what could have been done with a simple Rails helper function, but let me whet your appetite by discussing how handy cell views are as compared to partials.

Views

Instead of returning a plain string, you can also use a view template with placeholders, exactly the way Rails does it:

app/cells/comment_cell.rb

class CommentCell < Cell::ViewModel
  def show
    render # renders app/cells/comment/show.haml
  end
end

Using render, the Cells rendering stack will be invoked. Note that this is a completely separate implementation, not sharing any logic with ActionView. Cells’ rendering stack is actually around 50 lines of code. And that, as compared to ActionView’s 7,000 lines, slightly explains why cells are up to 10x faster than a conventional T-Rex.

You might have guessed it already. When calling render, the cell’s very own view app/cells/comment/show.haml is rendered.

Given we were using Haml as our template choice, here’s what the show.haml view could look like.

# app/cells/comment/show.haml

%h1 Comment
= model.body
.author
  = link_to model.author.name, model.author

This is a plain Haml template the way you’ve written them many times before. However, while Rails views reside in a global directory, this cell view sits in its private app/cell/comment directory. Also, cell views drop the redundant .html name part. A cell class always renders one format exclusively, making it unnecessary to encode that in the filename – a convenience appreciated by many Cells users.

On a side note, Cells supports Haml, Slim, and ERB. Make sure to quickly read the installation instructions and never forget to include the cells-haml gem in your Gemfile if you use Haml, like me.

Back to the view file. As you will no doubt have noticed, you can use Rails helpers like link_to. Also, you have access to the cell’s model. However, there’s no instance variables in this view. Scott! Do you miss them? You don’t have to! The cell provides instance methods to access presentation data. Let’s learn about that a bit more.

Logicless Views

The real power of Cells comes with the way “helpers” work. The above view can be simplified as follows:

app/cells/comment/show.haml

%h1 Comment
= body
.author
  = author_link

This is what we call a clean, logicless view. Let that sink in for a few seconds and appreciate the beauty, aesthetics, even elegance, of simple, logicless views before talking about the remaining implementation.

Minutes pass, and Scott is still staring at the view template. No logic. No clumsy helper calls bloating the view. Just plain templates. The thought of the ActionView T-Rex and its horrifying appearance pulls Scott back into reality.

Helpers Are Instance Methods

What happens in the view when we call author_link? In Rails, this would invoke a – hopefully existing – global helper, somewhere. Where exactly, nobody really understands. Let’s forget about Rails, helpers, and stinky T-Rexes for now.

When invoking author_link in a cell view, this method will be called directly on the cell instance. Have a look at the implementation and it will all make sense.

app/cells/comment_cell.rb

class CommentCell < Cell::ViewModel
  def show
    render # renders app/cells/comment/show.haml
  end

private
  def author_link
    link_to(model.author.name, model.author)
  end
end

That’s right, a “helper” in Cells is an instance method, bound to one specific class, and not a global, stomping monster. Within that method, you can use any Ruby you want, even Rails helpers.

But this time, you won’t have name clashes, you won’t have access to global variables that you might not even want to see, and you won’t have to artificially encapsulate your “helper”! Ruby and its object model does it for you. Even cooler, you’re free to use OOP features like inheritance, modules to share common helpers, or decorators as seen in the Draper gem within cells.

Speaking of decorations: Cells also provides a quick way to generate readers to the model object. This is a convenient way to shorten code.

app/cells/comment_cell.rb

class CommentCell < Cell::ViewModel
  property :body
  property :author
  # ..

private
  def author_link
    link_to(author.name, author)
  end

Using the property class method simply generates a shortcuts to model, allowing you to call body, or author directly. And, since it’s one and the same context, you can use these shortcuts in both cell instance and view.

Wrap Up

You now understand why dinosaurs must die. By introducing a new abstraction layer, the cell, along with a slightly extended file structure, it is possible to have fully stand-alone view components that can be reused without any pain.

Gone are the days of accidentally overriding helper functions or digging through nested partial calls. Cells implements the same behavior with a very functional semantic. State and dependencies have to be passed into the view model from the outside.

In the same sense, it uses object-orientated techniques where they make sense. Calling methods, or “helpers”, in the view will be delegated to the cell instance, where you have to provide an instance method to implement that very helper.

While you might still be reading and hoping for some more advanced examples, Scott is already absorbed in his code base, replacing helpers and partials with view models.

In the next episode, we’ll jump into testing cells, nesting and rendering collections. Another appetizer: I will discuss how view inheritance works and helps to save code.

Don’t wait until then, do it like Scott does, go and use Cells right now and feel the power of encapsulation in your view layer!

Frequently Asked Questions about Cells: A Better View Layer for Rails

What makes Cells a better view layer for Rails?

Cells provide a more organized and encapsulated approach to building view components in Rails. Unlike traditional Rails views, which can become cluttered and difficult to manage, Cells allow you to encapsulate a piece of a page into a reusable and testable object. This makes your code cleaner, more maintainable, and easier to understand.

How do I install and use Cells in my Rails application?

To install Cells, you simply need to add the gem to your Gemfile and run the bundle command. Once installed, you can create a new cell by running the rails g cell command followed by the name of the cell. The cell will be created in the app/cells directory, and you can define its view and logic within the cell class.

Can I use Cells with other Rails view templates like Haml or Slim?

Yes, Cells are compatible with all major Rails view templates including ERB, Haml, and Slim. You just need to specify the template engine when you create the cell.

How do I test Cells in Rails?

Cells come with a built-in testing framework that allows you to write unit tests for your cells. You can test the output of your cells, as well as any state changes that occur within the cell.

Can I use Cells with Rails engines?

Yes, Cells can be used with Rails engines. You just need to make sure that the engine’s views directory is included in the cell’s view paths.

How do I handle complex view logic with Cells?

Cells encourage you to keep your view logic simple and encapsulated within the cell. However, for more complex logic, you can use the concept of “cell states”, which allow you to define different states for a cell and render them based on certain conditions.

Can I use Cells with Rails’ asset pipeline?

Yes, Cells are fully compatible with Rails’ asset pipeline. You can include any assets required by the cell in the cell’s view directory, and they will be automatically included in the asset pipeline.

How do I handle forms with Cells?

Cells can handle forms just like regular Rails views. You can use the form_for or form_with helper methods within the cell’s view to create a form.

Can I use Cells with Rails’ caching mechanisms?

Yes, Cells support Rails’ caching mechanisms. You can use the cache method within the cell to cache the output of the cell.

How do I handle AJAX requests with Cells?

Cells can handle AJAX requests just like regular Rails views. You can use the respond_to method within the cell to handle different formats, including AJAX requests.

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 Rails
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week