So far, this chapter has shown you how to use a number of the helper methods that come with Rails. You can also create your own helper methods. There are lots of good reasons to do so:
You can come closer to Rails’ DRY (Don’t Repeat Yourself) ideal if you can combine multiple pieces into a single invocation.
You can see more obviously which pieces of your code are actually doing the same work when they call the same method, instead of perpetually reinventing the wheel.
The same code used in multiple places will rarely stay identical.
Multiple views within an application can reference the same helper methods.
Creating helper methods might not be your very first priority in creating an application, but once you have a basic idea of what you want to create in your views, it’s a good idea to start assembling common tasks into helper methods.
Within the application directory structure, helper methods go into
the app/helpers directory. At this point, the guestbook application will
have two files there: application_helper.rb and people_helper.rb.
Helper methods that are defined in application_helper.rb are available to
views throughout the entire Rails application, whereas methods defined
in people_helper.rb are only
available to views that pertain to operations on the person
model. For now,
the helper methods built in this section can go in people_helper.rb and graduate to application_helper.rb if you think they’re
worth sharing across the application.
If you have helper methods with the same names in people_helper.rb and in application_helper.rb, the method in people_helper.rb will take precedence.
The first helper method will take the Example 6-4 code for
generating radio buttons from a hash. Example 6-6 shows what’s left
when this is reduced to a call to the buttons
helper method.
Example 6-6. Creating a sorted set of linked radio buttons from a hash
<% nations = { 'United States of America' => 'USA', 'Canada' => 'Canada', 'Mexico'
=> 'Mexico', 'United Kingdom' => 'UK' }%>
<p>
<b>Country</b><br />
<%= buttons(:person, nations)
%>
</p>
The buttons
method is in the
people_helper.rb file, the contents
of which are shown in Example 6-7.
Example 6-7. Creating a sorted set of linked radio buttons from a hash
1 module PeopleHelper 2 3 def buttons(model_name, target_property, button_source) 4 html='' 5 list = button_source.sort 6 list.each do|x| 7 html << radio_button(model_name, target_property, x[1]) 8 html << h(x[0]) 9 html << '<br />' 10 end 11 return html 12 end 13 14 end
There’s a lot going on in the buttons
method. It’s contained by the PeopleHelper
module, which was originally
empty in the version created by the scaffolding. Lines 2 through 13 are
all new additions. This version of buttons
, defined starting on line 3, looks
more like the older version of the helper functions, taking a model name
as its first argument, then the targeted property, and then the source
from which the radio buttons will be created.
Because the helper function isn’t in the view, there isn’t any ERb markup here. Instead, the helper function builds a string, starting in line 4. Often, the first declaration of the string includes the first tag, but as the radio buttons don’t have a containing element, this starts with the empty string. Lines 5 and 6 are the same logic for sorting the hash as was used in the original code from Example 6-3, but the contents of the loop, in lines 7 to 9, are very different.
Lines 7 through 9 all append something to the html
variable, using the <<
operator. Line 7 appends radio button
markup created through Rails’ radio_button
helper. Line 8 appends the text
the user will see, and line 9 appends a <br
/>
tag, putting a line break between the buttons. Rails
developers often avoid mixing explicit markup with code, preferring to
use content_tag
or other helper
methods—but you can use markup here if you think it’s
appropriate.
Line 10 just closes the loop over the hash, but line 11 is a bit
unusual. Explicit return statements aren’t necessary in Ruby
methods unless you’re returning multiple results or want to break at an
unexpected time. Ruby will assume that the last variable you touched is
the return value. However, using return
is a good way to avoid surprises, and
if you feel like writing briefer code, you can leave off return
and just write html
there.
If you leave off line 11 completely, however, you’ll have an
unpleasant surprise, shown in Figure 6-6. It looks like
html
was the last variable touched in
line 9, but the each
loop block,
which closes in line 10, is actually considered the last thing touched.
The value of the block is the underlying array, which gets mashed
together to yield this unfortunate
result.
A more sophisticated helper method, shown in Example 6-8, could check the
list of items to select from, and decide whether to represent it as a
radio buttons or a list, depending on length. It adds an
extra if
statement,
highlighted in the code. This may or may not be a level of smarts you
want to build into your helper methods, but it certainly demonstrates
how custom helper methods can assemble just a little more logic for
your views.
Example 6-8. A helper method that chooses between radio buttons and selection lists
def button_select(model_name, target_property, button_source) html='' list = button_source.sortif list.length <4
list.each {|x| html << radio_button(model_name, target_property, x[1]) html << h(x[0]) html << '<br />' }else
html << select(model_name, target_property, list)
end
return html end end
There are a lot more things you could do in a helper method, from adding labels to your form components (addressing a complaint from the previous section) to handling calculations.
While the helper method in Example 6-8 works, its
foundation is a loop that builds a long string of HTML, using the
<<
operator to concatenate additional content. Example 6-9 is a slightly
more idiomatic Ruby version, skipping the creation of an explicit
html
variable and letting Ruby’s
default handling of return values handle what gets sent back to the
page.
Example 6-9. A more elegant version of the helper method
def button_select(model_name, target_property, button_source) list = button_source.sort if list.length < 4 list.collect do |item| radio_button(model_name, target_property, item[1]) + h(item[0]) end.join('<br />') else select(model_name, target_property, list) end end