Cells
View Components for Rails.
Overview
Say you’re writing a Rails online shop - the shopping cart is reappearing again and again in every view. You’re thinking about a clean solution for that part. A mixture of controller code, before-filters, partials and helpers?
No. That sucks. Take Cells.
Cells are View Components for Rails. They look and feel like controllers. They don’t have no DoubleRenderError. They can be rendered everywhere in your controllers or views. They are cacheable, testable, fast and wonderful. They bring back OOP to your view and improve your software design.
And the best: You can have as many cells in your page as you need!
Installation
It’s a gem!
Rails 3.x:
gem install cells
Rails 2.3:
gem install cells -v 3.3.8
Generate
Creating a cell is nothing more than
$ rails generate cell cart display -e haml
create app/cells/
create app/cells/cart
create app/cells/cart_cell.rb
create app/cells/cart/display.html.haml
create test/cells/cart_test.rb
That looks very familiar.
Render the cell
Now, render your cart. Why not put it in layouts/application.html.erb for now?
<div id="header">
<%= render_cell :cart, :display, :user => @current_user %>
Feels like rendering a controller action. For good encapsulation we pass the current user from outside into the cell - a dependency injection.
Code
Time to improve our cell code. Let’s start with app/cells/cart_cell.rb:
class CartCell < Cell::Rails
def display(args)
user = args[:user]
@items = user.items_in_cart
render # renders display.html.haml
end
end
Is that a controller? Hell, yeah. We even got a #render method as we know it from the good ol’ ActionController.
Views
Since a plain call to #render will start rendering app/cells/cart/display.html.haml we should put some meaningful markup there.
#cart
You have #{@items.size} items in your shopping cart.
ERB? Haml? Builder?
Yes, Cells support all template types that are supported by Rails itself. Remember- it’s a controller!
Helpers
Yes, Cells have helpers just like controllers. If you need some specific helper, do
class CartCell < Cell::Rails
helper MyExtraHelper
and it will be around in your cart views.
Partials?
Yeah, we do support rendering partials in views. Nevertheless, we discourage partials at all.
The distinction between partials and views is making things more complex, so why should we have two kinds of view types? Use ordinary views instead, they’re fine.
%p
= render :view => 'items'
View Inheritance
This is where OOP comes back to your view.
-
Inherit code into your cells by deriving more abstract cells.
-
Inherit views from parent cells.
Builders
Let render_cell take care of creating the right cell. Just configure your super-cell properly.
class LoginCell < Cell::Rails
build do
UnauthorizedUserCell unless logged_in?
end
A call to
render_cell(:login, :box)
will render the configured UnauthorizedUserCell instead of the original LoginCell if the login test fails.
Caching
Cells do strict view caching. No cluttered fragment caching. Add
class CartCell < Cell::Rails
cache :display, :expires_in => 10.minutes
and your cart will be re-rendered after 10 minutes.
You can expand the state’s cache key - why not use a versioner block to do just this?
class CartCell < Cell::Rails
cache :display do |cell, options|
options[:items].md5
end
The block’s return value is appended to the state key: "cells/cart/display/0ecb1360644ce665a4ef".
Check the API to learn more.
Testing
Another big advantage compared to monolithic controller/helper/partial piles is the ability to test your cells isolated.
Test::Unit
So what if you wanna test the cart cell? Use the generated test/cells/cart_cell_test.rb test.
class CartCellTest < Cell::TestCase
test "display" do
invoke :display, :user => @user_fixture
assert_select "#cart", "You have 3 items in your shopping cart."
end
Run your tests with
$ rake test:cells
That’s easy, clean and strongly improves your component-driven software quality. How’d you do that with partials?
RSpec
If you prefer RSpec examples, use the rspec-cells gem for specing.
it "should render the posts count" do
render_cell(:posts, :count).should have_selector("p", :content => "4 posts!")
end
To run your specs we got a rake task, too!
$ rake spec:cells
Rails 2.3 note
In order to copy the cells rake tasks to your app, run
$ script/generate cells_install
More features
Cells can do more.
- No Limits
-
Have as many cells in your page as you need - no limitation to your
render_cellcalls. - Cell Nesting
-
Have complex cell hierarchies as you can call
render_cellwithin cells, too.
Go for it, you’ll love it!
LICENSE
Copyright © 2007-2011, Nick Sutterer
Copyright © 2007-2008, Solide ICT by Peter Bex and Bob Leers
Released under the MIT License.