Using Cells View Inheritance to clean up your views

To the right!

In every Rails app is some reoccurring element.

In my blog example, this is a small teaser box (to the right!) showing a snippet from the newest post, and a footer section that provides a link to the article.

The teaser on my RubyFlow portal page.

However. My imaginary blog also has a page about RubyFlow where I’d list the links I posted there, just to proof how cool I am.

On the RubyFlow page, I don’t want the teaser to show a link to the article, but a red button to RubyFlow. Of course, this doesn’t make sense at all.

My god, I hate that red in combination with the pink on this site.

The medieval if-cascade

Things like that are usually implemented in the partial with some if/else part.

if @current_page == 'rubyflow'
  Don't read it, better go to rubyflow
else
  = link_to "Read it!", "/blog/read/#{@post.id}"

Pretty ugly.

Things start getting really ugly when there are more conditions to respect and end up in a medieval if-cascade. It’s geting even worse when those deciders are copied and spread across multiple files.

How we do it

Cells brings back the power of OOP to your views. It offers a feature called View Inheritance that saves us here. I will briefly discuss that while showing you how I implemented the different teasers.

$ script/generate cell teaser box body footer --haml
      create  app/cells/teaser_cell.rb
      create  app/cells/teaser/box.html.haml   
      create  app/cells/teaser/body.html.haml
      create  app/cells/teaser/footer.html.haml

The TeaserCell has three states that don’t do much right now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TeaserCell < ::Cell::Base
  def box
    @post = Post.find :first
    render
  end
 
  def body
    render
  end
 
  def footer
    render
  end
end

Just grabbing the newest post in line 3.

Nesting states

I use the box state as a container. It renders the body and footer in its view app/cells/teaser/box.html.haml.

#teaser
  %h3
    Nick says
 
  .body
    = render :state => :body
  .footer
    = render :state => :footer

That’s right, I use render :state in a cell view. That’s somehow comparable to rendering a partial, although it first executes the #body method, which in turn renders its view.

teaser-with-generic-body-and-footer

That’s how the teaser currently looks like as we didn’t customize the body and footer view, yet.

It is pretty obvious that our body view should print out the teaser text, whereas the footer displays the link.

app/cells/teaser/body.html.haml

%h2 #{@post.title}
%p  #{@post.body}

And the footer in app/cells/teaser/footer.html.haml.

= link_to "Read it!", "/blog/read/#{@post.id}"

Enriching the controller

In the blog view I then call #render_cell to integrate the teaser in my blog ERB page.

<div id="right_menu">
  <%= render_cell :teaser, :box %>
</div>

Ok, so now we got a beautiful teaser in the blog page. How do we get that teaser to show a red button on the RubyFlow page?

Inheritance strikes again!

The answer is - unsurprisingly: we derive a new RubyflowTeaserCell from the TeaserCell.

$ script/generate cell rubyflow_teaser footer --haml
      create  app/cells/rubyflow_teaser_cell.rb
      create  app/cells/rubyflow_teaser/footer.html.haml

The actual inheritance happens in the class.

class RubyflowTeaserCell < TeaserCell
end

Note that we don’t need any methods as they all come from TeaserCell. The same applies to the views, they’re inherited as well!

But wait, we planned on overwriting the footer so here’s how the app/cells/rubyflow_teaser/footer.html.haml looks like.

Don't read it, better go to
%span rubyflow

In the Rubyflow controller view I add the cells rendering.

<div id="right_menu">
  <%= render_cell :rubyflow_teaser, :box %>
</div>

That’s a call to RubyflowTeaserCell. Now, what happens here.

  • the derived RubyflowTeaserCell calls the #box method which is inherited
  • as the respective view is not found in app/cells/rubyflow_teaser it is looked up in the parent directory app/cells/teaser/ and found
  • the same happens with the :body state
  • when rendering the footer, the view is found in the cells own view directory and thus not inherited

Conclusion

View inheritance comes into play whenever complex deciders threaten the straightforwardness of your view code. When you encounter if-cascades with more than two ifs, think about using a cell with inheritance there.

Code?

The example code can be found at github.

One Response to “Using Cells View Inheritance to clean up your views”

  1. Tomasz Says:

    Cells are great and helpful! Just fits right in big projects. Well, I’m strongly using everywhere, whenever it can simplify reusing of codes, and actually I’m any more seeing partials. Now I’m porting my cms project to Rails 3 and waiting for Cells release. Keep doing great work.

Leave a Reply