Wednesday, September 9, 2009

Cells are bad ass

I'm researching Nick Sutterer's cells for rails plugin and loving it so far. But, love can be fickle and this is just infatuation. But again, so far, so good.

To me, the V in Rails' MVC is severely lacking. And by lacking, I mean it stinks. I wanted to reuse partials from other controllers, pass arguments around, that kind of thing. The stuff that makes an RIA easier to build (I just learned that acronym yesterday). Partials are slick for simple apps like blogs, but they really look butt ugly when they're thrown around inside a complex form. And I've heard that using a bunch of partials can be slow, which may or may not be a factor (YMMV). Cells claims to be fast, but at this point I don't care.

Cleaning out the Clutter

Now ... lets try out cells. First, I branch my code so it can be scrapped if I need to bail.

Here's what one of my views looked like before:

...

%h2 Categories
= render :partial => 'category', :locals => { :f => package_form }

%h2 Available Contents
= render :partial => 'shared/search_field', :locals => {:name => 'search', :value => @search}
= render :partial => 'potential_packages', :object => @package.potential_children, :locals => {:f => package_form}

%h2 Current Contents
= render :partial => 'child_links', :locals => {:f => package_form}

...

And here's that view on cells:

...

%h2 Categories
= render_cell(:categories, :selections, :form => package_form)

%h2 Available Contents
= render_cell(:search, :search_for_package)
= render_cell(:packages, :selection_list, :packages => @package.potential_children)

%h2 Current Contents
= render_cell(:packages, :package_children, :form => package_form)

...

It's a subtle difference, but to me a huge difference: it's cleaner and easier to read what's going on. Partials, with their hashes, really obscure the intention, IMO.

Here's the rest of the code for one of the cells. Here's the cell, located in file app/cells/packages.rb:

class PackagesCell < Cell::Base
def selection_list
render
end

def package_children
render
end

def package_child
render
end
end

No work to do, just the three "states" each with a simple render. By Rails-style convention, it knows where to find the file to render: app/cells/packages/package_children.html.haml:

- form = @opts[:form]
- children = form.object.children
- if children.empty?
%h3 -:- No contents -:-
- else
%table
%thead
%tr
%th Vendor Model
%th Min
%th Max
%th Default
%th Min Items
%th Max Items
%th % Discount
%th Remove?
%tbody
- form.fields_for :child_links do |nest_form|
= render_cell(:packages, :package_child, :form => nest_form)

Look -- nested cells for nested attributes!

Reuseable Cells

The real treat comes when I want to use those same searches and package lists elsewhere: I don't have to handle different cases, don't have to reference any directories, don't have to pass a :locals hash, and as a bonus, I get a cells "controller" class that can do some work with both the model AND the session, with an explicit render. Multiple render constraints are gone, finally. This isn't a treat, it's a freakin' desert buffet!

In addition, I get to delete many partials so my view/model directories are cleaner, with fewer files, and it's still easy to find the cells when you need to: just look into the view that's referencing the cell and there it is, the hierarchy laid out for all to see, easy to find in the app/cells directory.

Just to test all this theory out, I plunked a cell into another controller's form, changed the object.attribute reference (from @packages.potential_children to @contract.potential_packages), and the cells showed up, working. Not only that but I added the same cell a second time and changed the attribute (from potential_packages to potential_inventory_items) and it worked! Man, that's what I call DRY.

Hierarchical

Cells introduces a new directory, apps/cells, but IMO the layout is intelligent and well organized. It didn't take long to learn the DSL and organization (I was afraid it was going to be a long road to learn Yet Another Framework). This blog post by Mike Pence helped out a lot. Mike also gave a talk (aptly called Components Are Not a Dirty Word) along with Cell's author, Nick Sutterer, at RubyConf 2008.

Plus, it's Rails 2.3 compatible.

Thanks, Nick, for a beautiful plugin -- you've made me believe that V can be complete. I can't wait to put cells on apotomo for some dynamic interaction.

UPDATE: Russell has convinced me to not use cells. render :partials can be wrapped to reduce their noise, accept variables and be shared. I looked at the apotomo code and it's an exciting concept but looks very complex. I'm going to take a "wait and see" attitude for now, to see how Russell implements the JavaScript for the app.

UPDATE: I still like cells, but will wait until the app calls for one. So far, Russell's partial wrapper is okay but the view directories are getting kinda messy. But, it does put everything in one directory and we aren't using a lot of shared partials yet. Here's a link to a post that talks about presenters and cells.

UPDATE: Nick Sutterer and I will be giving a presentation on cells/apotomo components at the 2010 Lone Star Ruby Conference! I'm really looking forward to working with Nick on this presentation and implementing cells in the app.