When I started using Rails years ago I found helpers extremely cool. I could call a method in a view and it would _help_ me by doing something. The method was simply there, *no need to worry about its source* and how to access it, just call it.
I got older, wiser, and more opinionated. I still like the concept of helpers – of _methods_. *However, the way helpers are implemented in Rails sucks.* Also, having object-disoriented functions in your view brings us back to the years where OOP still had to be invented.
In this post I’d like to discuss why I dislike Rails helpers and how to get out of that misery.
h3. What’s a helper?
In Rails, a helper is a function.
Here, the @#capitalize@ method helps me capitalizing the username, which is freaking awesome. As this is pretty simple behaviour, let’s call helpers like this *utility methods*. They modify the input parameter, compute something or escape strings. Pretty straight-forward.
As a second example, I’d like to show a more complex helper.
This helper will iterate through news items for a particular user and _render_ markup, maybe using several partials. Since it actively renders templates, let’s call this a *view component*.
h3. Why are helpers shit?
At first sight, using helpers rocks. Capitalizing a string works like a charm – I simply call a _function_ and it happens.
However, looking at the first helper I can identify several drawbacks.
module StringHelper def capitalize(string) string.capitalize end
* Helpers in Rails are modules, which *do not allow inheritance*. If I’d need a foreign method I’d have to include another module into the helper module. Not a big deal.
* Using the @#capitalize@ methods happens without a receiver. The method is globally available in the view since Rails _somehow_ mixes the helper into the view. So, what happens if I have *two @#capitalize@ methods in two different helpers* mixed in the same view? I don’t have a clue. Do you?
Before getting to solutions, let me discuss another issue with helpers: *Another real problem is the implementation in Rails* – how these _functions_ are made available to the view.
h3. Helpers in Rails
Again, I’m not talking about how @#form_for@ or @#url_for@ are written internally, I’m talking about *how these methods get into the view*.
In Rails 3, all helpers are mixed into the view automatically, you still can insert additional modules using the controller’s helper facilities.
class HomeController < ApplicationController helper StringHelper
It’s not that the implementation as-it is bad code or something, it is the idea of magically mixing methods into the view instance to make them globally available. This adds complexity to the Rails core, namely “around 280”:https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/abstract_controller/helpers.rb “LOCs”:https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_controller/metal/helpers.rb. *Just to mix some methods into the view.*
h3. Helpers are shit.
I desperately tried to demonstrate the major disadvantages of helpers in Rails. To summarize.
- *I like utility methods*. There is nothing wrong with having those little “helpers” in your view. What I don’t like is that they are called without an obvious receiver – they look and feel like _functions_. This is wrong.
- The way Rails mixes helpers into the view is error-prone and sucks. Following a slightly different approach *there’s no need for all that complexity*.
- *Complex helpers suck.* I do believe in view components and the need for those but they shouldn’t be rendering helper methods.
Moaning is fine, but let’s see how things could be changed.
h3. Solution 1: Push Utility Methods into Decorators.
Luckily, a bunch of people feel uncomfy about the current helper architecture. My friend “Steve Klabnik wrote a nice article”:http://blog.steveklabnik.com/2011/09/09/better-ruby-presenters.html about “Jeff Casimir’s draper gem”:https://github.com/jcasimir/draper which introduces the Decorator pattern into Rails’ view layer.
Basically, the draper gem wraps existing model instances and *provides utility (“helper”) methods on the decorated instance*. Here’s an example.
class ArticleDecorator < ApplicationDecorator decorates :article def published_at model.published_at.strftime("%A, %B %e") end
Now that we defined the Decorator we can use it to wrap the actual model.
@article = ArticleDecorator.decorate(Article.find(1))
The wrapped model can then be used in the view.
The interesting point is that we call the utility helper *on the wrapped model* which clearly states a receiver. No need for a homeless, global helper function. This way, we can have *cleanly separated, domain-focused helpers for models*. Decorators also allow inheritance and all other OOP features, since they are just objects.
Decorators are a solid technique when it comes to – well – decorating models. What can we do if there’s no matching model, for instance, when we need to call @#url_for@?
h3. Solution 2: Use the Controller Instance as View Context
To learn more about that we should peek at the rendering cycle in Rails. What happens when a controller renders a template?
- An @ActionView@ instance is created (this will be the “context”).
- The controller manages a magical module that contains all helper methods. This module is now *mixed into the @ActionView@ instance* to make helpers available. I already discussed the need for hundreds of lines of code in order to achieve this “knowledge transfer” from the controller to the view.
- Next, instance variables from the controller are copied to the view instance as well.
*These are 3 completely useless steps. Completely.* Every template engine, whether it be Rails’ internal or “tilt”:https://github.com/rtomayko/tilt requires a so called _view context_ whenever a template is rendered. Both instance variables and methods (that is, helper calls) used within the template are looked up on this view context instance.
Now, there is absolutely no reason for having a separate @ActionView@ instance as view context! We can simply *use the controller instance as context object* and everything would work. No need to copy over variables, no need to transfer “helpers” to the view instance.
“Helpers” would be modules mixed into the controller – and that’s it.
class HomeController < ApplicationController include UrlMethods def show @link = link_to(home_url)
Notice how we can use the mixed-in “helper” methods in the controller instance – we simply included them.
The cool thing is we can also use the utility methods in the view which will be *invoked on the controller instance, again*. No magic copying, just modules.
The “Cells”:https://github.com/apotonick/cells project currently is experimenting with this approach and things work out fine. Will blog.
I can hear people now moan about *too many mixed-in methods in their ActionController* – and they are right! Again, this is due to Rails’ monolithic view/controller design. If one single controller is responsible for rendering an entire web page, then this controller has a lot of responsibilities – too many. That’s why we should use Cells to split up the view into components, which is discussed next.
h3. Solution 3: Use View Components instead of Complex Helpers.
Helpers that compute data _and_ render partials are scary. Often, there is too much concerns in the little helper.
def render_news_for(user) items = user.find_news render "shared/news", :items => items end
Let’s assume the @_news@ partial should be reusable throughout your application, needs some special helper function @#sanitize@ _and_ does caching.
<% cache do
Several problems here.
- Every controller has to take care of requiring the special @SanitizerHelper@ for the partial.
- *Caching happens by using helpers*, again, “which is no good”:http://nicksda.apotomo.de/2011/02/rails-misapprehensions-caching-views-is-not-the-views-job .
Moving the partial and its behaviour “into a cell”:http://nicksda.apotomo.de/2010/11/lets-write-a-reusable-sidebar-component-in-rails-3/ would cleanly separate concerns. The cell could be used as view context and thus provide utility methods itself.
class NewsCell < Cell::Base cache :show def show(items) @items = items render end def sanitize(string) # ... end
This creates a reusable view component with a defined scope. Intentionally, I keep the cells discussion briefly as this would break the mold.
h3. Combining Decorators and Cells
Using draper’s decorators within cells is what I figure a fantastic option. Where the *decorator cleanly wraps the model object* and provides utility methods for tweaking model data the *cell separates the concern into a reusable view component*, provides a limited scope and generic helper methods (like @#url_for@), and even caching!
I really don’t care whether draper, cells, or whatever replaces helpers – all I want is less magic code, more object-orientation and rock-solid software. This was a long post – gimme some feedback in the comments section or “tweet me”:http://twitter.com/#!/apotonick