I am no rails expert, in fact, I had a bit of a problem with helpers, had to look into it and eventually, ask some questions and get answer on the Rails group before I could fix the issue, I thought I would give back to the community on what I learned.
Helpers Rule.
That’s right, helpers are you friend, there isn’t much to say else, except they are dead useful ideas. Helpers save you loads of time and keep your code DRY, as opposed to wet
As you may not know, DRY stands for Don’t Repeat Yourself, and along with Convention over Configuration, it’s the core mantra of every rails programmer. How do they work? Simple enough, they are basically functions held in a centralized location, either in a controllers helper, or in the main Application helper, based on where you will need them.
If you only need them to be accessible from on controller and it’s helpers, then you put them in the ControllerName_helper.rb file that you either create, or was created for you with scaffold.
If you need something that is accessible from any view/controller, then you will be putting it in helpers/application_helper.rb
Let’s take an example. Let’s say you have a central image location for thumbnails and one for large images on another server, instead of hard coding that path into every view, you can create a helper that does it for you.
def my_image_path(args)
args[:version] ||= ''
return_value = ''
if args[:version] == 'thumb' then
return_value = 'http://images.myhost.com/thumbnails/'
elsif args[:version] == 'large' then
return_value = 'http://images.myhost.com/large_images/'
end
return return_value
end
Then you just pass it in a view like this:
<%= my_image_path :version => ‘thumb’ %>
Pretty cool huh?
You’ll notice something, in this example, I could have just done it this way:
def my_image_path(args)
args[:version] ||= ''
if args[:version] == 'thumb' then
'http://images.myhost.com/thumbnails/'
elsif args[:version] == 'large' then
'http://images.myhost.com/large_images/'
end
end
You can do that, however, as I learned the hard way, it can have unexpected results. I will tell you what wasn’t told to me,
just because you can do it, doesn’t mean you should, you should always explicitly return something!
Of course the above example isn’t so powerful, but, with this one, you can see the greatness of the helper.
I have a website that has products separated into categories of an infinite depth, that means, it can have as many subcategories
as it needs or wants. You can add products to multiple categories, so I need to generate a list box. Because of the
various different extra goodies, I need to be able to display this list box in several different views, for instance
the new and edit pages, but also a move, and list page so that multiple items can be moved into a selected category at once.
Now, I could write that code in every single view, but when I need to make a change, I have to edit like 5 different files, that’s
no good, and all of a sudden, my code is no longer DRY, enter Helpers to the rescue.
def category_list_options(args)
args[:category_id] ||= 0
args[:spacer] ||= ' '
@categories = Category.find(:all,:conditions => ['category_id = ?',args[:category_id]])
ret = ''
for category in @categories do
ret = ret + '<option value="' + category.id.to_s + '">' + args[:spacer] + ' ' + category.title + '</option>'
if category.categories.length > 0 then
ret = ret + category_list_options(:category_id => category.id,:spacer => args[:spacer] + args[:spacer])
end
end
return ret
end
Then I just call it in a view with:
<%= category_list_options :spacer => ' ' %>
Now of course, I can expand that, for instance, I can rewrite it to show them as table rows, like this:
def category_list_table(args)
args[:category_id] ||= 0
args[:spacer] ||= ' '
@categories = Category.find(:all,:conditions => ['category_id = ?',args[:category_id]])
ret = ''
for category in @categories do
ret = ret + "<tr><td>#{args[:spacer]} #{category.title}</td></tr>\n"
if category.categories.length > 0 then
ret = ret + category_list_table(:category_id => category.id,:spacer => args[:spacer] + args[:spacer])
end
end
return ret
end
I can call the whole tree like so:
<%= category_list_table :spacer => ‘ ’ %>
Or, when customers are browsing a certain category, I can grab all the ones down that chain like so:
<%= category_list_table :category_id => current_category.id,:spacer => ‘ ’ %>
This all assumes that you have a model that belongs to itself, with a foreign key in the table.
class Category < ActiveRecord::Base
has_many :categories
belongs_to :category
end
From then on, whenever we create a category, we set main categories as having a :category_id of 0, that way
we know this category belongs to no category, everything else is a subcategory
So you see, helpers really really rule. And this isn’t the half of it, I am sure I will be back to add more
code samples and ideas when I find new uses for helpers…
Ohh, I forgot…
One other cool factor, is javascript helpers, writing js is a total bitch. On a Pizza Shop’s website, which is basicallly
an online menu, I needed to have a js popup window to show the product and ingredients. I just added a little helper in
application_helper.rb like this:
def javascript_popup_window(args)
args[:height] ||= '600'
args[:width] ||= '300'
args[:name] ||= 'javascript_window'
args[:toolbar] ||= '0'
args[:statusbar] ||= '0'
args[:location] ||= ''
return "window.open('#{args[:location]}','#{args[:name]}','status=#{args[:statusbar]},toolbar=#{args[:toolbar]},height=#{args[:height]},width=#{args[:width]}')"
end
I know, the world’s longest line, but it’s straight forward. All I need to do is call it with:
<a onclick="<%= javascript_popup_window :location => '/controller/action/id' %>">Open</a>
Baddaboom, works like a charm.
You can also do something like this for a scriptaculous autocompleter:
def scriptaculous_autocomplete(args)
scriptaculous = "Event.observe(window, 'load' , function() {new Autocompleter.Local('#{args[:field]}' , '#{args[:div]}' , #{args[:name]});});"
array = ''
args[:values].each do |v|
array = array + "'#{v}',"
end
return "<script>#{scriptaculous}\n var #{args[:name]} = [#{array}''];</script>\n<div id=\"#{args[:div]}\"></div>"
end
Then you call it like so:
<%= scriptaculous_autocomplete (:field => 'item_title',
:div => 'item_title_choices',
:name => 'item_titles',
:values => ['and','one','two','three','four']) %>
Of course, that’s just off the top of my head. To learn more about scriptaculous, and Prototype, why no head over
to pragmatic programmer and pick up their book on the subject.
As for Helpers, check out prag’s agile webdevelopment book, it’s a scorcher.
Anyway, that’s all for now, let me know if there are any errors, most of this code is used by me in actual projects.
Also, if you have a better, cleaner, more robust way, send it to me and I’ll plop it up here.