Disambiguate rails helpers

Posted by Eric Torrey

Jul 24

Here at thoughtbot we strive to make explicit, agile code. We know the specification changes, it’s what we’re machined for. The place that changes the most, and is usually the hardest to maintain are the views. As my comrade pointed out the views usually run wild, so we use helper methods to prevent a long method chain, and keep things a little more readable. Sometimes however that usually leads to methods that aren’t used, or methods that are duplicated.

For example. Let’s assume you have a Blog, that belongs to a User, and has many Posts. Pretty standard right? There is no requirement for the Blog to have it’s own title so instead we use a helper method. On blogs#show we use a Blog helper method defined here:

1
2
3
4
5
6
7
module BlogsHelper

  def display_title_for(blog)
    "#{blog.user.name} - The Blog!"
  end

end

Good, now when a user clicks on a Post for that Blog, we get to posts#show. At the top of the page, we want to display the title of the Blog. So we try to call #display_title_for and get a NameError, something along the lines of undefined local variable or method.

We can’t use the method in the BlogsHelper from the Post’s views yet. We could go into the Post’s controller and add this:

1
2
3
4
5
6
7
8
9
class PostsController < ApplicationController

  helper :blogs

  def show
    @post = Post.find params[:id]
  end

end

I don’t like this solution, sure your being DRY, but your not being explicit. What happens when someone wants to change the display of the Blog’s title, and they are looking at the posts#show view? They will immediately look for a display_title_for(blog) in the Post’s helper – too bad it’s not there.

Another solution would be to duplicate the method on the PostsHelper. Although this also is a poor solution, if someone wants to change the way the blog title is being displayed, it will need to be changed in two spots.

So, I want helpers to be called explicitly, and defined explicitly. I want to know what is being called, and where it’s being called from. Let’s redefine the display_title_for(blog) on the BlogsHelper:

1
2
3
4
5
6
7
module BlogsHelper

  def self.display_title_for(blog)
    "#{blog.user.name} - The Blog!"
  end

end

Now that we have defined it as a module method we can use it in the post#show view like so.


  BlogsHelper.display_title_for(@post.blog)

Now we have explicit, dry, maintainable code.


Comments on this post

Floyd

Jul 24

Floyd said,

I think that as long as your method doesn’t depend on helpers already in ActionView, you should just put these things in the model, especially when they’re actually just attribute massages:

<filter:code> def title ”#{user.name} – The Blog!” end </filter>
Tammer Saleh

Jul 24

Tammer Saleh said,

I agree with Floyd, but assuming the method should be in the helpers file, I usually just put it in the application_helper file. I haven’t found a good number of helpers that are actually called from only one controller’s views (as this shows).

This obviously isn’t perfect, as I usually end up with a pretty large application_helper file.

Geoff Buesing

Jul 24

Geoff Buesing said,

I’ve found that I rarely need helpers for just one controller—if I write a helper for, say, pagination, I want to use that across the app. End result is, as Tammer points out, a large, ungainly ApplicationHelper file.

Recently I’ve been using the Edge Rails helper :all declaration, which you can declare in your ApplicationController; this loads all helper files for all controllers. With this, I restructured my helper files by topic (JavaScriptHelper, FormHelper, TimeHelper, etc.) and my code is now much better organized.

Tom

Jul 24

Tom said,

The other option is to create new helper files delineated not by controller but by theme: eg image_helper.rb, formatting_helper.rb and then including them with the #helper method. Of course, you still have to look at the controller rather than the helper to find out what’s going on, but you should be doing that anyway: one helper per controller is just not the right way to go about stuff.

I’ve done “one big application_helper.rb” before, and it just gets ugly. Organising thematically at least gives other developers a good idea of where some methods might reside.

jare care

Jul 24

jare care said,

Putting all you helper methods in ApplicationHelper is a terrible idea.

Modularization/separation of concerns is such a basic concept of software. That’s why there’s separate helpers for each controllers, that’s why there’s MVC.

After putting all your helpers in one file whats next? Putting all your actions in ApplicationController?

Put your helper methods where they belong and where every developer would expect them to be and look first. Using module methods to support this eric isn’t a bad idea.

Oliver Nicholas

Jul 24

Oliver Nicholas said,

I really like this idea, but one issue I ran into is that named routes don’t appear to be available from the module context. So you can’t use this for methods with might be producing a link using your named routes.

Is there some way to import the route methods into the…”class context” of the module, so they’re available without the module getting mixed into an ActionPack descendant?

matt jankowski

Jul 24

matt jankowski said,

I think that separating things somehow (whether it’s per-controller or per-functional-area) is clearly more correct than just dumping everything into ApplicationHelper. But there are really two questions here…

1. A strategy for separating everything in a maintainable and rational way 2. How to best use rails loading rules to make that code available for you to use

Dumping everything into application helper is definitely a symptom of letting question 2 dictate your answer to question 1 —but I think using #helper to pull in thematically separate helper files from your controllers (even from ApplicationController) is totally fine.

Christian Romney

Jul 25

Christian Romney said,

I’m not crazy about this idea. Obviously dumping everything into Application Helper is a smell, but for truly generic view code like link_to_gravatar or page_title, I think it’s perfectly acceptable.

Another option to consider is to override to_s for an object or to use a mixin for those models where you need this behavior. What bothers me about this solution is that readability is sacrificed. BlogsHelper.display_title_for(@post.blog) is not nearly as readable as title_for(@post.blog) or even simply @post.blog (implicit to_s called).

In general, I despise most classes with names ending in helper, manager, broker and utility. Often these are a sign of bad OO practices like anemic domain models and inappropriate intimacy where one object messes around with another’s internals. At least the way Rails handles helpers is somewhat transparent in that views just have access to the methods. In fact, I find it useful to think of Rails helpers as tag libraries since it keeps me focused on adding only generic view code to them.

Floyd

Jul 25

Floyd said,

I despise most classes with names ending in helper, manager, broker and utility.

I agree completely. OO programmers seem blind to the fact that too many classes is just as bad as too few, and class names are great way to evaluate at a glance how well-designed a system is. That’s why I say use helpers as if they were hacks—only when something doesn’t work in the model or view.

Shadowfiend

Jul 25

Shadowfiend said,

Is there a problem I’m not aware of with just using include to include one of the other helper modules into this controller’s helper? A code example from one of my applications:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module CommitteesHelper
  include DocumentsHelper

  # Takes a list of StaffMembers who are directors for a committee and extra    cts
  # those that are ADs.
  def ads_from( director_list )
    director_list.select do |dir|
      dir.position.include? 'Assistant Director'
    end
  end

  # Takes a list of StaffMembers who are directors for a committee and extra    cts
  # those that are Directors (rather than ADs).
  def directors_from( director_list )
    director_list.select do |dir|
      ! dir.position.include? 'Assistant Director'
    end
  end
end

Sorry, comments are closed for this article.

© 2000 - 2009 by thoughtbot, inc.
written by a bushel of tiny robots