“Thank you for helping Helpers Helping the Helpless. Your help was very... helpful!”
— Mrs. Duong in the movie The Weekenders
Throughout the book so far, we’ve already covered some of the helper methods provided by Rails to help you assemble the user interface of your web application. This chapter lists and explains all of the helper modules and their methods, followed by instructions on effectively creating your own helpers.
This chapter is essentially reference material. Although every effort has been made to make it readable straight through, you will notice that coverage of Action View’s helper modules is arranged alphabetically, starting with ActiveModelHelper
and ending with UrlHelper
. Within each module’s section, the methods are broken up into logical groups whenever appropriate.
This chapter is published under the Creative Commons Attribution-ShareAlike 3.0 license, http://creativecommons.org/licenses/b-sa/3.0/
ActiveModelHelper
The ActiveModelHelper
module contains helper methods for quickly creating forms from objects that follow Active Model conventions, starting with Active Record models. The form
method is able to create an entire form for all the basic content types of a given record. However, it does not know how to assemble user-interface components for manipulating associations. Most Rails developers assemble their own forms from scratch using methods from FormHelper
, instead of using this module. However, this module does contain some useful helper for reporting validation errors in your forms that you will use on a regular basis.
Note that as of Rails 3 you must install the following official plugin in order to use this module.
rails plugin install git://github.com/rails/dynamic_form.git
The error_message_on
and error_messages_for
methods help you to add formatted validation error information to your templates in a consistent fashion.
error_message_on(object, method, *options)
Returns a div
tag containing the error message attached to the specified method on the object, if one exists. It’s useful for showing validation errors inline next to the corresponding form field. The object
argument of the method can be an actual object reference or a symbol corresponding to the name of an instance variable. The method
should be a symbol corresponding to the name of the attribute on the object for which to render an error message.
The contents can be specialized with options for pre- and post-text and custom CSS class.
:prepend_text => string
Fragment to prepend to error messages generated.
:append_text => string
Fragment to append to error messages generated.
:css_class => class_name
CSS class name for div
generated wrapping the error message. Defaults to formError
.
Use of this method is common when the user-interface requirements specify individual validation messages per input field of a form, as in the following real-life example:
As in the example, the error_message_on
helper is most commonly accessed via the form
block variable of form_for
and its variants. When used via the form
variable, you leave off the first argument (specifying the object) since it’s implied.
error_messages_for(*params)
Returns a div
tag containing all of the error messages for all of the objects held in instance variables identified as parameters.
As in the example, the error_message_for
helper is most commonly accessed via the form
block variable of form_for
and its variants. When used via the form
variable, it is called error_messages
and you leave off the first argument (specifying the object) since it’s implied.
This method is used by Rails scaffolding, but rarely in real production applications. The Rails API documentation advises you to use this method’s implementation as inspiration to meet your own requirements:
This is a prepackaged presentation of the errors with embedded strings and a certain HTML structure. If what you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors instance yourself and set it up. View the source of this method to see how easy it is.
We’ll go ahead and reproduce the source of the method here with the warning that you should not try to use it as inspiration unless you have a good grasp of Ruby! On the other hand, if you have time to study the way that this method is implemented, it will definitely teach you a lot about the way that Rails is implemented, which is its own distinctive flavor of Ruby.
Later on in the chapter we’ll talk extensively about writing your own helper methods.
The next couple of methods are used for automatic field creation by the scaffolding code. You can try using them too, but I suspect that their usefulness is somewhat limited in real applications.
form(name, options)
Returns an entire form with input tags and everything for a named model object. Here are the code examples given in the Rails API documentation, using a hypothetical Post
object from a bulletin-board application as an example:
Internally, the method calls record.new?
to infer whether the action for the form should be create
or update
. It is possible to explicitly specify the action of the form (and the value of the submit button along with it) by using the :action
option.
If you need the form to have its enctype
set to multipart
, useful for file uploads, set the options[:multipart]
to true
.
You can also pass in an :input_block
option, using Ruby’s Proc.new
idiom to create a new anonymous code block. The block you supply will be invoked for each content column of your model, and its return value will be inserted into the form.
That example’s builder block, as it is referred to in the Rails API docs, uses the input
helper method, which is also part of this module, and is covered in the next section of this chapter.
Finally, it’s also possible to add additional content to the form by giving the call to form
a block, as in the following snippet:
The block is yielded a string accumulator (named extra
in the example), to which you append any additional content that you want to appear between the main input fields and the submit tag.
input(name, method, options)
The appropriately named input
method takes some identifying information, and automatically generates an HTML input tag based on an attribute of an Active Record model. Going back to the Post
example used in the explanation of form
, here is the code snippet given in the Rails API docs:
To quickly show you the types of input fields generated by this method, I’ll simply reproduce a portion of the code from the module itself:
By default, when Rails marks a field in your form that failed a validation check, it does so by wrapping that field in a div
element, with the class name field_with_errors
. This behavior is customizable, since it is accomplished via a Proc
object stored as a configuration property of the ActionView::Base
class:
Armed with this knowledge, changing the validation error behavior is as simple as overriding Action View’s field_error_proc
attribute with your own custom Proc
.I would suggest doing so in an initializer file.
In Listing 11.1, I changed the setting so that the input fields with validation errors are prefixed with a red ERR message.
Listing 11.1. Custom validation error display
Many people have suggested that it would have been a much better default solution to simply add a field_with_errors
CSS class to the input tag itself, instead of wrapping it with an extra div
tag. Indeed, that would have made many of our lives easier, since an extra div
often breaks pixel-perfect layouts. However, since html_tag
is already constructed at the time when the field_error_proc
is invoked, it is not trivial to modify its contents.
There are some solutions out there that use regular expressions and modify the html_tag
string, for instance this one, found at http://snippets.dzone.com/tag/field_error_proc:
Ugly! This is certainly an area of Action View that could use improvement.
AssetTagHelper
According to the Rails API docs, this module
Provides methods for linking an HTML page together with other assets such as images, javascripts, stylesheets, and feeds. You can direct Rails to link to assets from a dedicated assets server by setting
ActionController::Base.asset_host
in yourenvironment.rb
. These methods do not verify the assets exist before linking to them.
The AssetTagHelper
module includes some methods that you will use on a daily basis during active Rails development, particularly image_tag
.
Some of the helper methods in this module help you add content to the head
element of your HTML document.
auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
Returns a link tag that browsers and newsreaders can use to autodetect an RSS or ATOM feed. The type can either be :rss
(default) or :atom
. Control the link options in url_for
format using the url_options
.
You can modify the link
tag itself using the tag_options
parameter:
:rel
Specify the relation of this link; defaults to "alternate"
.
:type
Override MIME type (such as "application/atom+xml"
) that Rails would otherwise generate automatically for you.
:title
Specify the title of the link; defaults to a capitalized type.
Here are examples of usages of auto_discovery_link_tag
as shown in the Rails API docs:
Because of the options provided, you could theoretically use auto_discovery_link_tag
to generate a link
tag for a favorite icon, the little image that displays in the browser’s address bar and bookmarks:
That said, there is very little reason, if any, to use the auto_discovery_link_tag
to generate a favorite icon link in this way, because the Rails incantation is longer and more complicated than typing the HTML code! Neither is there anything dynamic about the construction of this tag that requires helper logic.
I made a point of including this example to reinforce the lesson that it is not necessarily the Rails way to use helpers to generate static markup that you could otherwise go ahead and write yourself.
javascript_include_tag(*sources)
Returns a script
tag for each of the sources provided. You can pass in the filename (the .js
extension is optional) of JavaScript files that exist in your public/javascripts
directory for inclusion into the current page, or you can pass their full path, relative to your document root.
To include the Prototype and Scriptaculous JavaScript libraries in your application, pass :defaults
as the source. When you’re using :defaults
, ifan application.js
file exists in your public/javascripts
directory, it will be included as well. You can modify the attributes of the script
tag by passing a hash as the last argument.
javascript_path(source)
Computes the path to a JavaScript asset in the public/javascripts directory. If the source filename has no extension, .js
will be appended. Full paths from the document root will be passed through. Used internally by javascript_include_tag
to build the script path.
stylesheet_link_tag(*sources)
Returns a stylesheet link
tag for the sources specified as arguments. If you don’t specify an extension, .css
will be appended automatically. Just like other helper methods that take a variable number of arguments plus options, you can pass a hash of options as the last argument and they will be added as attributes to the tag.
stylesheet_path(source)
Computes the path to a stylesheet asset in the public/stylesheets
directory. If the source filename has no extension, .css
will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag
to build the stylesheet path.
This module also contains a series of helper methods that generate asset-related markup. It’s important to generate asset tags dynamically, because often assets are either packaged together or served up from a different server source than your regular content. Asset helper methods also timestamp your asset source urls to prevent browser caching problems.
The image_tag
method makes use of the image_path
method that we just covered earlier in the chapter. This helpful method determines the path to use in the tag. You can call a controller “image” and have it work as a resource, despite the seemingly conflicting name, because for its internal use, ActionView aliases the method to path_to_image
.
audio_path(source)
Computes the path to an audio asset in the public/audios
directory, which you would have to add yourself to your Rails project since it’s not generated by default. Full paths from the document root will be passed through. Used internally by audio_tag
to build the audio path.
audio_tag(source, options = {})
Returns an HTML 5 audio tag based on the source
argument.
image_path(source)
Computes the path to an image asset in the public/images
directory. Full paths from the document root (beginning with a “/”) will be passed through. This method is used internally by image_tag
to build the image path.
image_tag(source, options = {})
Returns an img
tag for use in a template. The source
parameter can be a full path or a file that exists in your public images directory. You can add additional arbitrary attributes to the img
tag using the options parameter. The following two options are treated specially:
:alt
If no alternate text is given, the filename part of the source is used, after being capitalized and stripping off the extension.
:size
Supplied as widthxheight
so "30x45"
becomes the attributes width="30"
and height="45"
. The :size
option will fail silently if the value is not in the correct format.
video_path
Computes the path to an audio asset in the public/videos
directory, which you would have to add yourself to your Rails project since it’s not generated by default. Full paths from the document root will be passed through. Used internally by video_tag
to build the video src path.
video_tag(sources, options = {})
Returns an HTML 5 video
tag for the sources
. If sources
is a string, a single video tag will be returned. If sources
is an array, a video
tag with nested source
tags for each source will be returned. The sources
can be full paths or files that exists in your public videos directory.
You can add normal HTML video element attributes using the options
hash. The options
supports two additional keys for convenience and conformance:
:poster
Set an image (like a screenshot) to be shown before the video loads. The path is calculated using image_path
:size
Supplied as widthxheight
in the same manner as image_tag
.
By default, Rails links to assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated asset server by setting ActionController::Base.asset_host
in a configuration file, typically in config/environments/production.rb
so that it doesn’t affect your development environment. For example, you’d define assets.example.com
to be your asset host this way:
config.action_controller.asset_host = "assets.example.com"
The helpers we’ve covered take that into account when generating their markup:
Browsers typically open at most two simultaneous connections to a single host, which means your assets often have to wait for other assets to finish downloading. You can alleviate this by using a %d
wildcard in the asset_host
. For example, "assets%d.example.com"
. If that wildcard is present Rails distributes asset requests among the corresponding four hosts “assets0.example.com”, ..., “assets3.example.com”. With this trick browsers will open eight simultaneous connections rather than two.
To do this, you can either setup four actual hosts, or you can use wildcard DNS to CNAME the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from your hosting provider. Note that this technique is purely a browser performance optimization and is not meant for server load balancing.1
Alternatively, you can exert more control over the asset host by setting asset_host
to a proc like
config.action_controller.asset_host = Proc.new { |source|
"http://assets#{rand(2) + 1}.example.com"
}
The example generates http://assets1.example.com
and http://assets2.example.com
randomly. This option is useful for example if you need fewer/more than four hosts, custom host names, etc. As you see the proc takes a source
parameter. That’s a string with the absolute path of the asset with any extensions and timestamps in place, for example /images/rails.png?1230601161
.
Alternatively you may ask for a second parameter request
, which is particularly useful for serving assets from an SSL-protected page. The example below disables asset hosting for HTTPS connections, while still sending assets for plain HTTP requests from asset hosts. If you don’t have SSL certificates for each of the asset hosts this technique allows you to avoid warnings in the client about mixed media.
For easier testing and reuse, you can also implement a custom asset host object that responds to call
and takes either one or two parameters just like the proc.
By default, Rails appends an asset’s timestamps to all asset paths. This allows you to set a cache-expiration date for the asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp, which then updates the URL as the timestamp is part of that, which in turn busts the cache).
It’s the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take advantage of this feature. Here’s an example for Apache:
# Asset Expiration
ExpiresActive On
<FilesMatch ".(ico|gif|jpe?g|png|js|css)$">
ExpiresDefault "access plus 1 year"
</FilesMatch>
Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must have their clocks synchronized. If one of them drifts out of sync, you’ll see different timestamps at random and the cache won’t work. In that case the browser will request the same assets over and over again even though they didn’t change. You can use something like the Live HTTP Headers plugin for Firefox to verify that the cache is indeed working.
With the cache_asset_timestamps
setting enabled, the asset tag helper methods will make fewer expensive file system calls. However, you will be prevented from modifying any asset files while the server is running. This setting defaults to true and you can modify it in an initializer with the following code:
config.action_view.cache_asset_timestamps = false
A handful of class methods in AssetTagHelper
relate to configuration and are intended for use in plugins. You can find more details about these method in Chapter 19, “Extending Rails with Plugins.”
• register_javascript_expansion
• register_javascript_include_default
• register_stylesheet_expansion
AtomFeedHelper
Provides an atom_feed
helper to aid in generating Atom feeds in Builder templates.
The options for atom_feed
are:
:language
Defaults to "en-US"
.
:root url
The HTML alternative that this feed is doubling for. Defaults to "/"
on the current host.
:url
The URL for this feed. Defaults to the current URL.
:id
The id for this feed. Defaults to tag:#{request.host},#{options[:schema date]}:#{request.requesturi.split(".")[0]}
:schema_date
The date at which the tag scheme for the feed was first used. A good default is the year you created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, 2005 is used (as an “I don’t care” value).
:instruct
Hash of XML processing instructions in the form {target => {attribute => value, ...}}
or {target => [{attribute => value, ...}, ]}
Other namespaces can be added to the root element:
Listing 11.2. app/views/posts/index.atom.builder
The Atom spec defines five elements that may directly contain xhtml content if :type => 'xhtml'
is specified as an attribute:
• content
• rights
• title
• subtitle
• summary
If any of these elements contain xhtml content, this helper will take care of the needed enclosing div and an xhtml namespace declaration.
The atom_feed
method yields an AtomFeedBuilder
instance. Nested elements also yield AtomBuilder
instances.
CacheHelper
This module only contains one method, named cache
. It is used to perform fragment caching of blocks within templates, without caching the output of an entire action as a whole. Rails also features page caching using the caches_page
method of controllers, where the entire output of an action is stored as a HTML file that the web server can serve without going through the Action Pack.
In contrast, fragment caching is useful when certain elements of an action change frequently or depend on complicated state, while other parts rarely change or can be shared among multiple parties. The boundaries of a fragment to be cached are defined within a view template using the cache
helper method. The topic is covered in detail in the caching section of Chapter 17, Caching.
CaptureHelper
One of the great features of Rails views is that you are not limited to rendering a single flow of content. Along the way, you can define blocks of template code that should be inserted into other parts of the page during rendering using yield
. The technique is accomplished via a pair of methods from the CaptureHelper
module.
capture(& block)
The capture
method lets you capture part of a template’s output (inside a block) and assign it to an instance variable. The value of that variable can subsequently be used anywhere else on the template.
- message_html = capture do
%div
This is a message
I don’t think that the capture
method is that useful on its own in a template. It’s a lot more useful when you use it in your own custom helper methods. It gives you the ability to write your own helpers that grab template content wrapped using a block. We cover that technique later on in this chapter in the section “Writing Your Own Helpers.”
content_for(name, & block)
We mentioned the content_for
method in Chapter 10 in the section “Yielding Content.” It allows you to designate a part of your template as content for another part of the page. It works similarly to its sister method capture
(in fact, it uses capture
itself). Instead of returning the contents of the block provided to it, it stores the content to be retrieved using yield
elsewhere in the template (or most commonly, in the surrounding layout).2
A common example is to insert sidebar content into a layout. In the following example, the link will not appear in the flow of the view template. It will appear elsewhere in the template, wherever yield :navigation_sidebar
appears.
- content_for :navigation_sidebar do
= link_to 'Detail Page', item_detail_path(item)
content_for?(name)
Using this method, you can check whether the template will ultimately yield any content under a particular name using the content_for
helper method, so that you can make layout decisions earlier in the template. The following example clearly illustrates usage of this method, by altering the CSS class of the body
element dynamically:
DateHelper
The DateHelper
module is used primarily to create HTML select
tags for different kinds of calendar data. It also features one of the longest-named helper methods, a beast peculiar to Rails, called distance_of_time_in_words_to_now
.
I guess that helper method name was too much of a mouthful, since at some point it was aliased to time_ago_in_words
.
The following methods help you create form field input tags dealing with date and time data. All of them are prepared for multiparameter assignment to an Active Record object. That’s a fancy way of saying that even though they appear in the HTML form as separate input fields, when they are posted back to the server, it is understood that they refer to a single attribute of the model. That’s some Rails magic for you!
date_select(object_name, method, options = {})
Returns a matched set of three select
tags (one each for year, month, and day) preselected for accessing a specified date-based attribute (identified by the method
parameter) on an object assigned to the template (identified by the object_name
parameter).
It’s possible to tailor the selects through the options hash, which accepts all the keys that each of the individual select builders do (like :use_month_numbers
for select_month
).
The date_select
method also takes :discard_year
, :discard_month
, and :discard_day
options, which drop the corresponding select
tag from the set of three. Common sense dictates that discarding the month select will also automatically discard the day select. If the day is omitted, but not the month, Rails will assume that the day should be the first of the month.
It’s also possible to explicitly set the order of the tags using the :order
option with an array of symbols :year
, :month
, and :day
in the desired order. Symbols may be omitted and the respective select
tag is not included.
Passing :disabled => true
as part of the options will make elements inaccessible for change (see Listing 11.3).
Listing 11.3. Examples of date_select
datetime_select(object_name, method, options = {})
Works exactly like date_select
, except for the addition of hour and minute select
tags. Seconds may be added with the option :include_seconds
. Along with the addition of time information come additional discarding options: :discard_hour
, :discard_minute
, and :discard_seconds
.
time_select(object_name, method, options = {})
Returns a set of select
tags (one for hour, minute, and optionally second) preselected for accessing a specified time-based attribute (identified by method
) on an object assigned to the template (identified by object_name
). You can include the seconds with :include_seconds
.
time_select("post", "sunrise")
time_select("post", "start_time", :include_seconds => true)
Sometimes you need just a particular element of a date or time, and Rails obliges you with a comprehensive set of individual date and time select helpers. In contrast to the date and time helpers that we just looked at, the following helpers are not bound to an instance variable on the page. Instead, they all take a date or time Ruby object as their first parameter. (All of these methods have a set of common options, covered in the following subsection.)
select_date(date = Date.today, options = {})
Returns a set of select
tags (one each for year, month, and day) preselected with the date provided (or the current date). It’s possible to explicitly set the order of the tags using the :order
option with an array of symbols :year
, :month
, and :day
in the desired order.
select_datetime(datetime = Time.now, options = {})
Returns a set of select
tags (one each for year, month, day, hour, and minute) preselected with the datetime. Optionally add a seconds field using the :include_seconds => true
option. It’s also possible to explicitly set the order of the tags using the :order
option with an array of symbols :year
, :month
, and :day
, :hour
, :minute
, and :seconds
in the desired order. You can also add character values for the :date_separator
and :time_separator
options to control visual display of the elements (they default to "/"
and ":"
).
select_day(date, options = {})
Returns a select
tag with options for each of the days 1 through 31 with the current day selected. The date can also be substituted for an hour number. Override the field name using the :field_name
option. It defaults to day
. The date
parameter may be substituted by a value from 1 to 31.
select_hour(datetime, options = {})
Returns a select
tag with options for each of the hours 0 through 23 with the current hour selected. The datetime
parameter can be substituted with an hour number from 0 to 23.
select_minute(datetime, options = {})
Returns a select
tag with options for each of the minutes 0 through 59 with the current minute selected. Also can return a select
tag with options by minute_step
from 0 through 59 with the 00 minute selected. The datetime
parameter can be substituted by a seconds value of 0 to 59.
select_month(date, options = {})
Returns a select
tag with options for each of the months January through December with the current month selected. By default, the month names are presented as user options in the drop-down selection and the month numbers (1 through 12) are used as values submitted to the server.
It’s also possible to use month numbers for the presentation instead of names, by setting :use_month_numbers => true
. If you happen to want both numbers and names, set the :add_month_numbers => true
. If you would prefer to show month names as abbreviations, set the :use_short_month key => true
. Finally, if you want to use your own month names, set the value of the :use_month_names key
in your options to an array of 12 month names.
Override the field name using the :field_name option
. It defaults to month
.
select_second(datetime, options = {})
Returns a select
tag with options for each of the seconds 0 through 59 with the current second selected. The datetime
parameter can either be a DateTime
object or a second given as a number.
select_time(datetime, options = {})
Returns a set of HTML select
tags (one for hour and minute). You can set :add_separator
key to format the output.
select_year(date, options = {})
Returns a select
tag with options for each of the 5 years on each side of the current year, which is selected. The five-year radius can be changed using the :start_year
and :end_year
options. Both ascending and descending year lists are supported by making :start_year
less than or greater than :end_year
. The date parameter can either be a Date
object or a year given as a number.
All of the select-type methods share a number of common options that are as follows:
• :discard_type
Set to true
if you want to discard the type part of the select name. If set to true, the select_month
method would use simply date
(which can be overwritten using :prefix
) instead of date[month]
.
• :field_name
Allows you to override the natural name of a select
tag (from day, minute, and so on).
• :include_blank
Set to true
if it should be possible to set an empty date.
• :prefix
Overwrites the default prefix of date
used for the names of the select
tags. Specifying birthday
would result in a name of birthday[month]
instead of date[month]
when passed to the select_month
method.
• :use_hidden
Set to true
to embed the value of the datetime into the page as an HTML hidden input, instead of a select
tag.
distance_in_time
Methods with Complex Descriptive NamesSome distance_in_time
methods have really long, complex descriptive names that nobody can ever remember without looking them up. Well, at least for the first dozen times or so you might not remember.
I find the following methods to be a perfect example of the Rails way when it comes to API design. Instead of going with a shorter and necessarily more cryptic alternative, the framework author decided to keep the name long and descriptive. It’s one of those cases where a nonprogrammer can look at your code and understand what it’s doing. Well, probably.
I also find these methods remarkable in that they are part of why people sometimes consider Rails part of the Web 2.0 phenomenon. What other web framework would include ways to humanize the display of timestamps?
distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options ={})
Reports the approximate distance in time between two Time
or Date
objects or integers as seconds. Set the include_seconds
parameter to true
if you want more detailed approximations when the distance is less than 1 minute. The easiest way to show what this method does is via examples:
The Rails API docs ask you to note that Rails calculates 1 year as 365.25 days.
distance_of_time_in_words_to_now(from_time, include_seconds = false)
Works exactly like distance_of_time_in_words
except that the to_time
is hard-coded to the current time. Usually invoked on created_at
or updated_at
attributes of your model, followed by the string ago
in your template, as in the following example:
%strong= comment.user.name
%br
%small
= distance_of_time_in_words_to_now(review.created_at)
ago
DebugHelper
The DebugHelper
module only contains one method, named debug
. Output it in your template, passing it an object that you want dumped to YAML and displayed in the browser inside PRE
tags. Useful for debugging during development, but not much else.
FormHelper
The FormHelper
module provides a set of methods for working with HTML forms, especially as they relate to Active Record model objects assigned to the template. Its methods correspond to each type of HTML input fields (such as text, password, select, and so on) available. When the form is submitted, the value of the input fields are bundled into the params
that is passed to the controller.
There are two types of form helper methods. The types found in this module are meant to work specifically with Active Record model attributes, and the similarly named versions in the FormTagHelper
module are not.
The core method of this helper is called form_for
, and we covered it to some extent in Chapter 3, REST, Resources, and Rails. The helper method yields a form
object, on which you can invoke input helper methods, omitting their first argument. Usage of form_for
leads to succinct form code:
= form_for offer do |f|
= f.label :version, 'Version'
= f.text_field :version
%br
= f.label :author, 'Author'
= f.text_field :author
The form for block argument is a form builder object that carries the model. Thus, the idea is that:
= f.text_field :first_name
gets expanded to
= text_field :person, :first_name
If you want the resulting params hash posted to your controller to be named based on something other than the class name of the object you pass to form_for
, you can pass an arbitrary symbol as the first argument and the actual record as the second argument:
= form_for :client, person, :url => { :action => "update" } do |f|
In that case, the following call to text_field
= f.text_field :first_name
would get expanded to
= text_field :client, :first_name, :object => person
form_for
OptionsIn any of its variants, the rightmost argument to form_for
is an optional hash of options:
:url
The URL the form is submitted to. It takes the same fields you pass to url_for
or link_to
. In particular you may pass here a named route directly as well. Defaults to the current action.
:html
Optional HTML attributes for the form
tag.
:builder
Optional form builder class (instead of ActionView::Helpers::FormBuilder
)
The preferred way to use form_for
is to rely on automated resource identification, which will use the conventions and named routes of that approach, instead of manually configuring the :url
option.
For example, if post
is an existing record to be edited, then the resource-oriented style:
= form_for post do |f|
is equivalent to
= form_for :post, post, :url => post_path(post),
:html => { :method => :put, :class => "edit_post",
:id => "edit_post_45" } do |f|
The form_for
method also recognizes new records, by calling new?
on the object you pass to it.
= form_for(Post.new) do |f|
expands to
= form_for :post, Post.new, :url => posts_path, :html => { :class =>
"new_post",
:id => "new_post" } do |f|
...
The individual conventions can be overriden by supplying an object argument plus :url
and/or :html
options.
= form_for(post, :url => super_post_path(post)) do |f|
You can create forms with namespaced routes by passing an array as the first argument, as in the following example, which would map to a admin_post_url
:
= form_for([:admin, @post]) do |f|
This is the equivalent (old-school) version of form_tag
, which doesn’t use a yielded form object and explicitly names the object being used in the input fields:
- form_tag people_path do
.field
= label :person, :first_name
= text_field :person, :first_name
.field
= label :person, :last_name
= text_field :person, :last_name
.buttons
= submit_tag 'Create'
The first version has slightly less repetition (remember your DRY principle) and is almost always going to be more convenient as long as you’re rendering Active Record objects.
If you explicitly specify the object name parameter for input fields rather than letting them be supplied by the form, keep in mind that it doesn’t have to match a live object instance in scope for the template. Rails won’t complain if the object is not there. It will simply put blank values in the resulting form.
The HTML generated by the form_for
invocations in the preceding example is characteristic of Rails forms, and follows specific naming conventions.
In case you’re wondering, the authenticity_token
hidden field with gibberish up near the top of the form has to do with protection against malicious cross-site request forgery (CSRF) attacks.
When this form is submitted, the params
hash will look like the following example (using the format reflected in your development log for every request):
Parameters: {"commit"=>"Create", "action"=>"create",
"controller"=>"persons",
"person"=> {"first_name"=>"William", "last_name"=>"Smith"}}
As you can see, the params
hash has a nested "person"
value, which is accessed using params[:person]
in the controller. That’s pretty fundamental Rails knowledge, and I’d be surprised if you didn’t know it already. I promise we won’t rehash much more basic knowledge after the following section.
If you were editing an existing instance of Person
, that object’s attribute values would have been filled into the form. That’s also pretty fundamental Rails knowledge. What about if you want to edit a new model object instance, prepopulated with certain values? Do you have to pass the values as options to the input helper methods? No. Because the form helpers display the values of the model’s attributes, it would simply be a matter of initializing the object with the desired values in the controller, as follows:
Because you’re only using new
, no record is persisted to the database, and your default values magically appear in the input fields.
That’s all well and good for editing one object at a time. What if you want to edit multiple records at the same time? When the attribute name passed to form_for
or individual input field helper methods contains a set of square brackets, the id for the object will be included in the autogenerated name
and id
attributes of the input tag.
I find this technique potentially challenging, on a couple of levels. First of all, we usually identify attribute names using symbols, but tacking a pair of square brackets onto a symbol (like :name[]
) is invalid. We’re forced to use a string to name the object instead:
= form_for "person[]" do |f|
- for @person in people
= f.text_field :name
Second, it generates HTML for the input tags looking something like
<input type="text" id="person_8_name" name="person[8][name]"
value="Obie Fernandez"/>
The structure of the hash submitted to the controller is significantly different from what we’re used to seeing. That nested params
hash will now be three levels deep when it comes to the "person"
and to make it more confusing, the ids of the objects are being used as has
keys:
Now the controller code to handle the form needs to change, or you’re likely to see a stack trace like the following one:
NoMethodError (undefined method `8=' for #<User:0x8762174>)
The good news is that the way that you handle that nested hash structure in your controller’s update method is probably one of the nicest examples of how Rails is well integrated across its MVC layers:
Person.update(params[:person].keys, params[:person].values)
Beautiful! This is the sort of harmony that makes the Rails way so enjoyable.
If you have a way of inserting HTML into your document dynamically, via JavaScript and/or Ajax techniques, you can leverage Rails’ behavior with regard to empty square brackets. When you’re using the square-brackets naming, Rails will happily generate HTML for new model objects that looks like
<input type="text" id="person__name" name="person[][name]"/>
If you were dynamically adding rows of child record entry forms to a parent form, you could replicate that convention easily. Just make sure the names of your input fields have the empty square brackets.
When you submit the form, the Rails request dispatcher will assume that the value of the :person
key in the params
hash is supposed to be an Array
, and that is what you will have to deal with in the controller action as the value of params[:person]
, an array!
Considering that the create
class method of Active Record models takes an array or hashes to do multiple inserts, we have yet another one of those beautiful examples of Rails cross-framework harmony:
def add_people
Person.create(params[:person])
...
end
However, there are some drawbacks to this technique, because it only works when all of the input fields in the person
namespace have empty square brackets. Stick any other input fields on the same object without the empty square brackets, and Rack will barf:
TypeError (expected Hash (got Array) for param `person')
Okay, moving forward, here is a slightly more verbose and less magical way to define multiple sets of input fields. Use the :index
option of the input field methods themselves. It allows you to explicitly provide an identifier that will be inserted into the field names, and in doing so opens up some interesting possibilities.
First, it lets you replicate the square brackets technique that we just discussed in the preceding section. For example, here’s a set of name fields for a collection of people:
- for @person in @people
= text_field :person, :name, :index => @person.id
...
The id
attribute of the person will be inserted into the parameter hash in the way we’ve already discussed with the square brackets, and we’ll get the same nesting behavior.
Now to make it more interesting, notice that the :index
option is not picky about the type of identifier that you supply it, which makes it pretty useful for defining enumerated sets of records! That is exactly what sets it apart from the square-brackets technique, and I’m sure I need to explain it a little more.
Consider the template code in Listing 11.4, part of a basketball tournament application (or in a more generalized guise, any application that stores people in well-defined roles):
Listing 11.4. Basketball team entry form
That code produces the following HTML output (simplified) when you run it:
Now when you submit that form (as I just did, using one of my favorite basketball teams of all time), your controller action would receive the following parameters hash. I took the liberty of formatting the log output nicely, to make sure the structure is clear:
I made it a point to give those text field inputs for the player’s names and ages their own :players
identifier, rather than linking them to the form’s team
object. You don’t even need to worry about initializing an @players
variable for the form to work. Form helper methods do not complain if the variable they’re supposed to reflect is nil
, provided you identify it using a symbol and not by passing the instance variable directly to the method.
For the sake of completeness, I’ll give you some simplistic controller action code in Listing 11.5 that is capable of handling the form submission. Taking into account the nested parameters hash, we can take it apart in a loop based on params[:players].keys
and do operations per role. Of course, this code assumes that the team has an instance method add_player(role, player)
, but I hope you get where I’m going with the example.
Listing 11.5. Controller action to create team
“Now hold on a second,” you are probably saying to yourself. If our example Team
model knew how to handle the setting of a players
hash as part of its attributes, the controller code could be dramatically simpler. In fact, we could knock the meat of it down to just a couple of line (excluding error checking and redirection):
def create
@team = Team.create(params[:team])
@team.players = params[:players]
end
We’ll use what Josh Susser called faux accessors3—setters that let you initialize parts of a model that aren’t (database-backed) attributes. Our example Team
model would need a players
writer method that understood how to add those players to itself. Perhaps it would look like the example in Listing 11.6.
Listing 11.6. Adding writer methods that understand params hashes
To recap, the players=
writer method gets invoked as a result of calling Team.create
with the full params
hash structure, which includes a nested hash of :players
. I must warn you that your mileage may, as they say, vary with this kind of technique. It’s perfect for the example, with its has_many :through
relationship connecting the Team
, Position
, and Player
classes, but it may not be perfect for your domain model. The most important idea is to keep your mind open to the possibility of writing code that is this clean. It’s the Rails way.
Hiding your code behind a method like this will make your code both simpler, and more powerful. You can now test this method in isolation, and can stub it in a controller spec. Stubbing in this case allows you to focus on testing the logic of the controller action, and not the behavior of the database. It also means you or another team member can change the implementation without breaking unrelated specs, and it keeps the database code where it belongs, in the model.
I’ve gotten us off on quite a tangent! We were talking about form helpers, so let’s cover one more important aspect of them before moving forward.
A rather important lesson to learn about Rails form helper methods is that the value they display comes directly from the database prior to meddling by the developer. Unless you know what you’re doing, you may get some unexpected results if you try to override the values to be displayed in a form.
Let’s illustrate with a simple LineItem
model, which has a decimal rate
attribute (by merits of a rate
column in its database table). We’ll override its implicit rate accessor with one of our own:
class LineItem < ActiveRecord::Base
def rate
"A RATE"
end
end
In normal situations, the overridden accessor is hiding access to the real rate attribute, as we can illustrate using the console.
>> li = LineItem.new
=> #<LineItem >
>> li.rate
=> "A RATE"
However, suppose you were to compose a form to edit line items using form helpers:
= form_for :line_item do |f|
= f.text_field :rate
You would find that it works normally, as if that overridden rate
accessor doesn’t exist. The behavior is intentional, yet confusing enough that it has been reported multiple times as a bug.4
The fact is that Rails form helpers use special methods named attribute_before_type_cast
(which are covered in Chapter 5, Working with Active Record). The preceding example would use the method rate_before_type_cast
, and bypass the overriding method we defined.
The fields_for
helper method creates a scope around a specific model object like form_for
, but doesn’t create the form tags themselves. Neither does it have an actual HTML representation as a div
or fieldset
. The fields_for
method is suitable for specifying additional model objects in the same form, particularly associations of the main object being represented in the form.
The following simple example represents a person and its associated permissions.
When the object belonging to the current scope has a nested attribute writer for a certain attribute, fields_for
will yield a new scope for that attribute. This allows you to create forms that set or change the attributes of a parent object and its associations in one go.
Nested attribute writers are normal setter methods named after an association. The most common way of defining these writers is either by declaring accepts_nested_attributes_for
in a model definition or by defining a method with the proper name, along the lines of the faux accessors described earlier in the chapter. For example: the attribute writer for the association :address
is called address_attributes=
.
Whether a one-to-one or one-to-many style form builder will be yielded depends on whether the normal reader method returns a single object or an array
of objects. Consider a simple Ruby Person
class which returns a single Address
from its address
reader method and responds to the address_attributes=
writer method:
class Person
def address
@address
end
def address_attributes=(attributes)
# Process the attributes hash
end
end
This model can now be used with a nested fields_for, like:
When address is already an association on a Person you can use accepts_nested_attributes_for
to define the writer method for you, like:
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
If you want to destroy the associated model through the form, you have to enable it first using the :allow_destroy
option for accepts_nested_attributes_for
like:
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address, :allow_destroy => true
end
Now, when you use a checkbox form element specially named _destroy
, with a value that evaluates to true
, the logic generated by accepts_nested_attribute_for
will destroy the associated model. (This is a super useful technique for list screens that allow deletion of multiple records at once using checkboxes.)
fields_for
with One-to-Many AssociationsConsider a Person
class that returns an array of Project
instances from the projects
reader method and responds to the projects_attributes=
writer method:
class Person < ActiveRecord::Base
def projects
[@project1, @project2]
end
def projects_attributes=(attributes)
# Process the attributes hash
end
end
This model can now be used with a nested fields_for
helper method in a form. The block given to the nested fields_for
call will be repeated for each instance in the collection automatically:
It’s also possible to specify the instance to be used by doing the iteration yourself, which is a refinement of the square bracket techniques that we discussed earlier. The symbol passed to fields_for
refers to the reader method of the parent object of the form, but the second argument contains the actual object to be used for fields:
Since fields_for
also understands a collection as its second argument in that situation, you can shrink that last example to the following code. Just inline the projects collection:
If, in our example, Person was an Active Record model and projects was one of its has_many
associations, then you could use accepts_nested_attributes_for
to define the writer method for you:
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
As with using accepts_nested_attributes_for
with a belongs_to
association, if you want to destroy any of the associated models through the form, you have to enable it first using the :allow_destroy
option:
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects, :allow_destroy => true
end
This will allow you to specify which models to destroy in the attributes hash by adding a boolean form element named _destroy
= form_for person do |form|
= form.fields_for :projects do |project_fields| %>
= project_fields.check_box :_destroy
Delete this project
Nested records are updated on save, even when the intermediate parent record is unchanged. For example, consider the following model code. 5
The following spec snippet illustrates nested saving:
Under the covers, the form_for
method uses a class named ActionView::Helpers::FormBuilder
. An instance of it is yielded to the form block. Conveniently, you can subclass it in your application to override existing or define additional form helpers.
For example, let’s say you made a builder class to automatically add labels to form inputs when text_field
is called. You’d enable it with the :builder
option like:
= form_for person, :builder => LabelingFormBuilder do |f|
Instructions on making custom form builder classes would fill its own chapter, but luckily there are many open source examples to choose from on Github. If you’re interested, check out the results of http://github.com/search?type=Repositories&language=rb&q=form+builder.
For each if these methods, there is a similarly named form builder method that omits the object_name
parameter.
check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
This helper gives you an extra hidden input field to ensure that a false value is passed even if the check box is unchecked.
email_field(object_name, method, options = {})
Creates an email input field. This method is otherwise identical to text_field
.
file_field(object_name, method, options = {})
Creates a file upload field and automatically adds :multipart => true
to the enclosing form. See file_field_tag
for details.
hidden_field(object_name, method, options = {})
Creates a hidden field, with parameters similar to text_field
.
label(object_name, method, text = nil, options = {})
Creates a label tag with the for
attribute pointed at the specified input field.
>> label('timesheet', 'approved')
=> <label for="timesheet_approved">Approved</label>
>> label('timesheet', 'approved', 'Approved?')
=> <label for="timesheet_approved">Approved?</label>
Many of us like to link labels to input fields by nesting. (Many would say that’s the correct usage of labels.) As of Rails 3 the label
helper accepts a block so that nesting is possible and works as would be expected. As a result, instead of having to do this:
= f.label :terms, "<span>Accept #{link_to 'Terms', terms_path}</span>"
you can do the much more elegant and maintainable:
= f.label :terms do
%span Accept #{link_to "Terms", terms_path}
number_field(object_name, method, options = {})
Creates a number input field. This method is otherwise identical to text_field
with the following additional options:
:min
The minimum acceptable value.
:max
The maximum acceptable value.
:in
A range specifying the :min
and :max
values.
:step
The acceptable value granularity.
This field renders with a nil value by default for security reasons. If you want to pre-populate the user’s password you can do something like
f.password_field(:password, :value => user.password)
password_field(object_name, method, options = {})
Creates a password input field. This method is otherwise identical to text_field
, but renders with a nil value by default for security reasons. If you want to pre-populate the user’s password you can do something like
f.password_field(:password, :value => user.password)
radio_button(object_name, method, tag_value, options = {})
Creates a radio button input field. Make sure to give all of your radio button options the same name
so that the browser will consider them linked.
range_field(object_name, method, options = {})
Creates a range input field. This method is otherwise identical to number_field
.
search_field(object_name, method, options = {})
Creates a search input field. This method is otherwise identical to text_field
.
telephone_field(object_name, method, options = {})
Creates a telephone input field. This method is otherwise identical to text_field
and is aliased as phone_field
.
submit(value = nil, options = {})
Creates a submit button with the text value as the caption. The option :disable_with
can be used to provide a name for disabled versions of the submit button.
text_area(object_name, method, options = {})
Creates a multiline text input field (the textarea
tag). The :size
option lets you easily specify the dimensions of the text area, instead of having to resort to explicit :rows
and :cols
options.
text_field(object_name, method, options = {})
Creates a standard text input field.
FormOptionsHelper
The methods in the FormOptionsHelper
module are all about helping you to work with HTML select
elements, by giving you ways to turn collections of objects into option
tags.
The following methods help you to create select
tags based on a pair of object
and attribute
identifiers.
collection_select(object, attribute, collection, value_method, text_method, options = {}, html_options = {})
Return both select
and option
tags for the given object
and attribute
using options_from_collection_for_select
(also in this module) to generate the list of option
tags from the collection
parameter.
Create a select
tag and a series of contained option
tags for the provided object_name
and attribute. The value of the attribute currently held by the object (if any) will be selected, provided that the object is available (not nil). See options_for_select
section for the required format of the choices parameter.
Here’s a small example in which the value of @person.person_id
is 1:
select("post", "person_id", Person.all.collect {|p|
[ p.name, p.id ] }, { :include_blank => true })
Executing that helper code would generate the following HTML output:
<select name="post[person_id]">
<option value=""></option>
<option value="1" selected="selected">David</option>
<option value="2">Sam</option>
<option value="3">Tobias</option>
</select>
If necessary, specify :selected => value
to explicitly set the selection or :selected => nil
to leave all options unselected. The :include_blank => true
option inserts a blank option
tag at the beginning of the list, so that there is no preselected value.
time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
Return select
and option
tags for the given object and method, using time_zone_options_for_select
to generate the list of option
tags.
In addition to the :include_blank
option documented in the preceding section, this method also supports a :model
option, which defaults to TimeZone.
This may be used by users to specify a different timezone model object. (See time_zone_options_for_select
section for more information.)
For all of the following methods, only option
tags are returned, so you have to invoke them from within a select helper or otherwise wrap them in a select
tag.
option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
Returns a string of option
tags, like options_from_collection_for_select
, but surrounds them with OPTGROUP
tags. The collection
should return a subarray of items when calling group_method
on it. Each group in the collection
should return its own name when calling group_label_method
. The option_key_method
and option_value_method
parameters are used to calculate option
tag attributes.
It’s probably much easier to show in an example than to explain in words.
This example could output the following HTML:
For the sake of clarity, here are the model classes reflected in the example:
options_for_select(container, selected = nil)
Accepts a container (hash, array, or anything else enumerable) and returns a string of option
tags.
Given a container in which the elements respond to first and last (such as a two-element array), the “lasts” serve as option values and the “firsts” as option text. It’s not too hard to put together an expression that constructs a two-element array using the map
and collect
iterators.
For example, assume you have a collection of businesses to display, and you’re using a select field to allow the user to filter based on the category of the businesses. The category is not a simple string; in this example, it’s a proper model related to the business via a belongs_to
association:
A simplified version of the template code for displaying that collection of businesses might look like:
The first line puts together the container
expected by options_for_select
by first aggregating the category
attributes of the businesses
collection using map
and the nifty &:method
syntax supported by Rails. The second line generates the select
tag using those options (covered later in the chapter). Realistically you want to massage that category list a little more, so that it is ordered correctly and does not contain duplicates:
... businesses.map(&:category).uniq.sort.collect {...
Particularly with smaller sets of data, it’s perfectly acceptable to do this level of data manipulation in Ruby code. And of course, you probably don’t want to ever shove hundreds or especially thousands of rows in a select
tag, making this technique quite useful. Remember to implement the spaceship method in your model if you need it to be sortable by the sort
method.
Also, it’s worthwhile to experiment with eager loading in these cases, so you don’t end up with an individual database query for each of the objects represented in the select
tag. In the case of our example, the controller would populate the businesses collection using code like:
expose(:businesses) do
Business.where(...).includes(:category)
end
Hashes are turned into a form acceptable to options_for_select
automatically—the keys become firsts and values become lasts.
If selected
parameter is specified (with either a value or array of values for multiple selections), the matching last or element will get the selected attribute:
A lot of people have trouble getting this method to correctly display their selected item. Make sure that the value you pass to selected
matches the type contained in the object collection of the select
; otherwise, it won’t work. In the following example, assuming price
is a numeric value, without the to_s
, selection would be broken, since the values passed as options are all strings:
options_from_collection_for_select(collection, value_method, text_method, selected = nil)
Returns a string of option
tags that have been compiled by iterating over the collection and assigning the result of a call to the value_method
as the option value and the text_method
as the option text. If selected is specified, the element returning a match on value_method
will get preselected.
time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
Returns a string of option
tags for pretty much any timezone in the world. Supply a TimeZone
name as selected to have it preselected. You can also supply an array of TimeZone
objects as priority_zones
, so that they will be listed above the rest of the (long) list. TimeZone.us_zones
is a convenience method that gives you a list of the U.S. timezones only.
The selected
parameter must be either nil
, or a string that names a TimeZone
(covered in the Appendix A, ActiveSupport API Reference).
By default, the model
is the TimeZone
constant (which can be obtained in Active Record as a value object). The only requirement is that the model
parameter be an object that responds to all
, returning an array of objects representing timezones.
FormTagHelper
The following helper methods generate HTML form and input tags based on explicit naming and values, contrary to the similar methods present in FormHelper
, which require association to an Active Record model instance. All of these helper methods take an options
hash, which may contain special options or simply additional attribute values that should be added to the HTML tag being generated.
check_box_tag(name, value = "1", checked = false, options = {})
Creates HTML for a check box input field. Unlike its fancier cousin, check_box
in FormHelper
, this helper does not give you an extra hidden input field to ensure that a false value is passed even if the check box is unchecked.
email_field_tag(name, value = nil, options = {})
Creates an email input field. This method is otherwise identical to text_field_tag
.
field_set_tag(legend = nil, options = nil, & block)
Wrap the contents of the given block in a fieldset
tag and optinally give it a legend
tag.
file_field_tag(name, options = {})
Creates a file upload field. Remember to set your HTML form to multipart or file uploads will mysteriously not work:
- form_tag '/upload', :multipart => true do
= label_tag :file, 'File to Upload'
= file_field_tag :file
= submit_tag
The controller action will receive a File
object pointing to the uploaded file as it exists in a tempfile on your system. Processing of an uploaded file is beyond the scope of this book. If you’re smart, you’ll use Jonas Nicklas’ excellent CarrierWave gem instead of reinventing the wheel.6
form_tag(url_for_options = {}, options = {}, *parameters_for_url, & block)
Starts a form
tag, with its action attribute set to the URL passed as the url_for_options
parameter. It is aliased as start_form_tag
.
The :method
option defaults to POST. Browsers handle HTTP GET and POST natively; if you specify “put,” “delete,” or any other HTTP verb is used, a hidden input field will be inserted with the name _method
and a value corresponding to the method
supplied. The Rails request dispatcher understands the _method
parameter, which is the basis for the RESTful techniques you learned in Chapter 3.
The :multipart
option allows you to specify that you will be including file-upload fields in the form submission and the server should be ready to handle those files accordingly.
You might note that all parameters to form_tag
are optional. If you leave them off, you’ll get a form that posts back to the URL that it came from—a quick and dirty solution that I use quite often when prototyping or experimenting. To quickly set up a controller action that handles post-backs, just include an if
/else
condition that checks the request method, something like:
def add
if request.post?
# handle the posted params
redirect_to :back
end
end
Notice that if the request is a post, I handle the form params
and then redirect back to the original URL (using redirect_to :back
). Otherwise, execution simply falls through and would render whatever template is associated with the action.
hidden_field_tag(name, value = nil, options = {})
Creates a hidden field, with parameters similar to text_field_tag
.
image_submit_tag(source, options = {})
Displays an image that, when clicked, will submit the form. The interface for this method is the same as its cousin image_tag
in the AssetTagHelper
module.
Image input tags are popular replacements for standard submit tags, because they make an application look fancier. They are also used to detect the location of the mouse cursor on click—the params
hash will include x
and y
data.
label_tag(name, text = nil, options = {})
Creates a label tag with the for
attribute set to name
.
number_field_tag(name, value = nil, options = {})
Creates a number input field. This method is otherwise identical to text_field_tag
with the following additional options:
:min
The minimum acceptable value
:max
The maximum acceptable value
:in
A range specifying the :min
and :max
values
:step
The acceptable value granularity
password_field_tag(name = "password", value = nil, options = {})
Creates a password input field. This method is otherwise identical to text_field_tag
.
radio_button_tag(name, value, checked = false, options = {})
Creates a radio button input field. Make sure to give all of your radio button options the same name
so that the browser will consider them linked.
range_field_tag(name, value = nil, options = {})
Creates a range input field. This method is otherwise identical to number_field_tag
.
search_field_tag(name, value = nil, options = {})
Creates a search input field. This method is otherwise identical to text_field_tag
.
select_tag(name, option_tags = nil, options = {})
Creates a drop-down selection box, or if the :multiple option is set to true, a multiple-choice selection box. The option_tags
parameter is an actual string of option tags to put inside the select tag. You should not have to generate that string explicitly yourself. Instead, use the helpers in FormOptions
(covered in the previous section of this chapter), which can be used to create common select boxes such as countries, time zones, or associated records.
submit_tag(value = ''Save changes'', options = {})
Creates a submit button with the text value as the caption. The option :disable_with
can be used to provide a name for disabled versions of the submit button.
telephone_field_tag(name, value = nil, options = {})
Creates a telephone input field. This method is otherwise identical to text_field_tag
and is aliased as phone_field_tag
.
text_area_tag(name, content = nil, options = {})
Creates a multiline text input field (the textarea
tag). The :size
option lets you easily specify the dimensions of the text area, instead of having to resort to explicit :rows
and :cols
options.
>> text_area_tag "body", nil, :size => "25x10"
=> <textarea name="body" id="body" cols="25" rows="10"></textarea>
text_field_tag(name, value = nil, options = {})
Creates a standard text input field.
JavaScriptHelper
Provides helper methods to facilitate inclusion of JavaScript code in your templates.
escape_javascript(javascript)
Escapes line breaks, single and double quotes for JavaScript segments.
javascript_tag(content, html_options={})
Outputs a script
tag with the content inside. The html_options
are added as tag attributes.
NumberHelper
This module provides assistance in converting numeric data to formatted strings suitable for displaying in your view. Methods are provided for phone numbers, currency, percentage, precision, positional notation, and file size.
human_size(size, precision=1)
Alias for number_to_human_size
.
number_to_currency(number, options = {})
Formats a number into a currency string. You can customize the format in the options hash.
:precision
Sets the level of precision, defaults to 2
:unit
Sets the denomination of the currency, defaults to "$"
:separator
Sets the separator between the units, defaults to "."
:delimiter
Sets the thousands delimiter, defaults to ","
number_to_human_size(size, precision=1)
Formats the bytes in size into a more understandable representation. Useful for reporting file sizes to users. This method returns nil if size cannot be converted into a number. You can change the default precision of 1
.
This method is also aliased as human_size
.
number_to_percentage(number, options = {})
Formats a number as a percentage string. You can customize the format in the options hash.
:precision
Sets the level of precision, defaults to 3
:separator
Sets the separator between the units, defaults to "."
number_to_phone(number, options = {})
Formats a number as a U.S. phone number. You can customize the format in the options hash.
:area code
Adds parentheses around the area code
:delimiter
Specifies the delimiter to use, defaults to "-"
:extension
Specifies an extension to add to the end of the generated number
:country code
Sets the country code for the phone number
number_with_delimiter(number, delimiter=",", separator=".")
Formats a number with grouped thousands using a delimiter. You can customize the format using optional delimiter and separator parameters.
delimiter
Sets the thousands delimiter, defaults to ","
separator
Sets the separator between the units, defaults to "."
number_with_precision(number, precision=3)
Formats a number with the specified level of precision. The default level of precision is 3.
PrototypeHelper
PrototypeHelper
has been heavily modified from what it was in earlier versions of Rails. It now primarily contains the implementation of RJS, covered in Section 12.2.
The following helper methods were removed and made available in an official Prototype Legacy Helper available at http://github.com/rails/prototype_legacy_helper.
• button_to_remote
• form_remote_for
• form_remote_tag
• link_to_remote
• observe_field
• observe_form
• periodically_call_remote
• remote_form_for
• submit_to_remote
Be aware that the functionality of the form_remote_for
, form_remote_tag
, link_to_remote
, and remote_form_for
methods remains partially supported using the :remote => true
option on link and form helpers, as covered in Section 12.1.1.
RawOutputHelper
This is an extremely simple helper module, barely worth mentioning.
raw(stringish)
Bypasses HTML sanitization, by calling to_s
, then html_safe
on the argument passed to it.
RecordIdentificationHelper
This module, which wraps the methods of ActionController::RecordIdentifier
, encapsulates a number of naming conventions for dealing with records, like Active Record models or Active Resource models or pretty much any other type of model object that you want to represent in markup code (like HTML) and which has an id
attribute. These patterns are then used to try to elevate the view actions to a higher logical level. For example, assume that you have map.resources :posts
defined in your routes file, and code that looks like this in your view:
= div_for(post) do
= post.body
The HTML for the div
element would thus be rendered like:
<div id="post_45" class="post">
What a wonderful world!
</div>
Notice the convention reflected in the id
attribute. Now, for the controller, which has an Ajax-enabled destroy
method. The idea is that it can be called to delete the record and make it disappear from the page without a reload operation:
As the preceding example shows, you can stop caring to a large extent what the actual id of the model is (the div
element holding the model information, that is). You just know that one is being assigned and that the subsequent calls in RJS expect that same naming convention and allow you to write less code if you follow it. You can find more information on this technique in Chapter 12.
dom_class(record_or_class, prefix = nil)
The DOM
class convention is to use the singular form of an object or class.
dom_class(post) # => "post"
dom_class(Person) # => "person"
If you need to address multiple instances of the same class in the same view, you can prefix the dom_class
:
dom_class(post, :edit) # => "edit_post"
dom_class(Person, :edit) # => "edit_person"
dom_id(record, prefix = nil)
The DOM
id convention is to use the singular form of an object or class with the id following an underscore. If no id is found, prefix with new_
instead.
dom_id(Post.create) # => "post_42"
dom_id(Post.new) # => "new_post"
If you need to address multiple instances of the same class in the same view, you can prefix the dom_id
like: dom_id(Post.create, :edit)
results in edit_post_42
.
partial_path(record_or_class)
Returns plural/singular for a record or class, which is very useful for automatically rendering partial templates by convention.
partial_path(post) # => "posts/post"
partial_path(Person) # => "people/person"
RecordTagHelper
This module is closely related to RecordIdentificationHelper
in that it assists in creation of HTML markup code that follows good, clean naming conventions.
content_tag_for(tag_name, record, *args, & block)
This helper method creates an HTML element with id
and class
parameters that relate to the specified Active Record object. For instance, assuming @person
is an instance of a Person
class, with an id
value of 123
then the following template code
= content_tag_for(:tr, @person) do
%td= @person.first_name
%td= @person.last_name
will produce the following HTML
<tr id="person_123" class="person">
...
</tr>
If you require the HTML id
attribute to have a prefix, you can specify it as a third argument:
>> content_tag_for(:tr, @person, :foo) do ...
=> "<tr id="foo_person_123" class="person">..."
The content_tag_for
helper also accepts a hash of options, which will be converted to additional HTML attributes on the tag. If you specify a :class
value, it will be combined with the default class name for your object instead of replacing it (since replacing it would defeat the purpose of the method!).
>> content_tag_for(:tr, @person, :foo, :class => 'highlight') do ...
=> "<tr id="foo_person_123" class="person highlight">..."
div_for(record, *args, & block)
Produces a wrapper div
element with id
and class
parameters that relate to the specified Active Record object. This method is exactly like content_tag_for
except that it’s hard-coded to output div
elements.
SanitizeHelper
The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. Rails 3 sanitizes and escapes html content by default, so this helper is really intended to assist with the inclusion of dynamic content into your views.
sanitize(html, options = {})
Encodes all tags and strip all attributes (not specifically allowed) from the html
string passed to it. Also strips href
and src
tags with invalid protocols, particularly in an effort to to prevent abuse of javascript:
attribute values.
= sanitize @article.body
With its default settings, the sanitize
method does its best to counter known hacker tricks such as using unicode/ascii/hex values to get past the JavaScript filters.
You can customize the behavior of sanitize
by adding or removing allowable tags and attributes using the :attributes
or :tags
options.
= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id
class style)
It’s possible to add tags to the default allowed tags in your application by altering the value of config.action_view.sanitized_allowed_tags
in an initializer. For instance, the following code adds support for basic HTML tables.
You can also remove some of the tags that are allowed by default.
Or change them altogether.
Sanitizing user-provided text does not guarantee that the resulting markup will be valid (conforming to a document type) or even well-formed. The output may still contain unescaped <
, >
, &
characters that confuse browsers and adversely affect rendering.
sanitize_css(style)
Sanitizes a block of CSS code. Used by sanitize when it comes across a style attribute in HTML being sanitized.
strip_links(html)
Strips all link tags from text leaving just the link text.
strip_tags(html)
Strips all tags from the html
string, including comments. Its HTML parsing ability is limited by that of the html-scanner tokenizer built into Rails. 7
TagHelper
This module provides helper methods for generating HTML tags programmatically.
cdata_section(content)
Returns a CDATA section wrapping the given content
. CDATA sections are used to escape blocks of text containing characters that would otherwise be recognized as markup. CDATA sections begin with the string <![CDATA[
and end with (and may not contain) the string ]]>
.
content_tag(name, content = nil, options = nil, & block)
Returns an HTML block tag of type name
surrounding the content. Add HTML attributes by passing an attributes hash as options
. Instead of passing the content as an argument, you can also use a block to hold additional markup (and/or additional calls to content_tag
) in which case, you pass your options
as the second parameter.
Here are some simple examples of using content_tag
without a block:
Here it is with content in a block (shown as template code rather than in the console):
- content_tag :div, :class => "strong" do
Hello world!
The preceding code produces the following HTML:
<div class="strong"><p>Hello world!</p></div>
escape_once(html)
Returns an escaped version of HTML without affecting existing escaped entities.
tag(name, options = nil, open = false)
Returns an empty HTML tag of type name
, which by default is XHTML compliant. Setting open to true
will create an open tag compatible with HTML 4.0 and below. Add HTML attributes by passing an attributes hash to options.
The options hash is used with attributes with no value like (disabled
and readonly)
, which you can give a value of true
in the options hash. You can use symbols or strings for the attribute names.
TextHelper
The methods in this module provide filtering, formatting, and string transformation capabilities.
auto_link(text, link = :all, href_options = {}, & block)
Turns all URLs and email addresses inside the text
string into clickable links. The link
parameter is used to optionally limit what should be linked; pass it :email_addresses
or :urls
. You can add HTML attributes to the generated links using href_options
.
If for whatever reason you are unhappy with the way that Rails is turning your email addresses and URLs into links, you can supply a block to this method. Each address found is yielded and the return value of the block is used as the link text.
concat(string, binding)
The preferred method of outputting text in your views is to use the = expression
in Haml syntax, or the <%= expression %>
in eRuby syntax. The regular puts and print methods do not operate as expected in an eRuby code block; that is, if you expected them to output to the browser. If you absolutely must output text within a non-output code block like - expression
in Haml, or <% expression %>
in eRuby, you can use the concat
method. I’ve found that this method can be especially useful when combined with capture
in your own custom helper method implementations.
The following example code defines a helper method that wraps its block content in a div with a particular css class.
You would use it in your template as follows:
- wrap do
My wrapped content
current_cycle(name = "default")
Returns the current cycle string after a cycle has been started. Useful for complex table highlighting or any other design need which requires the current cycle string in more than one place.
cycle(first_value, *values)
Creates a Cycle
object whose to_s
method cycles through elements of the array of values passed to it, every time it is called. This can be used, for example, to alternate classes for table rows. Here’s an example that alternates CSS classes for even and odd numbers, assuming that the @items
variable holds an array with 1 through 4:
As you can tell from the example, you don’t have to store the reference to the cycle in a local variable or anything like that; you just call the cycle
method repeatedly. That’s convenient, but it means that nested cycles need an identifier. The solution is to pass cycle a :name => cycle_name
option as its last parameter. Also, you can manually reset a cycle by calling reset_cycle
and passing it the name of the cycle to reset. For example, here is some data to iterate over:
And here is the template code. Since the number of cells rendered varies, we want to make sure to reset the colors cycle before looping:
excerpt(text, phrase, radius = 100, excerpt_string = "...")
Extracts an excerpt from text that matches the first instance of phrase
. The radius expands the excerpt on each side of the first occurrence of phrase
by the number of characters defined in radius
(which defaults to 100). If the excerpt radius
overflows the beginning or end of the text, the excerpt_string
will be prepended/appended accordingly. If the phrase isn’t found, nil is returned.
highlight(text, phrases, highlighter = '<strong class="highlight">1</strong>')
Highlights one or more phrases everywhere in text by inserting into a highlighter template. The highlighter can be specialized by passing highlighter as a single-quoted string with 1
where the phrase is to be inserted.
pluralize(count, singular, plural = nil)
Attempts to pluralize the singular word unless count is 1. If the plural is supplied, it will use that when count is > 1. If the ActiveSupport Inflector
is loaded, it will use the Inflector
to determine the plural form; otherwise, it will just add an “s” to the singular word.
reset_cycle(name = "default")
Resets a cycle (see the cycle
method in this module) so that it starts cycling from its first element the next time it is called. Pass in a name
to reset a named cycle.
simple_format(text)
Returns text transformed into HTML using simple formatting rules. Two or more consecutive newlines (
) are considered to denote a paragraph and thus are wrapped in p
tags. One newline (
) is considered to be a line break and a br
tag is appended. This method does not remove the newlines from the text.
strip_links(text)
Strips all link tags from text leaving just the link text.
strip_tags(html)
Strips all HTML tags from the HTML, including comments. This uses the html-scanner
tokenizer and so its HTML parsing ability is limited by that of html-scanner
.
truncate(text, length = 30, truncate_string = "...")
If text
is longer than length
, text
will be truncated to the length specified and the last three characters will be replaced with the truncate_string
:
word_wrap(text, line_width = 80)
Wraps the text into lines no longer than line_width
. This method breaks on the first whitespace character that does not exceed line_width
(which is 80 by default).
TranslationHelper
and the I18n APII18n stands for internationalization and the I18n gem that ships with Rails makes it easy to support multiple languages other than English in your Rails applications. When you internationalize your app, you do a sweep of all the textual content in your models and views that needs to be translated, as well as demarking data like currency and dates, which should be subject to localization.8
Rails provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or for providing multi-language support in your application.
The process of internationalization in Rails involves the abstraction of strings and other locale-specific parts of your application (such as dates and currency formats) out of the codebase and into a locale file.
The process of localization means to provide translations and localized formats for the abstractions created during internationalization. In the process of localizing your application you’ll probably want to do following three things:
• Replace or add to Rails’s default locale
• Abstract strings used in your application into keyed dictionaries—e.g., flash messages, static text in your views, etc.
• Store the resulting dictionaries somewhere
Internationalization is a complex problem. Natural languages differ in so many ways (e.g., in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on:
• Providing support for English and similar languages by default
• Making it easy to customize and extend everything for other languages
As part of this solution, every static string in the Rails framework—e.g., Active Record validation messages, time and date formats—has been internationalized, so localization of a Rails application means overriding Rails defaults.
Before diving into the more complicated localization techniques, lets briefly cover a simple way to translate views that is useful for content-heavy pages. Assume you have a BooksController
in your application. Your index
action renders content in app/views/books/index.html.haml
template. When you put a localized variant of that template such as index.es.html.haml
in the same directory, Rails will recognize it as the appropriate template to use when the locale is set to :es
. If the locale is set to the default, the generic index.html.haml
view will be used normally.
You can make use of this feature when working with a large amount of static content that would be clumsy to maintain inside locale dictionaries. Just bear in mind that any changes to a template must be kept in sync with all of its translations.
TranslationHelper
MethodsThe following two methods are provided for use in your views and assume that I18n support is setup in your application.
localize(*args)
aliased to l
Delegates to ActiveSupport’s I18n#translate
method with no additional functionality. Normally you want to use translate
instead.
translate(key, options = {})
aliased to t
Delegates to ActiveSupport’s I18n#translate
method, while performing two additional functions. First, it’ll catch MissingTranslationData
exceptions and turn them into inline spans that contain the missing key, such that you can see within your views when keys are missing.
Second, it’ll automatically scope the key provided by the current partial if the key starts with a period. So if you call translate(".foo")
from the people/index.html.haml
template, you’ll be calling I18n.translate("people.index.foo")
. This makes it less repetitive to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don’t prepend the key with a period, nothing is converted.
There are just a few simple steps to get up and running with I18n support for your application.
Following the convention over configuration philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
Rails adds all .rb
and .yml
files from the config/locales
directory to your translations load path, automatically.9 The default en.yml
locale in this directory contains a sample pair of translation strings:
en:
hello: "Hello world"
This means, that in the :en
locale, the key hello
will map to the “Hello world” string.10
You can use YAML or standard Ruby hashes to store translations in the default (Simple
) backend.
Unless you change it, the I18n library will use English (:en
) as its default locale for looking up translations. Change the default in config/application.rb
using code similar to:
config.i18n.default_locale = :de
The i18n library takes a pragmatic approach to locale keys (after some discussion11), including only the locale (“language”) part, like :en
, :pl
, not the region part, like :en-US
or :en-UK
, which are traditionally used for separating “languages” and “regional setting” or “dialects”. Many international applications use only the “language” element of a locale such as :cz
, :th
or :es
(for Czech, Thai, and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the :en-US
locale you would have $ as a currency symbol, hereas in :en-UK
, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full “English – United Kingdom” locale in a :en-UK
dictionary. Rails I18n plugins such as Globalize212 may help you implement it.
If you want to translate your Rails application to a single language other than English, you can just set default_locale
to your locale in application.rb
as shown above and it will persist through the requests. However, you probably want to provide support for more locales in your application, depending on the user’s preference. In such cases, you need to set and pass the locale between requests. You can set the locale in a before_filter
in your ApplicationController
like:
You may be tempted to store the chosen locale in a session or a cookie. Do not do so. The locale should be transparent and a part of the URL. This way you don’t break people’s basic assumptions about the web itself: If you send a URL of some page to a friend, he or she should see the same page, same content.
This approach requires you to pass the locale as a URL query parameter as in http://example.com/books?locale=pt
. (This is, for example, Google’s approach.)
Getting the locale from params
and setting it accordingly is not the hard part of this techniqe. Including the locale parameter in every URL generated by your application is the hard part. To include an explicit option in every URL
= link_to books_url(:locale => I18n.locale)
would be tedious at best and impossible to maintain at worst.
A default_url_options
method in ApplicationController
is useful precisely in this scenario. It enables us to set defaults for url_for
and helper methods dependent on it.
Every helper method dependent on url_for
(e.g., helpers for named routes like root_path
or root_url
, resource routes like books_path
or books_url
, etc.) will now automatically include the locale in the query string, like
http://localhost:3001/?locale=ja
Having the locale hang at the end of every path in your application can negatively impact readability of your URLs. Moreover, from an architectural standpoint, locales are a concept that live above other parts of your application domain and your URLs should probably reflect that.
You might want your URLs to look more like www.example.com/en/books
(which loads the English locale) and www.example.com/nl/books
(which loads the Netherlands locale). This is achievable with the same default_url_options
strategy we just reviewed. You just have to set up your routes with a path_prefix
option in this way:
# config/routes.rb
resources :books, :path_prefix => '/:locale'
Even with this approach, you still need to take special care of the root URL of your application. An URL like http://localhost:3001/nl
will not work automatically, because the map.root :controller => "dashboard"
declaration in your routes.rb
doesn’t take locale into account. After all, there should only be one “root” of your website.
A possible solution is to map a URL like:
# config/routes.rb
map.dashboard '/:locale', :controller => "dashboard"
Do take special care about the order of your routes, so this route declaration does not break other ones. It would be most wise to add it directly before the map.root
declaration at the end of your routes file.
This solution has currently one rather big downside. Due to the default_url_options
implementation, you have to pass the :id
option explicitely, like link_to 'Show', book_url(:id => book)
and not depend on Rails’ magic in code like link_to 'Show', book
. If this should be a problem, have a look at two plugins which simplify work with routes in this way: Sven Fuchs’s routing_filter13 and Raul Murciano’s translate_routes14. Also make sure to read How to encode the current locale in the URL15 in the Rails i18n Wiki.
Another option you have is to set the locale from the domain name where your application runs. For example, we want www.example.com
to load the English (or default) locale, and www.example.es
to load the Spanish locale. Thus the top-level domain name is used for locale setting. This has several advantages:
• The locale is a very obvious part of the URL
• People intuitively grasp in which language the content will be displayed
• It is very trivial to implement in Rails
• Search engines seem to like that content in different languages lives at different, inter-linked domains
You can implement it like this in your ApplicationController
:
Try adding localhost aliases to your /etc/hosts
file to test this technique.
127.0.0.1 application.com
127.0.0.1 application.it
127.0.0.1 application.pl
We can also set the locale from the subdomain in a very similar way inside of ApplicationController
.
In specific cases, it would make sense to set the locale from client-supplied information, i.e., not from the URL. This information may come for example from the users’ prefered language (set in their browser), can be based on the users’ geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites. See the sidebar about sessions, cookies, and RESTful architecture.
Accept-Language
One source of client supplied information would be an Accept-Language
HTTP header. People may set this in their browser16 or other clients (such as curl
).
A trivial implementation of setting locale based on the Accept-Language
header in ApplicationController
might be:
In real production environments, you should use much more robust code that the example above. Try plugins such as Iain Hecker’s http_accept_language17 or even Rack middleware such as Ryan Tomayko’s locale.18
Yet another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as GeoIP Lite Country.19 The mechanics of the code would be very similar to the code above—you would need to query the database for the user’s IP, and look up your prefered locale for the country/region/city returned.
You can also provide users of your application with means to set (and possibly override) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above—you’d probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you’d set the locale to this value using a before_filter
in ApplicationController
.
After you’ve setup I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests, you’re ready for the really interesting part of the process: actually internationalizing your application.
First of all, you should be acquainted with the I18n API. The two most important methods of the I18n API are
translate # Lookup text translations
localize # Localize Date and Time objects to local formats
These have the aliases #t
and #l
so you can use them like
I18n.t 'store.title'
I18n.l Time.now
Take the following basic pieces of a simple Rails application as an example for describing the process.
The example has two strings that are currently hardcoded in English. To internationalize this code, we must replace those strings with calls to Rails’s #t
helper with a key that makes sense for the translation.
Now when you render this view, it will show an error message which tells you that the translations for the keys :hello_world
and :welcome_flash
are missing.
Rails adds a t
(translate
) helper method to your views so that you do not need to spell out I18n.t
all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a <span class="translation_missing">
.
To make the example work you would add the missing translations into the dictionary files (thereby doing the localization part of the work):
You may use YAML (.yml
) or plain Ruby (.rb
) files for storing your translations. YAML is the prefered option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what’s wrong. (If you encounter any “weird issues” with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
Okay! Now let’s add a timestamp to the view, so we can demo the date/time localization feature as well. To localize the time format you pass the Time object to I18n.l
or use Rails’s #l
helper method in your views.
# app/views/home/index.html.haml
%h1= t(:hello_world)
%p.notice= flash[:notice]
%p= l(Time.now, :format => :short)
And in our pirate translations file let’s add a time format (it’s already there in Rails’s defaults for English):
rails-i18n
repositoryThere’s a great chance that somebody has already done much of the hard work of translating Rails’ defaults for your locale. See the rails-i18n repository at Github20 for an archive of various locale files. When you put such file(s) in config/locale/
directory, they will automatically be ready for use.
Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you.
For example, your config/locale
directory could look like:
This way, you can separate model and model attribute names from text inside views, and all of this from the “defaults” (e.g., date and time formats). Other stores for the i18n library could provide different means of such separation.
The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further through settings in config/application.rb
:
Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent:
I18n.t :message
I18n.t 'message'
The translate
method also takes a :scope
option which can contain one or more additional keys that will be used to specify a "namespace" or scope for a translation key:
I18n.t :invalid, :scope => [:activerecord, :errors, :messages]
This looks up the :invalid
message in the Active Record error messages.
Additionally, both the key and scopes can be specified as dot-separated keys as in:
I18n.translate :"activerecord.errors.messages.invalid"
Thus the following four calls are equivalent:
I18n.t 'activerecord.errors.messages.invalid'
I18n.t 'errors.messages.invalid', :scope => :activerecord
I18n.t :invalid, :scope => 'activerecord.errors.messages'
I18n.t :invalid, :scope => [:activerecord, :errors, :messages]
When a :default
option is given, its value will be returned if the translation is missing:
I18n.t :missing, :default => 'Not here'
# => 'Not here'
If the :default
value is a Symbol, it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned.
E.g., the following first tries to translate the key :missing
and then the key :also_missing.
As both do not yield a result, the string “Not here” will be returned:
I18n.t :missing, :default => [:also_missing, 'Not here']
# => 'Not here'
To look up multiple translations at once, an array of keys can be passed:
I18n.t [:odd, :even], :scope => 'activerecord.errors.messages'
# => ["must be odd", "must be even"]
Also, a key can translate to a (potentially nested) hash of grouped translations. For instance, one can receive all Active Record error messages as a hash with:
I18n.t 'activerecord.errors.messages'
# => { :inclusion => "is not included in the list", :exclusion => ... }
Rails implements a convenient way to reference keys inside of views. Assume you have the following local file:
es:
books:
index:
title: "T'{i}tulo"
You can reference the value of books.index.title
inside of the app/views/books/index.html.haml
template by prefixing the key name with a dot. Rails will automatically fill in the scope based on the identity of the view.
<%= t '.title' %>
In many cases you want to abstract your translations in such a way that variables can be interpolated into the translation. For this reason, the I18n API provides an interpolation feature.
All options besides :default
and :scope
that are passed to translate
will be interpolated to the translation:
If a translation uses :default
or :scope
as an interpolation variable, an I18n::ReservedInterpolationKey
exception is raised. If a translation expects an interpolation variable, but this has not been passed to translate
, an I18n::MissingInterpolationArgument
exception is raised.
In English there are only one singular and one plural form for a given string, e.g. “1 message” and “2 messages” but other languages have different grammars with additional or fewer plural forms21. Thus, the I18n API provides a flexible pluralization feature.
The :count
interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by Unicode:
The algorithm for pluralizations in :en
is as simple as:
entry[count == 1 ? 0 : 1]
The translation denoted as :one
is regarded as singular, versus any other value regarded as plural (including the count being zero).
If the lookup for the key does not return a Hash suitable for pluralization, an I18n::InvalidPluralizationData
exception is raised.
The Simple
backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format. A Ruby hash locale file would look like:
The equivalent YAML file would look like:
pt:
foo:
bar: baz
In both cases, the top level key is the locale. :foo
is a namespace key and :bar
is the key for the translation “baz”.
Here is a real example from the Active Support en.yml
translations YAML file:
So, all of the following equivalent lookups will return the :short
date format "%B %d"
:
I18n.t 'date.formats.short'
I18n.t 'formats.short', :scope => :date
I18n.t :short, :scope => 'date.formats'
I18n.t :short, :scope => [:date, :formats]
Generally we recommend using YAML as a format for storing translations.
You can use the methods Model.human_name
and Model.human_attribute_name(attribute)
to transparently look up translations for your model and attribute names.
For example when you add the following translations:
User.human_name
will return “Dude” and User.human_attribute_name(:login)
will return “Handle”.
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account.
This gives you quite powerful means to flexibly adjust your messages to your application’s needs.
Consider a User model with a validates_presence_of
validation for the name attribute like:
class User < ActiveRecord::Base
validates_presence_of :name
end
The key for the error message in this case is :blank
. Active Record will look up this key in the namespaces:
activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
Thus, in our example it will try the following keys in this order and return the first result:
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
When your models are additionally using inheritance then the messages are looked up in the inheritance chain.
For example, you might have an Admin model inheriting from User:
class Admin < User
validates_presence_of :name
end
Then Active Record will look for messages in this order:
activerecord.errors.models.admin.attributes.title.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.title.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes.
The translated model name, translated attribute name, and value are always available for interpolation.
So, for example, instead of the default error message "can not be blank"
you could use the attribute name like "Please fill in your {{attribute}}"
.
error_messages_for
HelperIf you are using the Active Record error_messages_for
helper, you will want to add translations for it.
Rails ships with the following translations:
Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here’s a brief overview.
• distance_of_time_in_words
translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See datetime.distance_in_words
22 translations.
• datetime_select
and select_month
use translated month names for populating the resulting select tag. See date.month_names
23 for translations. datetime_select
also looks up the order option from date.order24 (unless you pass the option explicitely). All date selection helpers translate the prompt using the translations in the datetime.prompts25 scope if applicable.
• The number_to_currency
, number_with_precision
, number_to_percentage
, number_with_delimiter
, and number_to_human_size
helpers use the number format settings located in the number26 scope.
• human_name
and human_attribute_name
use translations for model names and attribute names if available in the activerecord.models27 scope. They also support translations for inherited class names (e.g., for use with STI) as explained in “Error message scopes”.
• ActiveRecord::Errors#generate_message
(which is used by Active Record validations but may also be used manually) uses human_name
and human_attribute_name
. It also translates the error message and supports translations for inherited class names as explained in “Error message scopes”.
* ActiveRecord::Errors#full_messages
prepends the attribute name to the error message using a separator that will be looked up from activerecord.errors.format.separator (and which defaults to ' '
).
• Array#to_sentence
uses format settings as given in the support.array
scope.
In some contexts, you might want to I18n’s default exception-handling behavior. For instance, the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose, a different exception handler can be specified. The specified exception handler must be a method on the I18n module. You would add code similar to the following to your spec_helper.rb
file or other kind of initializer.
This would re-raise all caught exceptions including MissingTranslationData
.
UrlHelper
This module provides a set of methods for making links and getting URLs that depend on the routing subsystem, covered extensively in Chapters 2 and 3 of this book.
button_to(name, options = {}, html_options = {})
Generates a form containing a single button that submits to the URL created by the set of options. This is the safest method to ensure that links that cause changes to your data are not triggered by search bots or accelerators. If the HTML button does not work with your layout, you can also consider using the link_to
method (also in this module) with the :method
modifier.
The options hash accepts the same options as the url_for
method (also part of this module).
The generated FORM
element has a class name of button-to
to allow styling of the form itself and its children. The :method
and :confirm
options work just like the link_to
helper. If no :method
modifier is given, it defaults to performing a POST operation. You can also disable the button by passing :disabled => true
.
current_page?(options)
Returns true
if the current request URI was generated by the given options. For example, let’s assume that we’re currently rendering the /shop/checkout
action:
link_to(name, options = {}, html_options = nil)
One of the fundamental helper methods. Creates a link tag of the given name using a URL created by the set of options. The valid options are covered in the description of this module’s url_for
method. It’s also possible to pass a string instead of an options hash to get a link tag that uses the value of the string as the href for the link. If nil
is passed as a name, the link itself will become the name.
:confirm => 'question?'
Adds a JavaScript confirmation prompt with the question specified. If the user accepts, the link is processed normally; otherwise, no action is taken.
:popup => true
Forces the link to open in a pop-up window. By passing true
, a default browser window will be opened with the URL. You can also specify a string of options to be passed to JavaScript’s window.open
method.
:method => symbol
Specify an alternative HTTP verb for this request (other than GET). This modifier will dynamically create an HTML form and immediately submit the form for processing using the HTTP verb specified (:post
, :put
, :delete
, or other custom string like "HEAD"
, and so on).
Generally speaking, GET requests should be idempotent, that is, they do not modify the state of any resource on the server, and can be called one or many times without a problem. Requests that modify server-side resources or trigger dangerous actions like deleting a record should not usually be linked with a normal hyperlink, since search bots and so-called browser accelerators can follow those links while spidering your site, leaving a trail of chaos.
If the user has JavaScript disabled, the request will always fall back to using GET, no matter what :method
you have specified. This is accomplished by including a valid href
attribute. If you are relying on the POST behavior, your controller code should check for it using the post?
, delete?
, or put?
methods of request
.
As usual, the html_options
will accept a hash of HTML attributes for the link tag.
link_to_if(condition, name, options = {}, html_options = {}, & block)
Creates a link tag using the same options as link_to
if the condition is true
; otherwise, only the name
is output (or block
is evaluated for an alternative value, if one is supplied).
link_to_unless(condition, name, options = {}, html_options = {}, & block)
Creates a link tag using the same options as link_to
unless the condition is true
, in which case only the name
is output (or block is evaluated for an alternative value, if one is supplied).
link_to_unless_current(name, options = {}, html_options = {}, & block)
Creates a link tag using the same options as link_to
unless the condition is true
, in which case only the name
is output (or block
is evaluated for an alternative value, if one is supplied).
This method is pretty useful sometimes. Remember that the block given to link_to_unless_current
is evaluated if the current action is the action given. So, if we had a comments page and wanted to render a “Go Back” link instead of a link to the comments page, we could do something like
mail_to(email_address, name = nil, html_options = {})
Creates a mailto
link tag to the specified email_address
, which is also used as the name of the link unless name
is specified. Additional HTML attributes for the link can be passed in html_options
.
The mail_to
helper has several techniques for hindering email harvesters and customizing the email address itself by passing special keys to html_options
:
:encode
This key will accept the strings "javascript"
or "hex"
. Passing "javascript"
will dynamically create and encode the mailto:
link and then eval
it into the DOM of the page. This method will not show the link on the page if the user has JavaScript disabled. Passing "hex"
will hex-encode the email_address
before outputting the mailto:
link.
:replace at
When the link name isn’t provided, the email_address
is used for the link label. You can use this option to obfuscate the email_address
by substituting the @ sign with the string given as the value.
:replace dot
When the link name isn’t provided, the email_address
is used for the link label. You can use this option to obfuscate the email_address
by substituting the “.” in the email with the string given as the value.
:subject
The subject line of the email.
:body
The body of the email.
:cc
Add cc recipients to the email.
:bcc
Add bcc recipients to the email.
Here are some examples of usages:
url_for(options = {})
The url_for
method returns a URL for the set of options provided and takes the same options as url_for
in Action Controller.
Note that by default, the :only_path
option is set to true so that you’ll get the relative /controller/action
instead of the fully qualified URL like http://example.com/controller/action
. Note that as of Rails 3 url_for
always returns unescaped strings.
Here is the complete list of options accepted by url_for
:
:anchor
Specifies an anchor name (#anchor
) be appended to the end of the path
:only_path
Specifies a relative URL (omitting the protocol, host name, and port)
:trailing_slash
Adds a trailing slash, as in "/archive/2005/"
. Note that this is currently not recommended since it breaks caching
:host
Overrides the default (current) host if provided
:protocol
Overrides the default (current) protocol if provided
:user
Inline HTTP authentication (requires :password
option)
:password
Inline HTTP authentication (requires :user
option)
:escape
Determines whether the returned URL will be HTML-escaped
When you pass url_for
a string, whether explicitly or via a named route helper method, it’ll assume that it is an already formed URL and won’t manipulate it. I’m mentioning this mostly for reference purposes; remember that you don’t normally call url_for
directly, but rather through other helper methods such as link_to
.
If you pass url_for
a hash, it will do old-school URL generation with it, but only if it finds a matching route definition in routes.rb
.
>> url_for(:controller => 'books', :action => 'find')
=> "/books/find"
Back in the day, URL generation of this type (controller/action) always worked because of the default route. Nowadays, most Rails applications have their default route turned off and using url_for
with hashes is a lot less common. If you’re a relative newcomer to Rails, it still makes sense to at least have a basic understanding of how this functionality works.
>> url_for(:controller => 'members', :action => 'login', :only_path =>
false, :protocol => 'https')
=> "https://www.railsapplication.com/members/login"
If some of the parameters of the route are missing from the call to url_for
, it defaults to the current values for :controller
, :action
, and any other parameters required by the route and/or used in the current request.
For example, inside a show
view for a template belonging to an auctions controller, you could create a link to the edit
action like:
link_to "Edit auction", :action => "edit", :id => auction
Assuming that this view is only ever rendered by actions in the auctions controller, the current controller at the time of the rendering will always be auctions. Because there’s no :controller
specified in the URL hash, the generator will fall back on auctions, and based on the default route (:controller/:action/:id
) or a matching resource route, it should come up with something like:
<a href="http://localhost:3000/auctions/edit/5">Edit auction</a>
The same is true of the action. If you don’t supply an :action
key, then the current action will be interpolated. Keep in mind, though, that it’s pretty common for one action to render a template that belongs to another. So it’s less likely that you’ll want to let the URL generator fall back on the current action than on the current controller.
If you pass the magic symbol :back
to any method that uses url_for
under the covers (redirect_to
, etc.) the contents of the HTTP_REFERER
request header will be returned. (If a referer is not set for the current request, it will return javascript:history.back()
to try to make the browser go back one page.)
>> url_for(:back)
=> "javascript:history.back()"
If you pass an Active Record or Active Resource model instance instead of a hash to any method in the UrlModule
that takes url_for
parameters, you’ll trigger generation of a path for that record’s named route (assuming that one exists). For example, passing a Timesheet
object instance will generate a timesheet_path
route.
>> url_for(timesheet) # existing record
=> "/timesheets/5"
The lookup is based on the name of the class and is smart enough to call new?
on the passed model to figure out whether to reference a collection or member route.
>> url_for(Timesheet.new)
=> "/timesheets"
If the object’s route is nested within another route, you’ll have to provide either a symbol designating the namespace that it’s in and/or one or more other objects which are above it in the nesting hierarchy. (Make sure to use an array around your objects and symbols, so that the first argument is properly interpreted!)
Somewhat confusingly, collection routes (even within namespaces) are accessed by prepending the name of the route to the front of the array, as in the following example:
>> url_for([:new, :admin, Client.new])
=> "/admin/clients/new"
As you develop an application in Rails, you should be on the lookout for opportunities to refactor duplicated view code into your own helper methods. As you think of these helpers, you add them to one of the helper modules defined in the app/helpers
folder of your application.
There is an art to effectively writing helper methods, similar in nature to what it takes to write effective APIs. Helper methods are basically a custom, application-level API for your view code. It is difficult to teach API design in a book form. It’s the sort of knowledge that you gain by apprenticing with more experienced programmers and lots of trial and error. Nevertheless, in this section, we’ll review some varied use cases and implementation styles that we hope will inspire you in your own application design.
Here is a simple helper method that has been of use to me on many projects now. It’s called page_title
and it combines two simple functions essential to a good HTML document:
• Setting the title
of the page in the document’s head
• Setting the content of the page’s h1
element
This helper assumes that you want the title
and h1
elements of the page to be the same, and has a dependency on your application template. The code for the helper is in Listing 11.7 and would be added to app/helpers/application_helper.rb
, since it is applicable to all views.
Listing 11.7. The page—title
Helper
First it sets content to be yielded in the layout as :title
and then it outputs an h1
element containing the same text. I could have used string interpolation on the second line, such as "<h1>#{name}</h1>"
, but it would have been sloppier than using the built-in Rails helper method content_tag
.
My application template is now written to yield :title
so that it gets the page title.
As should be obvious, you call the page_title
method in your view template where you want to have an h1
element:
photo_for
HelperHere’s another relatively simple helper. This time, instead of simply outputting data, we are encapsulating some view logic that decides whether to display a user’s profile photo or a placeholder image. It’s logic that you would otherwise have to repeat over and over again throughout your application.
The dependency (or contract) for this particular helper is that the user object being passed in has a profile_photo
associated to it, which is an attachment model based on Rick Olson’s old attachment_fu
Rails plugin.28 The code in Listing 11.8 should be easy enough to understand without delving into the details of attachment_fu
. Since this is a code example, I broke out the logic for setting src
into an if/else
structure; otherwise, this would be a perfect place to use Ruby’s ternary operator.
Listing 11.8. The photo—for
helper encapsulating common view logic
Luckily, the latest generation of attachment plugins such as Paperclip and CarrierWave use a NullObject pattern to alleviate the need for you to do this sort of thing.
breadcrumbs
HelperLots of web applications feature user-interface concepts called breadcrumbs. They are made by creating a list of links, positioned near the top of the page, displaying how far the user has navigated into a hierarchically organized application. I think it makes sense to extract breadcrumb
logic into its own helper method instead of leaving it in a layout template.
The trick to our example implementation (shown in Listing 11.9) is to use the presence of helper methods exposed by the controller, on a convention specific to your application, to determine whether to add elements to an array of breadcrumb links.
Listing 11.9. breadcrumbs Helper Method for a Corporate Directory Application
Here’s the line-by-line explanation of the code, noting where certain application-design assumptions are made:
On line 2, we abort execution if we’re in the context of the application’s homepage
controller, since its pages don’t ever need breadcrumbs. A simple return with no value implicitly returns nil
, which is fine for our purposes. Nothing will be output to the layout template.
On line 3 we are starting to build an array of HTML links, held in the html
local variable, which will ultimately hold the contents of our breadcrumb trail. The first link of the breadcrumb trail always points to the home page of the application, which of course will vary, but since it’s always there we use it to initialize the array. In this example, it uses a named route called root_path
.
After the html
array is initialized, all we have to do is check for the presence of the methods returning objects that make up the hierarchy (lines 4 to 9). It is assumed that if a department is being displayed, its parent company will also be in scope. If an employee is being displayed, both department and company will be in scope as well. This is not just an arbitrary design choice. It is a common pattern in Rails applications that are modeled on REST principles and using nested resource routes.
Finally, on line 10, the array of HTML links is joined with the > character, to give the entire string the traditional breadcrumb appearance. The call to html_safe
tells the rendering system that this is HTML code and we’re cool with that—don’t sanitize it!
I don’t think that partials (by themselves) lead to particularly elegant or concise template code. Whenever there’s a shared partial template that gets used over and over again in my application, I will take the time to wrap it up in a custom helper method that conveys its purpose and formalizes its parameters. If appropriate, I might even generalize its implementation to make it more of a lightweight, reusable component. (Gasp!)
tiles
HelperLet’s trace the steps to writing a helper method that wraps what I consider to be a general-purpose partial. Listing 11.10 contains code for a partial for a piece of a user interface that is common to many applications, and generally referred to as a tile. It pairs a small thumbnail photo of something on the left side of the widget with a linked name and description on the right.
Tiles can also represent other models in your application, such as users and files. As I mentioned, tiles are a very common construct in modern user interfaces and operating systems. So let’s take the cities tiles partial and transform it into something that can be used to display other types of data.
Listing 11.10. A tiles partial prior to wrapping and generalization
I realize that it has become passé to use HTML tables, and I happen to agree that div
-based layouts plus CSS are a lot more fun and flexible to work with. However, for the sake of simplicity in this example, and since the UI structure we’re describing is tabular, I’ve decided to structure it using a table.
Since we’re going to transform this city-specific partial into a generalized UI component, I want to make sure that the code we start with makes absolute sense to you first. Before proceeding, I’m going through the implementation line by line and explaining what everything in Listing 11.10 does.
Line 1 opens up the partial with a table element and gives it semantically significant CSS classes so that the table and its contents can be properly styled.
Line 2 leverages a useful Array
extension method provided by ActiveSupport, called in_groups_of
. It uses both of the local variables: cities
and columns
. Both will need to be passed into this partial using the :locals
option of the render :partial
method. The cities
variable will hold the list of cities to be displayed, and columns
is an integer representing how many city tiles each row should contain. A loop iterates over the number of rows that will be displayed in this table.
Line 3 begins a table row using the tr
element.
Line 4 begins a loop over the tiles for each row to be displayed, yielding a city
for each.
Line 5 opens a td
element and uses Haml’s object reference notation to autogenerate an dom_id
attribute for the table cell in the style of city_98, city_99
, and so on.
Line 6 opens a div
element for the left side of the tile and has the CSS class name needed so that it can be styled properly.
Line 7 calls the image_tag
helper to insert a thumbnail photo of the city.
Skipping along, lines 9-10 insert the content for the .title div
element, in this case, the name and state of the city.
Line 12 directly invokes the description
method.
In order to use this partial, we have to call render :partial
with the two required parameters specified in the :locals
hash:
render "cities/tiles", :cities => @user.cities, :columns => 3
I’m guessing that most experienced Rails developers have written some partial code similar to this and tried to figure out a way to include default values for some of the parameters. In this case, it would be really nice to not have to specify :columns
all the time, since in most cases we want there to be three.
The problem is that since the parameters are passed via the :locals
hash and become local variables, there isn’t an easy way to insert a default value in the partial itself. If you left off the :columns => n
part of your partial call, Rails would bomb with an exception about columns
not being a local variable or method. It’s not the same as an instance variable, which defaults to nil
and can be used willy-nilly.
Experienced Rubyists probably know that you can use the defined?
method to figure out whether a local variable is in scope or not, but the resulting code would be very ugly. The following code might be considered elegant, but it doesn’t work!29
columns = 3 unless defined? columns
Instead of teaching you how to jump through annoying Ruby idiom hoops, I’ll show you how to tackle this challenge the Rails way, and that is where we can start discussing the helper wrapping techique.
First, I’ll add a new helper method to the CitiesHelper
module of my application, like in Listing 11.11. It’s going to be fairly simple at first. In thinking about the name of the method, it occurs to me that I like the way that tiled(cities)
will read instead of tiles(cities)
, so I name it that way.
Listing 11.11. The CitiesHelper tiled method
Right from the start, I can take care of that default columns
parameter by giving the helper method parameter for columns a default value. That’s just a normal feature of Ruby. Now instead of specifying the render :partial
call in my view template, I can simply write = tiled(cities)
which is considerably more elegant and terse. It also serves to decouple the implementation of the tiled city table from the view. If I need to change the way that the tiled table is rendered in the future, I just have to do it in one place: the helper method.
Now that we’ve set the stage, the fun can begin. The first thing we’ll do is move the helper method to the ApplicationHelper
module so that it’s available to all view templates. We’ll also move the partial template file to app/views/shared/_tiled_table.html.haml
to denote that it isn’t associated with a particular kind of view and to more accurately convey its use. As a matter of good code style, I also do a sweep through the implementation and generalize the identifiers appropriately. The reference to cities
on line 2 becomes collection
. The block variable city
on line 4 becomes item
. Listing 11.12 has the new partial code.
Listing 11.12. Tiles partial code with revised naming
There’s still the matter of a contract between this partial code and the objects that it is rendering. Namely, they must respond to the following messages: photo
, name
, and description
. A survey of other models in my application reveals that I need more flexibility. Some things have names, but others have titles. Sometimes I want the description to appear under the name of the object represented, but other times I want to be able to insert additional data about the object plus some links.
Ruby allows you to store references to anonymous methods (also known as procs or lambdas) and call them at will whenever you want.30 Knowing this capability is there, what becomes possible? For starters, we can use lambdas to pass in blocks of code that will fill in parts of our partial dynamically.
For example, the current code for showing the thumbnail is a big problem. Since the code varies greatly depending on the object being handled, I want to be able to pass in instructions for how to get a thumbnail image without having to resort to big if/else
statements or putting view logic in my model classes. Please take a moment to understand the problem I’m describing, and then take a look at how we solve it in Listing 11.13. Hint: The thumbnail
, link
, title
, and description
variables hold lambdas!
Listing 11.13. Tiles partial code refactored to use lambdas
Notice that in Listing 11.13, the contents of the left and right div
elements come from variables containing lambdas. On line 2 we make a call to link_to
and both of its arguments are dynamic. A similar construct on line 5 takes care of generating the title link. In both cases, the first lambda should return the output of a call to image_tag
and the second should return a URL. In all of these lambda usages, the item
currently being rendered is passed to the lambdas as a block variable.
Things like link.call(item)
could potentially look even sassier as link[item]
, except that you’ll shoot your eye out doing it. (Proc#[]
is an alias for Proc#call
.)
If you now direct your attention to Listing 11.14, you’ll notice that the tiled
method is changed considerably. In order to keep my positional argument list down to a manageable size, I’ve switched over to taking a hash of options as the last parameter to the tiled
method. This approach is useful and it mimics the way that almost all helper methods take options in Rails.
Default values are provided for all parameters, and they are all passed along to the partial via the :locals
hash given to render
.
Listing 11.14. The tiled collection helper method with lambda parameters
Finally, to wrap up this example, here’s a snippet showing how to invoke our new tiled
helper method from a template, overriding the default behavior for links:
tiled(cities, :link => lambda {|city| showcase_city_path(city)})
The showcase_city_path
method is available to the lambda block, since it is a closure, meaning that it inherits the execution context in which it is created.
This very long chapter served as a thorough reference of helper methods, both those provided by Rails and ideas for ones that you will write yourself. Effective use of helper methods leads to more elegant and maintainable view templates. At this point, you should also have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project.
Before we fully conclude our coverage of Action Pack, (the name used to refer to Action Controller and Action View together), we’ll jump into the world of Ajax and JavaScript. Arguably, one of the main reasons for Rails’s continued popularity is its support for those two crucial technologies of Web 2.0.
This chapter is published under the Creative Commons Attribution-ShareAlike 3.0 license, http://creativecommons.org/licenses/b-sa/3.0
3.142.194.230