10 Points How Cells Improves your Rails Architecture

The Cells gem has been around in the Rails community for more than four years now. It has hundreds of pleased users who reported all kinds of use cases and how cells improved their apps. Here’s a compilation of the best practices.

The challenge

A shopping cart in a Rails app.

Usually you’d take a shared partial to display the cart in every controller.

%h1 Your cart

- if items.blank?
  = render :partial => "shared/cart/blank"

- else
  %ul
    - for i in items
      %li #{i}

Some view would render the cart somewhere.

 'shared/cart/cart',
  :locals => {:items => current_items} %>

1. Keep your views dumb

The render :partial looks harmless, and many Railers will argue “Well, you can put that in a helper.” – anyway, this little piece of code alreay contains a lot of knowledge. Too much.

That’s knowing

  • which items to display (current_items)
  • the exact location of the cart partial

A dumb view should not know anything about file locations at all.

 current_items %>

The CartCell we’re rendering hides any physical file location from us.

2. Don’t put logic into views

Yeah, a snippet like

- if items.blank?
  = render :partial => "shared/cart/blank"

- else
  ..

already contains decider logic – didn’t we learn something different from MVC?

class CartCell  :blank
end

The cell is supposed to render its display view. If no items were passed, it renders the blank view.

Any decider logic happens in a class, and not in some object-unoriented helper or partial.

3. Interfaces can save lives

So I used :locals in my example above. However, I often see controllers where people just set instance variables and use them throughout all partials.

  def index
    @items = current_items
  ..

The partial blindly accesses instance variables.

  %ul
    - for i in @items

Every controller using that partial has to know which instance variables it has to set before rendering. It’s only a matter of time until some controller renders a partial without providing the queried ivar – and your code crashes.

Cells by contrast force you to define which variables you wanna pass – making you writing interfaces without noticing it.

 current_items %>

4. Controllers should be slim

So instead of globally cluttering your controller with instance variables, put that into well-encapsulated cells.

class CartCell < Cell::Base
  def display
    @user = session[:user]
    
    render
end

Each cell has a limited scope not polluting anything except itself.

5. Avoid helpers

Helpers are plain modules. And can be pain.

What if the item list in the cart would be sortable? People implement that with helpers.

module CartHelper
  def render_cart_sorted
    order = params[:order] || :asc
    items = @items.sort(order)

    render :partial => 'shared/cart/cart', 
      :locals => {:items => items}
  end

This leads me to questioning myself “What is a helper? It’s not a view, but renders. It’s not a controller, but it aggregates data. What are helpers?”.

The answer: Helpers are shit. They completely break anything we learned from MVC and they are untestable.

class CartCell < Cell::Base
  def display
    setup!
    
    render
  end

  def setup!
    order = decide_order(params[:order])
    @items = @opts[:items].sort(order)
  end

  def decide_order(order)
    order || :asc
  end 
end

Here, sorting (or delegating sorting to the model) is the controller’s job. It’s duty is to process user gestures (user clicks on sorting icon) and to aggregate data (actually sorts data for presentation).

This is called V/C glue code as it lives between view and controller.

Under no circumstances should this be done in a helper.

6. Unit-test your glue code

Brrr, a shiver runs up and down my spine. I just imagined how you’d test this helper code.

With cells, you’d first assure that your sorting is doin’ right.

class CartCellTest < Cell::TestCase
  test "sorting should be doin' right" do
    assert_equal :asc,  cell(:cart).decide_order(nil)
    assert_equal :desc, cell(:cart).decide_order(:desc)
  end
end

Easy. What’s next?

7. Functional-test your rendering

We usually write functional tests when it comes to rendering markup. With helpers and partials this can be really painful.

Cells are made for heavy-duty testing.

class CartCellTest  "some bullshit"}
    
    invoke :display
    assert_select "ul li", 3
    ..
  end
end

Cells let you simply test what you just did – in a separate, well-encapsulated environment.

8. Get OOP back in your views

Unfortunately, the current Rails view layer discourages using modern concepts, like inheritance, encapsulation and OOP at all. Why?

Why should we have great models and sucky views?

Now imagine it’s getting christmas, and your cart should look christmessy. Like some snowflakes in the background and a smiling reindeer on the order button.

class XmasCartCell  'xmas'
  end
end

We simply inherit

  • the setup methods
  • the behaviour
  • the views

It’s just OOP, back in your views. No magic, no implicit syntax, just OOP from the book.

9. Teams love components

Mando works on the shopping cart, whereas Charles plays around with the bestseller cell. Both are working on features shown in the same controller.

However, Charles and Mando will never get into trouble with each other – both have a separate component which does not know anything about the outer world.

Mando is strictly bound to

app/cells/
         cart_cell.rb
         cart/
             display.haml
             order_button.haml

Charles codes somewhere else in

app/cells/
         bestseller_cell.rb
         bestseller/
                   list_latest.haml

They have separate tests, too. And when they think they’re stable they just plug the cells in the controllers and it will work.

Mando and Charles love components!

10. Hide your caching

The BestsellerCell needs a lot of computation. Who bought what, when and how often? Why not cache the view?

According to the Rails docs, partials do that with the following pattern.


  Our bestsellers:
   "bestseller",
    :collection => Bestseller.find(:all) %>

Caching is exposed to the action. The action has to know about the partial’s caching requirements. This is wrong.

The partial computing and rendering the list knows best about its needs. So put the caching where it belongs – to the partial!

class BestsellerCell  10.minutes
  
  def list
    @bestseller = Bestseller.find(:all)
    render
  end
end

When calling

render_cell :bestseller, :list 

nobody except the cell itself knows about the caching for that special page fragment. Even the expiry strategy is in the cell. This is called knowledge hiding.

More to come

The component-oriented approach with cells yields more improvements for your code. And there is more, yet. Interactive cells with AJAX often needed for rich client applications or dashboards – we got a cells-backed framework for that: Apotomo.

Go, try and love it.

Advertisement

12 thoughts on “10 Points How Cells Improves your Rails Architecture

  1. Excellent job with Cells Nick. I am again looking at it and I am starting to get more and more convinced that all of the Rails projects I’ve worked on should be using this mechanism.

    Like

  2. Cells seem quite cool.

    Does it work well with rails 3?

    I’m about to write an app where the users home page is made up of blocks which they can move around the page (twitter feed, blog, flickr feed,…). Cells seem ideal.

    Like

  3. Wouldn’t it be better if you could just use normal controllers as cells? Then you wouldn’t have to worry that in future you might need to make a cell out of a piece of functionality.

    Surely this would be easily possible with rails 3 new architecture?

    Like

  4. @Chris – Cells in Rails 3 are derived from AbstractController, so you’re on the safe side.

    Concerning your dashboard, check out Apotomo if your widgets should be interactive – if they are render-only, use Cells.

    Like

  5. High 5 Nick, Cells saved me out of the box from all this pain in the viewss you mentioned. Using them happily in 2 Rails3 projects, integrating seamlessly with other gems like devise. Any core contribution plans?

    Like

  6. I especially like the part about the cell taking care of fetching and caching its own data. It’ll definitely be in the next stuff I’m planning to do (probably with Apotomo too!). Great job 🙂

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s