Ruby
Article

Introduction to Cells: A Better View Layer for Rails

By Nick Sutterer

“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!

  • Andrew Garshyn

    It’s interseting how you deal with css styling. Is there a way to encapsulate it there too?
    If you use div with “author” class you might want to reference it in css.
    Thanks for the article!

  • boriscy

    I’m sure that cells really helps for more complicated examples, can u point me one that has a more complicated view?

    • apotonick

      The next post is going to cover a bit more complex example. Thanks for the feedback!

      • slip.08

        when do you write a second article?

        • apotonick

          I am on it and it will be out within the next 10 days! Thanks for asking!

  • markbrown4

    I disagree with most of the opinion points raised in this article. Rails dev’s aren’t plagued by asking “Where do I put this code?”, the conventions answer this clearly.

    Vitriol aside you’re making two points, that global helpers are bad and that a ViewModel is a good thing. Global helpers can be bad, and a ViewModel may be required. I can see cases where cells would help but if it were the only way to do things I wouldn’t enjoy it.

    • apotonick

      Thanks Mark! I’ve heard that a lot, and have to mildly disagree. The conventions in Rails are limited to table names, class names, a superficial file structure and two buckets (M and C) where you can put all your code.

      If you find that sufficient, and are happy with your Vanilla Rails code, then I’m happy, too, and you shouldn’t use anything else.

    • Nick

      Mark, you’re right, the conventions do answer it clearly, and I think that’s why the article starts off talking about callbacks – it’s a strong convention in Rails. Want to notifiy a user of something after an update? Set a callback to start a background job.

      I was mildly offended the first time the first time I read @apotonick:disqus’s opinion on things like callbacks. They had made my life so much easier up to that point. Until one day I found myself with no option but to Google “disable ActiveRecord callbacks” to ease some weird testing headaches. Turns out it’s possible but it just felt… wrong. Dependency injection and stubbing out APIs – that makes sense. Turning off part of a class’s functionality so I can test it properly? Code smell.

      Coding often makes most sense with real world parallels (to me anyway). If I went to HR and said “hey, as soon as you pay me my salary each month, send a message to my PA that’ says ‘pay the landlord the rent'” they’d look at me like I’ve lost it because it has nothing to do with them. And rightly so – even if they agreed, it would be a bad idea. Callbacks are not a bad idea, but they’re usually used poorly.

      Conventions are not a bad thing per se, but they are not the panacea many make them out to be. Same with abstractions – neither bad nor good. They’re both just tools with appropriate and inappropriate applications, and both have pros and cons. The problem I have with the conventions is one big downside (as I see it) – they often lead to devs NOT occasionally being plagued by the question “where should I put this code?”. Instead – usually egged on by the Rails guides or some tutorial – they just dump code in tangentially related places (usually the model) where it will cause pain in future.

      Basically, for me, blind trust in conventions prevents developers growing their skill set and experience by investigating other ways of doing things. It did for me. It’s not the conventions’ fault – it’s the way we promote them as the solution to everything and anything that challenges them as something that doesn’t belong in Ruby development.

      • markbrown4

        I wasn’t referring to callbacks on models, it’s well known that you need to be careful with them. I was referring to this:

        – Scott, it is not only your fault.
        – Speaking of problems – there are many in Rails and the applications using it.
        – And the biggest one is clearly the lack of abstraction layers
        – The Presentation Dinosaur
        – in most Rails views you will find massive code chunks
        – 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.
        – instead of relying on a globally acting “PHP script”.
        – ApplicationController::render method in Rails 5 is a desperate attempt to revive Rails’ Jurassic view layer.
        – A horrifying thought and totally defeating the purpose of a convention-driven framework.
        – you can hear the last roar of the dying ActionView T-Rex.
        – You now understand why dinosaurs must die.

  • brauliobo

    I would rather go to Riot.js or React for better views

    • apotonick

      Or use Phoenix or Django, if you want a better Rails?

      I agree, though, if you have a SPA frontend, I would (and actually do) totally go for React or a similar JavaScript frontend. Cells is made for server-side generated views.

  • Geoffroy Planquart

    Thanks for the article, really interesting, however I’d find it rather strange to have to name my cells differently depending on the format I want… The part of my app which have to render a view (or even a document to upload to a server) does not have to care wether it’s text, html, xml, or even PDF !

    • apotonick

      Not sure if I understand that correctly, but regardless whether or not you have to name your cells after the format, there will always be a point where you have to dispatch to a certain format.

      UniversalCell#to_json
      UniversalCell#to_html
      UniversalCell#to_pdf

      Here, the “name” is encoded in the public method name.

      JSONCell#call()
      HTMLCell#call()
      PDFCell#call()

      And here, it’s reflected in the class name (which is what you dislike, I guess?). Is that what you mean?

      What I’m trying to say is: hiding the format in different public method calls boils down to the same API as having different objects with just one public method.

      • Geoffroy Planquart

        I see what you mean and I agree with you up to a certain point. In ActionView, the format is specified using the file extension “.format.handler”, which is not at all equal to just “.handler” (or .erb which is just a special case).

        Erb files for example could be anything from YAML to HTML, including JS,CSS,LaTeX… So specifying .erb as the format is not enough.

        Concerning the way you choose what format you want, you may :
        – respond with different format indifferently depending on which one is available
        – need not to know what is the application’s default format in a controller
        – don’t want to case/when 15 lines at the end of every action to decide what Cell to use, but rather just know you have to render this kind of or the view named X, and the request or application preference choose for you

        However, it’s true that this kind of functionality adds a lot of line of code on ActionVIew, and that it could be greatly simplified using view-models instead of assigns and global helpers…

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.