Rails Components: Cells vs Embedded Actions
Posted by Lance Ivy Fri, 09 May 2008 17:19:00 GMT
Components are the red-headed left-handed stepchild in the Rails world. Support for render_component in Rails core is reluctant at best, with most (all?) issues going ignored and unpatched. The problem is that reliance on components interferes with the effectiveness of Rails’ RESTful paradigm. Both approaches are designed to reduce code duplication, and Rails has chosen to become opinionated and pick one over the other. This is a good thing, as it lets Rails optimize its approach and provide deep support throughout.
I can imagine two cases where you might want components.
The first is when you have some complex page (e.g. a portal page) that pulls together pieces from your RESTful controllers. That alone isn’t enough to justify introducing components though, since you may be able to very effectively solve the problem with good use of partials (use the :locals hash and do not check any instance variables or the params hash!) and/or reduced reliance on the controller for data collection (move the data collection needed for the partial into a model method used in the partial itself). But maybe, after ruling out other good design choices, you may still need components here. It’s a tough call, but I’d recommend avoiding components until you really absolutely need them.
Which brings me to my second case. Rails’ RESTful paradigm relies heavily on URL mapping. But what if you need one URL to respond identically to another URL but only sometimes? If the two URLs responded identically all the time, you could simply add routing to send both to the same controller/action. But if they’re only identical sometimes, then you may need separate controllers, where one of the controllers essentially delegates to the other when certain conditions are true. This is where I think components can complement good RESTful design principles.
In my situation, I have a landing page that may be customized by an account holder, but until it’s customized it should respond identically to a deeper resource. So it’s time for a component solution. I’ve found three choices:
render_component
Ugh. Too many problems, and it’s not even supported. Moving right along…
embedded actions
This is essentialy render_component reborn. And cleaned up and supported. The embedded_actions plugin provides a simple way to, well, embed the result of another controller’s action. It has support for caching built right in, but even better, it registers a new Mime::Type so that you can detect an embedded request in your controller and respond specifically (respond_to :embedded just like respond_to :html).
The upside of this approach is that you can reuse all your existing controller logic. The downside is that it still hase some intractable render_component-style problems because you’re still instantiating a second controller object. The StreetEasy guys have identified and fixed a couple of duplication issues like logging, flashes, and session management, but the fact remains that this is a second controller object that will still have to run through all of its filters and will still have to maintain its own instance variables.
What does this mean? It means that when you embed an action, you may check user permissions twice because of duplicate filters. Filters can be pretty fast, yes, but it gets worse: in my application I need to load up a lot of context for each request (account configuration, subscription plan limits, etc.). All of this information is cached on-demand for the duration of the request using standard instance variables and the nifty `||=’ syntax. But those instance variables are not shared between the main controller and the embedded controller, which means it all has to be loaded twice. This is not an issue that can be solved without hacking the controllers to check for a "parent_controller" everywhere, thereby destroying the cleanliness of the very solution itself.
cells
Cells takes a completely different approach. It essentially lets you create sub-controllers with their own views. These sub-controllers do not know anything about requests or responses, and they can not be directly mapped to URLs. They have no filters. They simply encapsulate the relationship between collecting/processing data and returning text, which your regular controllers’ views can insert into the final client response.
Clean. Simple. Too simple? Well, the upside is that you get much better performance, and you get the encapsulation benefits of object-oriented programming (the arguments provided are the arguments used, no behind-the-scenes appealing to params or random instance variables). The downside is that can’t leave code in your controllers and embed it somewhere else; instead, you have to pull the code out into a cell that both controllers can use equally well. And, of course, there’s the danger of trying to utilize cells too much, ignoring the cleanliness and deep support in Rails for RESTful controller design (e.g. for form handling).
Oh, and it doesn’t having caching support built into the plugin itself. I suspect this isn’t a long-term problem, since it seems easily solved.
My Plan
Again, in my situation, I have one controller that provides a customizable landing page. Until it’s customized, however, it needs to respond identically to a deeper resource. At first glance I thought that embedded actions were the easiest way to go, since I already have the other controller set up. And cells don’t have caching support built-in. But even so, given the performance problems related to embedded controllers, I think that cells will prove to be cleaner and faster in the long run, and that’s where I plan to invest my time.
Subscribe