Using Cells View Inheritance to clean up your views
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.
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 => :footerThat’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.
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
RubyflowTeaserCellcalls the#boxmethod which is inherited - as the respective view is not found in
app/cells/rubyflow_teaserit is looked up in the parent directoryapp/cells/teaser/and found - the same happens with the
:bodystate - 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.



May 14th, 2010 at 10:51 am
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.