Designing Helpers in Ruby on Rails

Designing Helpers in Ruby on Rails – Introduction

I assume that many of you have written (and still writing) helpers for your views in your awesome Rails projects. A helper, as you all know, is a module which contains methods that are vital for the simplification and the dryness of your views. And because helpers are meant to be used within your views they often contain html code (either plain or ruby code that produces html) which, as you may have noticed, is not very flexible.

A simple example is that you have written an awesome helper method in your ApplicationHelper that does some magic (but unfortunately contains html) and you want to use the same method in another view but you are bound to use the same html your helper contains (or to avoid that you have to write some ugly code in there!).

Designing Helpers in Ruby on Rails – Detaching

The agile way to follow here is to detach the work of templating a helper method from the method itself. We can achieve this by constructing our little KickAssTemplate class that will be responsible for templating our helpers (and as you will see not only that!). We create a file lib/kick_ass_template.rb and a base class

class KickAssTemplate
    
  def initialize(object)
    @object = object
  end
    
  def prepare
    # Hook method
    # does all the work of the html generation
  end
    
  def html
    # Hook method
    # does the work of html rendering
  end
    
end

As you have noticed I didn’t write any serious code in there, just a bunch of hook methods and a pretty generic initialize method. You can follow a design pattern here that suits you the best, but for our example we will use the Template Method pattern, kinda fits the name don’t you think? ;).

Designing Helpers in Ruby on Rails – Application

Ok so far so good. Now, to continue, we have to have a real problem. So lets make one! Suppose that you are a developer at stackoverflow and also suppose that stackoverflow is written in ruby :P. A task of yours is that you must create a helper that renders all the recent badges, users awarded with. You also know that this badge module will occur in different places inside the website and with different layouts, a small, a medium and a large in size (time for your KickAssTemplate). So you first construct a SmallTemplate class


Note: I will use Nokogiri for the generation of the html (I prefer this approach but you can do it your way).


class SmallTemplate < KickAssTemplate
    
  def initialize(label, hash)
    # the hash will be something like: 
    # {link_to_badge => link_to_user} see stackoverflow.com
    @label = label #the label for the module e.g. "Recent Badges"
    super(hash)
  end
    
  def prepare
     # info: this is a technique for generating 
     # a document with multiple root nodes with Nokogiri
     @html = Nokogiri::HTML::DocumentFragment.parse ""
     Nokogiri::HTML::Builder.with(@html) do |doc|
       doc.div(:class => "smallLabel"){ @label }
       doc.div(:class => "smallBody"){
         doc.ul(:class => "smallList"){
           @object.each_pair do |obj1, obj2|
              doc.li(:class => "smallLink"){
                 obj1+" "+obj2
              }
           end
         }
       }
     end
  end
    
  def html
    CGI::unescapeHTML(@html.to_html.html_safe)
  end
    
end

Similarly constructing classes that handle the medium sized module and the large sized module you finish the first part. The second part is actually constructing the helper. Suppose that you have a Badging model with a user_id and a badge_id column that references a Badge and a User. And to get the recent badgings (because that’s what you need) you write (because you are awesome) a scope

class Badgings < ActiveRecord::Base

    scope :recent, where("created_at < ?", 3.days.ago)

end

and in your controller you create the hash of a form that will be handy i.e. {badge_1 => user_1, badge_2 => user_2, …}

class SomeController < ApplicationController
    
    def some_action
       # code in here
       @badgings = Hash[*Badgings.recent.map{|b| [b.badge, b.user]}.flatten]
    end

end

Thus your helper method should read something like the following

module ApplicationHelper
    
    def render_module(*args)
        options = args.extract_options!

        # set the hash to be the first argument
        hash = args.first

        # choose the template and give one default as well as the label
        template_class = options[:template]||MediumTemplate
        label = options[:label]
 
        # create the hash of the form {link_to_badge => link_to_user}
        link_hash = {}
        hash.each_pair do |k,v|
            link_hash.merge!({
                    link_to(k.name, k) => link_to(v.name, v)
            }) 
        end

        # render the module
        template = template_class.new(label, link_hash)
        template.prepare
        template.html
    end

end

Finally in your view you call the helper as

<%= render_module @badgings, :label => "Recent Badges", :template => SmallTemplate %>

Designing Helpers in Ruby on Rails – Flexibility

To show you how flexible this design is, suppose that you get assigned to a new task which is to create a new module that uses the same layouts as the Badges module (i.e. small, medium and large) but displays the recent tags together with their count. The changes you have to make are very very little. In fact you only have to change the render_module helper as

module ApplicationHelper
    
    def render_module(*args)
        options = args.extract_options!

        # set the hash to be the first argument
        hash = args.first

        # choose the template and give one default as well as the label
        template_class = options[:template]||MediumTemplate
        label = options[:label]
 
        # create the hash of interest
        link_hash = {}
        hash.each_pair do |k,v|
            link_hash.merge!({
                    link_to(k.name, k) => hash.first.first.is_a?(Badging) ? link_to(v.name, v) : v
            }) 
        end

        # render the module
        template = template_class.new(label, link_hash)
        template.prepare
        template.html
    end

end

And as you may have guessed in the view (assuming you have available a @taggings hash of the form {tag_1 => count_1, …})

<%= render_module @taggings, :label => "Recent Tags", :template => LargeTemplate %>

1 Comment Designing Helpers in Ruby on Rails

Leave a Reply to daniel Cancel reply

Your email address will not be published. Required fields are marked *


*