{{{
This is a real world example *how to use “Rails Cells”:https://github.com/apotonick/cells, the popular gem for creating reusable view components*. The following post is clean and all-ages. Let’s just all be friends, no bashing, no f-words today, just a brief HOWTO.
It is *different from what you might have learned from Rails so far*. This doesn’t mean it is _wrong_. Just think about it. If you feel it’s shit, let me know. If you like this approach, I wanna know as well.
h3. What are sidebar elements?
Usually reoccurring components like a “Recent posts” box in a blog application are what we refer to as _sidebar element_.
The exemplary blog application “used in this post(s) can be found on github”:https://github.com/apotonick/cells-blog-example and runs with Rails 3.
So what we do today is a sidebar box. It contains
* links to a certain subset of *recent posts*
* tags to click, *which will filter the post list*
h3. No partials and helpers today
As we’re learning a new paradigm today, we’re not gonna do the traditional approach to implement the box. *Using partials and helpers might do it as well*, but, let’s look forward.
The first step is to generate a cell, which *exposes the same properties as a real controller*.
blog$ rails g cells:cell Posts recent --haml create app/cells/posts_cell.rb create app/cells/posts/recent.html.haml create test/cells/posts_cell_test.rb
Ok, a cell class, a view, a test. Great.
In the second step, I want to render a list of recents posts. My file @app/cells/posts_cell.rb@ looks like this.
class PostsCell < Cell::Base def recent @posts = Post.recent render end
Like a controller.
h3. Cells are controllers.
So what do we do here?
* We *aggregate the items* to display by delegating to the model.
* A call to @#render@ *instructs the cell state to render its view* in @app/cells/posts/recent.html.haml@.
The view is simple as well.
%h3 Recent Posts %ul - for p in @posts %li #{link_to p.title, p}
* We simply iterate over the posts and *link to the real article*.
* Note that we can *use helpers, logic, everything in cells views*.
As a last step, we *render the cell in the application layout*.
%body %h1 The Incredible Cells Blog #sidebar = render_cell :posts, :recent
That’s all for creating a render-only sidebar component. You could have done this with your traditional tools. However, we already got a couple of benefits.
* The cell is *reusable throughout your project* and even between different projects. Just plug the @app/cells/posts@ directory into another app and you’re done. I will post separately about that.
* No controller pollution as the cell computes and renders in its own separate instance.
* We have *better testability* – another post on that one, promised!
h3. Have a tag-cloud widget
One of Cells strength is when it comes to *writing composed widgets*. The tag cloud sitting above the recent posts list *should be a separate cell _state_* – meaning a separate method and a view.
class PostsCell < Cell::Base def recent # ... def tag_cloud @tags = Post.tag_counts_on(:tags) render end end
Not hard to see that we might need another view in @app/cells/posts/tag_cloud.html.haml@.
- tag_cloud(@tags, %w(css1 css2)) do |tag, css_class| = link_to tag.name, "?tag=#{tag.name}", :class => css_class
Yeah, we can even use *helpers from other gems*, like the “acts_as_taggable_on”:https://github.com/mbleigh/acts-as-taggable-on helpers. We’re not limited in any way, dude.
To actually _show_ the tag cloud _inside_ our box, let’s *plug the tags into the @recent.haml@ view* for now.
%h3 Recent Posts = render :state => :tag_cloud %ul - for p in @posts %li #{link_to p.title, p}
Wow, we basically can *embed actions within actions* – this is fuc… simply great.
h3. Interaction, babe!
When clicking a tag the “Recent posts” list should update, showing only posts matching that tag. This is something called _interactivity_ – the *cell in your controller view processes user gestures*!
Not a big deal. And, if you think this is breaking MVC: “it is not.”:http://nicksda.apotomo.de/2010/11/rails-misapprehensions-cells-dont-break-mvc/ *This _is_ MVC*.
Let’s see how the cell controller handles that.
class PostsCell < Cell::Base def recent tag = params[:tag] @posts = tag ? Post.tagged_with(tag) : Post.recent render end def tag_cloud # ... end
The cell controller is exactly *the place where this VC-gluecode should live*. We don’t need work-arounds like presenters or helpers here, we prefer true MVC.
h3. Discussion
We just learned the basic usage of Cells, including
* Generating and *writing simple render-only cells*.
* Composing *nested cells* by calling @render :state@ in cell views.
* Processing user input by using @#params@ in the cell state.
And we got plenty of stuff to check out, like
* Caching cells to *boost your application performance* and simultaneously improving your architecture.
* Writing *functional tests _and_ unit tests for cells* to create reliable components that can be plugged into _any_ controller and just work!
* putting cells into Rails engines to *maximize your modularity*. Imagine real widgets for a blog application shipped as gems and running seamless in your views.
Now, comment! And, hey parents: Writing posts without using swearwords _SUCKS_!
}}}
Hey,
thanks, I was waiting for a simple and clear example like this to understand *one of* the benefits of Cells, I may use this in a project refactoring soon, I’ll tell you if it fits my needs (I’m almost sure it does, though, since it exactly the same as this sidebar component example!).
Cheers and congrats for Cells and this simple-but-to-the-point tutorial, I think you really need these kind words after all these *jealous* people that yelled at you!
LikeLike
Rails was so monolithic. Now it’s getting much more modular with Rails3, bundler an cells. We will start to see more and more sharing of small controls. A new era has begun. Great post.
Devise an Conquer!
LikeLike
Thanks for your work on this, Nick. I’m planning to use Cells in a current project. I’m curious about using this with javascript calls. What if I want to pull in this sidebar via JS?
Are cells routable like that?
LikeLike
I’ll definitely use Cells in my new projects. Using helpers or before filters to load data for side bar components is very awkward compared to Cells approach.
LikeLike
Do Cells work with Sinatra?
LikeLike
@Rymaï, @atros, @Jim and @Greg: Thanks dudes, for your nice and motivating comments! 🙂
@Jim: What you want is Apotomo. I will release the 1.0 and announce it in a couple o’ days. Lemme know if you run into any trouble!
@Jello: Well, I once worked on that: http://github.com/apotonick/cells-sinatra – do you think this is helpful?
LikeLike
nice article man 🙂
LikeLike
Thanks for Cells, it’s what Rails really missed.
LikeLike
Great post, We’ve used apotomo on our last two projects, all the widgets are using apotomo from now on 🙂
The reusable thing is really helpful btw.
Less headaches and cleaner code.
Thank You (beer is on me)
LikeLike
Thanks nick I’ll checkout Apotomo. One thing that stands out about cells is the structure. Why put view files anywhere but in the app/views directory?
The way it is, I anticipate headaches with maintenance for any number of cells over a handful.
Why do cells views live where they do instead of in app/views?
LikeLike
@Jim: Cells are separated in app/cells. Imagine you had a
PostsController
and aPostsCell
– we couldn’t discern between controller and cell views if they were in the same directory.Beside that, we simply don’t like the way Rails organizes the VC assets 😉
What problems do you see in the maintenance?
As a hint, remember you can always do “submodules” like
where you group views logically in cells. Does that help?
BTW- writing a post how to switch from cells to Apotomo!
LikeLike
The maintenance problems I see is that we have views in 2 locations. Having an app/cells directory makes sense, but I’d rather organize my views together in app/views/cells (and maybe configurable if that namespace exists).
It’s not a deal-breaker, but it just doesn’t feel right to have views split up.
LikeLike
@Jim: Just do
Cell::Base.prepend_view_path "app/views/cells"
, how do you like that?LikeLike
can you forget the partials and only works with cells or there’s a reason for keep working with partial??…I don’t like partial, I don’t like his syntax, I don’t like his speed and the ideas behind this…….
LikeLike
@angel: Uhm, the good thing here is, it’s up to you 😉
I personally would not use Cells everywhere, sometimes a stupid partial is simply enough.
As a rule of thumb I’d say use a cell when
your partial uses a lot of locals or instance variables
you find out there’s too much logic in it
you wanna test that partial separately
caching the partial with fragment caching gets a problem
reusing the partial in another controller is required
your helper+partial code is too tightly coupled
LikeLike
I’m fixing my sidebar to use cells as we speak and I’m amazed by how much I prefer this approach.
It’s my new favourite gem.
I have a question though, is there a simple rails way to return an array of all the cells in my app? I want to iterate over it
LikeLike
@stephen: Thanks, glad you like it!
Concering your registry-question… hmm. Can’t think of any elegant way right now, I’m afraid you have to search the
app/cells
directories. We had aCellRegistry
years ago but then I learned to use Rails’ autoloading 🙂My recommendation: post this question to the mailing list , there might be a solution around already. Enjoy, and Cheers!
LikeLike
Very helpful introduction.
All the examples use a cell in the place you would use a partial view, called from a full view.
Can you also use a cell instead of a top-level view? Can you call render_cell in a controller? And would you want to? I kind of think you might sometimes, although I’m having trouble describing the circumstances.
LikeLike
@Jonathan: You might call
render_cell
in a controller, which you could use to replace the top-level view. Some people already do this in order to separate clearly between controller (HTTP, data) and presentation (cell).LikeLike
A great gem! I have one issue, I’m using the declarative_auth gem and at some point want to call the permitted_to? method. However this method wants to be aware of the controller in order to obtain the defined authentication rules, is there a way to tell declarative_auth which controller is being referenced?
LikeLike
@Andrew: Thanks! Can you send a link to the permitted_to? method? Maybe you open an issue on github?
LikeLike
Great work. I am using this on my current project for sidebars and some widgets on main viewport area of the page. Its really working out well. Rails being monolithic was not giving me this freedom and had to write custom code to achieve reusable components.
LikeLike