Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

USING HELPERS 471

22.2Using Helpers

Earlier we said that it’s OK to put code in templates. Now we’re going to modify that statement. It’s perfectly acceptable to put some code in templates—that’s what makes them dynamic. However, it’s poor style to put too much code in templates.

There are three main reasons for this. First, the more code you put in the view side of your application, the easier it is to let discipline slip and start adding application-level functionality to the template code. This is definitely poor form; you want to put application stuff in the controller and model layers so that it is available everywhere. This will pay off when you add new ways of viewing the application.

The second reason is that rhtml is basically HTML. When you edit it, you’re editing an HTML file. If you have the luxury of having professional designers create your layouts, they’ll want to work with HTML. Putting a bunch of Ruby code in there just makes it hard to work with.

The final reason is that code embedded in views is hard to test, whereas code split out into helper modules can be isolated and tested as individual units.

Rails provides a nice compromise in the form of helpers. A helper is simply a module containing methods that assist a view. Helper methods are outputcentric. They exist to generate HTML (or XML, or JavaScript)—a helper extends the behavior of a template.

By default, each controller gets its own helper module. It won’t be surprising to learn that Rails makes certain assumptions to help link the helpers into the controller and its views. If a controller is named BlogController, it will automatically look for a helper module called BlogHelper in the file blog_helper.rb in the app/helpers directory. You don’t have to remember all these details—the generate controller script creates a stub helper module automatically.

For example, the views for our store controller might set the title of generated pages from the instance variable @page_title (which presumably gets set by the controller). If @page_title isn’t set, the template uses the text “Pragmatic Store.” The top of each view template might look like

<h3><%= @page_title || "Pragmatic Store" %></h3> <!-- ... -->

We’d like to remove the duplication between templates: if the default name of the store changes, we don’t want to edit each view. So let’s move the code that works out the page title into a helper method. As we’re in the store controller, we edit the file store_helper.rb in app/helpers (as shown on the next page).

Report erratum

USING HELPERS 472

module StoreHelper def page_title

@page_title || "Pragmatic Store" end

end

Now the view code simply calls the helper method.

<h3><%= page_title %></h3> <!-- ... -->

(We might want to eliminate even more duplication by moving the rendering of the entire title into a separate partial template, shared by all the controller’s views, but we don’t talk about them until Section 22.9, Partial Page Templates, on page 509.)

Sharing Helpers

Sometimes a helper is just so good that you have to share it among all your controllers. Perhaps you have a spiffy date-formatting helper that you want to use in views called from all of your controllers. You have two options.

First, you could add the helper method to the file application_helper.rb in the directory app/helpers. As its name suggests, this helper is global to the entire application, and hence its methods are available to all views.

Alternatively, you can tell controllers to include additional helper modules using the helper declaration. For example, if our date-formatting helper was in the file date_format_helper.rb in app/helpers, we could load it and mix it into a particular controller’s set of views using

class ParticularController < ApplicationController

helper :date_format

# ...

You can include an already-loaded class as a helper by giving its name to the helper declaration.

class ParticularController < ApplicationController

helper DateFormat

# ...

You can add controller methods into the template using helper_method. Think hard before doing this—you risk mixing business and presentation logic. See the documentation for helper_method for details.

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 473

22.3Helpers for Formatting, Linking, and Pagination

Rails comes with a bunch of built-in helper methods, available to all views. In this section we’ll touch on the highlights, but you’ll probably want to look at the Action View RDoc for the specifics—there’s a lot of functionality in there.

Formatting Helpers

One set of helper methods deals with dates, numbers, and text.

<%= distance_of_time_in_words(Time.now, Time.local(2005, 12, 25)) %>

248 days

<%= distance_of_time_in_words(Time.now, Time.now + 33, false) %>

1 minute

<%= distance_of_time_in_words(Time.now, Time.now + 33, true) %> half a minute

<%= time_ago_in_words(Time.local(2004, 12, 25)) %>

116 days

<%= number_to_currency(123.45) %> $123.45

<%= number_to_currency(234.56, :unit => "CAN$", :precision => 0) %>

CAN$235.

<%= number_to_human_size(123_456) %>

120.6 KB

<%= number_to_percentage(66.66666) %>

66.667%

<%= number_to_percentage(66.66666, :precision => 1) %>

66.7%

<%= number_to_phone(2125551212) %>

212-555-1212

<%= number_to_phone(2125551212, :area_code => true, :delimiter => " ") %>

(212) 555 1212

<%= number_with_delimiter(12345678) %>

12,345,678

<%= number_with_delimiter(12345678, "_") %>

12_345_678

<%= number_with_precision(50.0/3) %>

16.667

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 474

The debug method dumps out its parameter using YAML and escapes the result so it can be displayed in an HTML page. This can help when trying to look at the values in model objects or request parameters.

<%= debug(params) %>

--- !ruby/hash:HashWithIndifferentAccess name: Dave

language: Ruby action: objects controller: test

Yet another set of helpers deal with text. There are methods to truncate strings and highlight words in a string (useful to show search results perhaps).

<%= simple_format(@trees) %>

Formats a string, honoring line and paragraph breaks. You could give it the plain text of the Joyce Kilmer poem Trees, and it would add the HTML to format it as follows.

<p> I think that I shall never see <br />A poem lovely as a tree.</p>

<p>A tree whose hungry mouth is prest

<br />Against the sweet earth’s flowing breast; </p>

<%= excerpt(@trees, "lovely", 8) %>

...A poem lovely as a tre...

<%= highlight(@trees, "tree") %>

I think that I shall never see

A poem lovely as a <strong class="highlight">tree</strong>.

A <strong class="highlight">tree</strong> whose hungry mouth is prest Against the sweet earth’s flowing breast;

<%= truncate(@trees, 20) %>

I think that I sh...

There’s a method to pluralize nouns.

<%= pluralize(1, "person") %> but <%= pluralize(2, "person") %>

1 person but 2 people

If you’d like to do what the fancy web sites do and automatically hyperlink URLs and e-mail addresses, there are helpers to do that. There’s another that strips hyperlinks from text.

Back on page 91 we saw how the cycle helper can be used to return the successive values from a sequence each time it’s called, repeating the sequence

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 475

as necessary. This is often used to create alternating styles for the rows in a table or list.

Finally, if you’re writing something like a blog site, or you’re allowing users to add comments to your store, you could offer them the ability to create their text in Markdown (BlueCloth)2 or Textile (RedCloth)3 format. These are simple formatters that take text with very simple, human-friendly markup and convert it into HTML. If you have the appropriate libraries installed on your system,4 this text can be rendered into views using the markdown and textilize helper methods.

Linking to Other Pages and Resources

The ActionView::Helpers::AssetTagHelper and ActionView::Helpers::UrlHelper modules contain a number of methods that let you reference resources external to the current template. Of these, the most commonly used is link_to, which creates a hyperlink to another action in your application.

<%= link_to "Add Comment", :action => "add_comment" %>

The first parameter to link_to is the text displayed for the link. The next is a hash specifying the link’s target. This uses the same format as the controller url_for method, which we discussed back on page 400.

A third parameter may be used to set HTML attributes on the generated link.

<%= link_to "Delete", { :action => "delete", :id => @product}, { :class => "dangerous" }

%>

This third parameter supports three additional options that modify the behavior of the link. Each requires JavaScript to be enabled in the browser. The :confirm option takes a short message. If present, JavaScript will be generated to display the message and get the user’s confirmation before the link is followed.

<%= link_to "Delete", { :action => "delete", :id => @product},

{:class => "dangerous", :confirm => "Are you sure?" }

%>

The :popup option takes either the value true or a two-element array of window creation options (the first element is the window name passed to the JavaScript window.open method; the second element is the option string). The response to the request will be displayed in this pop-up window.

2. http://bluecloth.rubyforge.org/

3.http://www.whytheluckystiff.net/ruby/redcloth/

4.If you use RubyGems to install the libraries, you’ll need to add an appropriate require_gem to

your environment.rb.

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 476

<%= link_to "Help", { :action => "help" },

:popup => ['Help', 'width=200,height=150']

%>

The :method option is a hack—it allows you to make the link look to the application as if the request were created by a POST, PUT, or DELETE, rather than the normal GET method. This is done by creating a chunk of JavaScript that submits the request when the link is clicked—if JavaScript is disabled in the browser, a GET will be generated.

<%= link_to "Delete", { :controller => 'articles', :id => @article },

:method => :delete

%>

The button_to method works the same as link_to but generates a button in a self-contained form, rather than a straight hyperlink. As we discussed in Section 21.6, The Problem with GET Requests, on page 462, this is the preferred method of linking to actions that have side effects. However, these buttons live in their own forms, which imposes a couple of restrictions: they cannot appear inline, and they cannot appear inside other forms.

Rails has conditional linking methods that generate hyperlinks if some condition is met and just return the link text otherwise. link_to_if and link_to_unless take a condition parameter, followed by the regular parameters to link_to. If the condition is true (for link_to_if) or false (for link_to_unless) a regular link will be created using the remaining parameters. If not, the name will be added as plain text (with no hyperlink).

The link_to_unless_current helper is used to create menus in sidebars where the current page name is shown as plain text and the other entries are hyperlinks.

<ul>

<% %w{ create list edit save logout }.each do |action| -%>

<li>

<%= link_to_unless_current(action.capitalize, :action => action) %>

</li>

<% end -%>

</ul>

As with url_for, link_to and friends also support absolute URLs.

<%= link_to("Help" , "http://my.site/help/index.html") %>

The image_tag helper can be used to create <img> tags. The image size may be specified using a single :size parameter (of the form widthxheight) or by explictly giving the width and height as separate parameters.

<%= image_tag("/images/dave.png", :class => "bevel", :size => "80x120") %> <%= image_tag("/images/andy.png", :class => "bevel",

:width => "80", :height => "120") %>

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 477

If you don’t give an :alt option, Rails synthesizes one for you using the image’s filename.

If the image path doesn’t start with a / character, Rails assumes that it lives under the /images directory. If it doesn’t have a file extension, Rails currently assumes .png, but this will be an error in Rails 2.0.

You can make images into links by combining link_to and image_tag.

<%= link_to(image_tag("delete.png", :size => "50x22"), { :controller => "admin",

:action

=> "delete",

:id

=>

@product},

{ :confirm

=>

"Are you sure?" })

%>

The mail_to helper creates a mailto: hyperlink that, when clicked, normally loads the client’s e-mail application. It takes an e-mail address, the name of the link, and a set of HTML options. Within these options, you can also use :bcc, :cc, :body, and :subject to initialize the corresponding e-mail fields. Finally, the magic option :encode=>"javascript" uses client-side JavaScript to obscure the generated link, making it harder for spiders to harvest e-mail addresses from your site.5

<%= mail_to("support@pragprog.com", "Contact Support", :subject => "Support question from #{@user.name}", :encode => "javascript") %>

As a weaker form of obfuscation, you can use the :replace_at and :replace_dot options to replace the at sign and dots in the displayed name with other strings. This is unlikely to fool harvesters.

The AssetTagHelper module also includes helpers that make it easy to link to stylesheets and JavaScript code from your pages and to create autodiscovery RSS or Atom feed links. We created a stylesheet link in the layouts for the Depot application, where we used stylesheet_link_tag in the head.

Download depot_r/app/views/layouts/store.rhtml

<%= stylesheet_link_tag "depot", :media => "all" %>

The javascript_include_tag method takes a list of JavaScript filenames (assumed to live in public/javascripts) and creates the HTML to load these into a page. head. As a shortcut you can pass it the parameter :defaults, in which case it loads the files prototype.js, effects.js, dragdrop.js, and controls.js, along with application.js if it exists. Use the latter file to add your own JavaScript to your application’s pages.6

5.But it also means your users won’t see the e-mail link if they have JavaScript disabled in their browsers.

6.Writers of plugins can arrange for their own JavaScript files to be loaded when an application

specifies :defaults, but that’s beyond the scope of this book.

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 478

An RSS or Atom link is a header field that points to a URL in our application. When that URL is accessed, the application should return the appropriate RSS or Atom XML.

<html>

<head>

<%= auto_discovery_link_tag(:rss, :action => 'rss_feed') %>

</head>

. . .

Finally, the JavaScriptHelper module defines a number of helpers for working with JavaScript. These create JavaScript snippets that run in the browser to generate special effects and to have the page dynamically interact with our application. That’s the subject of a separate chapter, Chapter 23, The Web, V2.0, on page 521.

By default, image and stylesheet assets are assumed to live in the images and stylesheets directories relative to the application’s public directory. If the path given to an asset tag method includes a forward slash, then the path is assumed to be absolute, and no prefix is applied. Sometimes it makes sense to move this static content onto a separate box or to different locations on the current box. Do this by setting the configuration variable asset_host.

ActionController::Base.asset_host = "http://media.my.url/assets"

Pagination Helpers

A community site might have thousands of registered users. We might want to create an administration action to list these, but dumping thousands of names to a single page is somewhat rude. Instead, we’d like to divide the output into pages and allow the user to scroll back and forth in these.

Rails uses pagination to do this. Pagination works at the controller level and at the view level. In the controller, it controls which rows are fetched from the database. In the view, it displays the links necessary to navigate between different pages.

Let’s start in the controller. We’ve decided to use pagination when displaying the list of users. In the controller, we declare a paginator for the users table.

Download e1/views/app/controllers/pager_controller.rb

def user_list

@user_pages, @users = paginate(:users, :order => 'name') end

The declaration returns two objects. @user_pages is a paginator. It divides the user model objects into pages, each containing by default 10 rows. It also fetches a pageful of users into the @users variable. This can be used by our view to display the users, 10 at a time. The paginator knows which set of users to

Report erratum

HELPERS FOR FORMATTING, LINKING, AND PAGINATION 479

show by looking for a request parameter, by default called page. If a request comes in with no page parameter, or with page=1, the paginator sets @users to the first 10 users in the table. If page=2, the 11th through 20th users are returned. (If you want to use some parameter other than page to determine the page number, you can override it. See the Rails API documentation for more information.)

Over in the view file user_list.rhtml, we display the users using a conventional loop, iterating over the @users collection created by the paginator. We use the pagination_links helper method to construct a nice set of links to other pages. By default, these links show the two page numbers on either side of the current page, along with the first and last page numbers.

Download e1/views/app/views/pager/user_list.rhtml

<table> <tr><th>Name</th></tr>

<% for user in @users %>

<tr><td><%= user.name %></td></tr>

<% end %>

</table>

<hr>

<%= pagination_links(@user_pages) %>

<hr>

Navigate to the user_list action, and you’ll see the first page of names. Click the number 2 in the pagination links at the bottom, and the second page will appear.

This example represents the middle-of-the-road pagination: we define the pagination explicitly in our user_list action. We could also have defined pagination

Report erratum