{{{
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.
Hello,
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.
<a href="">
}}}{{{
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
}}}
I find the decorator pattern useful for more than this, but the draper gem seems overkill. Plain Ruby is just as simple and even less magic!
LikeLike
You’re touching some important problems here.
Draper and the idea of decorators is interesting, indeed. It’s better than the traditional Rails way. Still, it concerns me that we’re creating a new object here (a decorator) where in my understanding we still have the same base object – an article in this case.
Why don’t we use the idea of “extending the object runtime” here?
where ArticleView is a role (in the DCI sense) implemented as a module:
In short – let’s not create new objects, but use the existing ones and use roles. It means that in this request the article knows how to represent itself.
Similarly, with the #url_for method. It becomes just article.url (injected by ArticleView as well).
LikeLike
Andrzej Krzywda – I think inheritance is the better option here. And what’s wrong with creating more objects?
LikeLike
Good insight, Nick. The Rails way doesn’t scale with regards to code maintainance as the application gets bigger.
Chances of conflicts as the codebase evolves increases, specially for helpers. That’s why I always avoided them.
Nowadays, with lots of logic moving to the client-side, this is less of a problem with views being delegated to tools like Handlebars, Knockout, Batman, etc.
That’s probably the reason why no one seems to be really concerned about this issue.
By the way, using frameworks like Cell or Apotomo is the way to go for separating concerns the right way, in my opinion. Keep up with the good work!
LikeLike
Justin: I try to reflect the user mental model in the object design. If you ask the user what is the thing that he’s reading now – he’s going to say “I’m reading an article”. Not an article_decorator.
As for inheritance, it is a possible way of implementing it but it just doesn’t sound well to me.
Rodrigo is right here – we move most of the logic to the client-side, especially the view stuff. I think that’s the right direction – with client-side we can do a real MVC with better UI modularization than with Rails.
LikeLike
News.for_user(@user)
Nice and easy, obvious where the method exists and easily testable.
If you end up needing logic in a view directly or via a helper, maybe have two versions of the view and render the appropriate one.
LikeLike
I recently wanted to use helpers in a way that would allow helpers defined later overwrite methods defined by helpers included earlier (that’s an expected behavior I believe). It was total pain in the ass for two reasons:
a) rails by default loads all helpers for every controller
b) the helper which is named after controller is included into view context firstly (not lastly) and trying to add it later has no effect because it is already included.
It took me a while to figure out a nasty workaround: https://gist.github.com/1234553 . So not only helpers are way behind the rest of rails code and totally not OO, they are also very hard to customize (meaning “to use in a non-standard way”). The whole point of Rails 3 was to make Rails more modular and allow developers to do things “their way” instead of “rails way” when they need. I think that this point was not achieved when it comes to helpers. They are pretty much the same thing since Rails 1.
I totally agree with you that there is a problem. However the second solution scares me a lot. Every other way is a better way 🙂
LikeLike
This week episode on RailCasts.com is about the Draper gem, and shows how it helps to clean up your code.
Ryan BATES also introduces this week the “pro” declination of his screencasts ($9/month) whose first episode is titled “Presenters from Scratch” and explains how to implement Draper like features… from scratch (with all the flexibility it offers) and with testing.
LikeLike
@Daniel, @Andrzej: I also like plain Ruby best, but I guess the draper Decorators are made without any magic, so I’d recommend checking it out. Definitely, I love the idea of Andrzej, using a DCI approach to extend the model instance dynamically for the view. This could be a different approach for draper.
@Rodrigo: Would love to see an example how JS helps avoiding helpers!
@Robert: Agreed. Why do you hate the 2nd approach, using the controller instance as view context? That’s the way Sinatra does it, btw.
@Frederic: Thanks, will have a look at that!
LikeLike
@nick – I think that the second approach would make controllers a giant classes with too much responsibility. As for instance variables being copied into view context. I like decent_exposure approach to that problem. We should explicitly expose data for views via methods (not instance variables). I even use this approach with apotomo.
Another thing that I would consider is exposing data for view in separate object:
def show
view_data.post = Post.find(1)
end
This view data could be a simple container or it could have more responsibilites.
LikeLike
@Daniel, @Andrzej, @nick – ah, but don’t tie decorators so tightly to models!
Because Draper uses classes, you can use them without tying them to a model.
So, with the composition approach, how would you extend a non-model? You’d have to create a class or object:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gistfile1.rb
hosted with ❤ by GitHub
Might as well just use classes from the beginning. Also, including a “base” module into other modules *and* classes feels dirty to me. Include (or define) it in the superclass and be done with it.
LikeLike
Nick, btw, your website is shit! lol – no follow up emails for comments!
LikeLike
@Justin: The DCI approach isn’t bound to any “models”, you just add instance methods temporarily at run-time to decorate an object.
The point is you’d really just use
to decorate it, nothing more. What’s so dirty about that?
I installed email notifications, let’s see if that works 🙂
LikeLike
@Robert: The ActionController already is a giant class with way too many responsibilities – that’s why I say, split that into several cells! Why should I mix in
#published_at
into the AC controller if that method is needed once in a small fragment, only? That could be modeled into a separate cell.You can still use decent_exposure, which would be a good idea for an Apotomo screencast, btw!!!
LikeLike
@nick – my point was how do you come up with that object? You have to create a class or do Object.new.extend
Most likely you would not use Object.new because you probably need to pass some data/state to the decorator. Therefore, you would use a class. So, you would have modules for your models, and classes for your non-models. Inconsistent.
LikeLike
Rails helpers move us away from OO design patterns whereas Cells and Draper move us obviously closer. Putting decent_exposure (or something like it) in the core and not copying instance variables would put us in the right direction without changing too much.
The other downside with helpers in most rails apps is that we set “helper :all” in the application controller because the community (myself included at the time) wanted to use helpers to organize code rather than extend a specific instance of the view.
Andrzej is right to point out that the mental model is important too. I wrote a bit about the DCI approach and why you would want to use it.
LikeLike
The automatic mail follow-ups should work now.
LikeLike
Little typo: “Solution 1: Push Utitlity Methods”. Should be “Utility”.
LikeLike
I am confused how helpers are not OO. Modules like everything in Ruby is an object.
How modules get mixed into the view is not a mystery.
ActionView creates the object that the view is in, and mixes in the appropriate helpers: application_helper and current_view_helper.
Perhaps you need to study how mixins work?
LikeLike
As long as the “conflicting” methods are not in either the current_view_helper or application_helper there is no conflict, if they do, well it follows the ruby object model obviously, so it should be clear which one get called. If it isn’t, then bone up on the Ruby object model, which is a little complex, but not massively so.
Since Modules are a Class, and an Object you can mixin as many other modules as need. Saying you can’t include other modules is rank ignorance.
LikeLike
@qwerty: Frankly, I’m sick of the “In Ruby, everything is an object” excuse for making things wrong. Yes, modules are objects, even mee knows that.
Nevertheless, in OOP every method call needs a receiver.
Why is the receiver magically the ActionView instance and not the controller when using helpers? Why do tons of helper methods still need to re-delegate to the controller if it makes so much sense to use the ActionView as context? Why do so many people complain that gem xyz suddenly overwrites helper #abc since we have one global namespace for all the application helpers?
Telling me to “bone up on the Ruby object model” doesn’t help, I’m more concerned about the architecture of helpers in Rails and not about the implementation as-it.
LikeLike
If you were more concerned about knowing a little more about Ruby’s object model you would know what the receiver is.
Try this
module T
def foo= foo
@foo=foo
end
end
Class Q
include T
def foo
@foo
end
end
Inside an object the self receiver is almost always implicit, outside it never is. View code doesn’t need an explicit receiver because the modules are mixed into the view object. It is the same reason you can directly access instance variables.
It is not a global namespace in the same way the Kernel module is not global. Ruby isn’t a hodgepodge of poorly implemented feature like PHP.
Essentially, you actually have to learn Ruby to understand advanced Ruby concepts. Shocking I know, but still very true.
Telling you to done is very helpful, because your answer lies in the object model.
Seriously, read Metaprogramming Ruby, it will make all this stuff very clear.
LikeLike
@QWERTY: Thx for the examples! The problem is not my misunderstandings of the Ruby object model but the fact that I don’t want to access the ActionView instance. Seriously, all helper calls go to a monolithic, global view instance – which is about the same as a PHP script.
LikeLike
I’m using draper and in my Test::Unit tests, I have to add the following:
setup do
ApplicationController.new.set_current_view_context
end
This provides draper with the view context.
I am waiting anxiously for the day when the Rails core team announces a better way to render views!
LikeLike
When I say Test::Unit tests, I meant Test::Unit widget tests. :b
LikeLike
@nick,
You seriously misunderstand the ruby object model.
Do you think the Kernel module is a monolithic instance?
If yes, you don’t understand Ruby.
If no, how are helpers any different?
You can mix in modules from inside or outside the object, maybe that is what is screwing you up.
LikeLike
@kevin
That is an issue with Draper not rails.
Besides, Test::Unit is pretty weak, use Rspec.
LikeLike
@wtf: I re-read this post having your insightful additions in mind – couldn’t follow since this article is about Rails helper architecture and not Ruby module internals. You and me are talking about two different abstraction layers.
The global monolithic ActionView instance is my problem, not the Kernel.
> Besides, Test::Unit is pretty weak, use Rspec.
Sounds like a well-proven selling point.
LikeLike
What is wrong with helpers as functions? As far as i understand, OOP is needed only when a function is operating on a big and obscure data value, the new value is only slightly different from the original one, and the original value is discarded afterwards, like in:
user.update_email(new_email)
instead of
user = update_user_email(user, new_email)
OOP also conveniently allows to reuse the same method name for different functions based on the receiver type.
Otherwise i see no reason not to use pure functions. However, helper functions are not pure functions. I am not familiar with details, but i believe that the receiver of a helper method is the view/controller context, isn’t it? Then it looks very natural to me, because it is the responsibility of the view to decide how things are formatted or capitalized.
Decorators/delegators look to me like a bad secretary: they pass through all phone calls to which they cannot answer. Sending a message to a decorator, you are not sure who would answer, and if you are sure, then maybe using a decorator was not an appropriate choice?
Decorators look to me like a convenient hack to reuse code without posing too many questions about which code is responsible for what.
LikeLike
IMO, those criticizing Nick, and pointing out that Kernel is an object that is not technically global, and view helpers are the same and therefore ok, are missing the point. The point is that in order to make our applications simpler (and therefore more maintainable, extensible, etc.), we should always be seeking to make the accessibility to code and data as narrow as possible. This minimizes the number of possible interactions, and therefore complexity. Whether or not a helper is an object is not the issue; it’s that it’s so widely accessible.
I’ve been looking at functional programming lately (I wrote a blog article introducing FP in Ruby at http://www.bbs-software.com/blog/2012/11/05/intro-to-functional-programming-in-ruby/). Given that, I decided to try creating a function in the controller and passing it to the view in an instance variable. (If they got to be more numerous, I could put them in a hash (maybe even call it ‘helpers’ 😉 ), where the value was the function and the key was a name by which to access it.)
This may seem kludgy, and I haven’t thought much about how it would scale, but the benefit is that it narrows the scope about as much as one can. The way I did it was to create the function in the controller’s constructor, and assign it to an instance variable. Then, it’s available to the view. Treating functions as values is rare in Ruby (except when we pass them to functions), and I’m wondering if we should do it more often. Thoughts?
– Keith
LikeLike
I also hate helpers in rails. I think stuff should go in the controller or the view. Helpers make it very hard to figure out where it is and what the scope is. If your view gets too complicated, then pull some of it out into a partial. I guess my approach isn’t kosher because then I am accessing models from the views, but that seems like a small price to pay for avoiding the helpers. And I think it makes the code simpler and more easy to maintain.
LikeLike