The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
In defense of helpers

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
john hume

Posts: 82
Nickname: duelnmrkrs
Registered: Oct, 2005

John Hume is a developer and consultant with ThoughtWorks.
In defense of helpers Posted: May 14, 2008 8:51 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by john hume.
Original Post: In defense of helpers
Feed Title: El Humidor
Feed URL: http://feeds.feedburner.com/ElHumidor
Feed Description: John D. Hume on Ruby and development with agile teams.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by john hume
Latest Posts From El Humidor

Advertisement

...in which I defend helpers[1] as good OO, if you use them just so; point out an aspect of the convention that stands in the way of that style; and provide an alpha plugin that tries to change that.

By Rails convention helper modules are where view logic belongs. During request processing, a controller will automatically look for a module in the helpers directory with a name matching the controller. If found, the module will be mixed in to the controller's response's template object, an instance of ActionView::Base (and self in the rendered erb template). A controller can also specify additional helper modules to mix in using the helper class method.

The typical approach I've seen is to define helper methods that take model objects or their attributes as arguments (where the model was typically put into an instance variable by the controller). So the template does something like

<span class='contributors'>
  <%= contributors_list @project.contributors %>
</span>

to use a helper like this

module ProjectsHelper
  
  TOO_MANY = 10
  
  def contributors_list contributors
    if contributors.size < TOO_MANY
      contributors.to_sentence
    else
      contributors[0...(TOO_MANY - 1)].join(', ') + ', and more'
    end
  end
end

I think it's because of this functional style of helper method that I've seen a fair amount of bias against helpers. OO developers like encapsulation, and helper modules generally encapsulate logic but not the information needed to apply that logic.

For example, the first Rails project I was on didn't use any application helpers. The team had created a parallel construct called presenters. The "final" state of the presenter stuff evolved over months of development, but by the end, a page-specific presenter object was always made available in the @presenter instance variable (thanks to some frameworkey extensions in our ApplicationController based on a naming convention), and eventually a method_missing was monkey-patched into ActionView::Base to automatically delegate everything to @presenter so our templates weren't cluttered.

By the time the method_missing went in, we'd come back around to something very close to Rails' built in helpers, and I had a little bit of an aha moment. The helper is the page (because it's mixed in), I thought, why would I pass it my instance variables?

The approach of taking in arguments for things that could have been pulled from instance variables is consistent with a general rule in Ruby that modules ought not to mess with instance variables if they can avoid it. This rule makes good sense in general-purpose modules (like Enumerable or Comparable) because by design these modules are meant to be mixed in to all sorts of objects, and they don't want to put weird constraints on their hosts. (Imagine if the documentation for Enumerable told you "in addition to providing an #each method, the object should take care to avoid using instance variables called @_cursor, @_enumerators, @...." No one would like that.)

Helper modules aren't like that though. They're designed for a specific page or set of pages (i.e., a specific view concern) in your application. The only reason they're modules rather than classes is that you might have multiple view concerns on the same page.

So I thought it might be interesting to let the helper know more and the template know less about Ruby by moving knowledge of the controller-exposed instance variables into the helper. It worked and felt good.

For a while.

Then I realized that the helper wasn't a page definition: it was a few of them. Since all the actions on a controller get the controller's helpers mixed in, a typical controller would have listing pages, detail pages, and edit pages all with the same helper modules.

Blast!

So I wanted helpers to be selected per action rather than controller-wide. But they weren't. So after talking about it for a while, last Friday morning I finally rolled a Rails plugin to make it the way I wanted: it's called ActionHelper and you can find it on GitHub.

For the moment, it does the naming convention thing that you'd probably expect: when processing UsersController's show action, the module UsersShowHelper will be mixed in to the template if it exists. It also allows actions to declare what helper modules they want by calling action_helper inside the action. (You'd expect a class method, and I agree there should be one, but I haven't figured out a pretty API yet, so for now it's not there.) See the README for an actual example.

If you have thoughts on a pretty declarative class method API for this (whether it's annotation-style or more Railsey), call it out in the comments. Better yet, fork the repository on GitHub and send me a pull request once you've got something going. (ActionHelper has been my "get comfy with git" mini-project.)

Thanks for reading.


1^ Note that I'm talking about the helpers in your application, not the ones Rails provides in ActionView::Helpers. Those are general-purpose modules.

Read: In defense of helpers

Topic: Off to Potsdam: Attending S3 Previous Topic   Next Topic Topic: Testing: Duplicate Code in Your Tests

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use