9.2. Built-in Template Engines

A template enables the view to dynamically generate the response's body by combining dynamic values, like instance variables and the flash object, and static content like HTML tags and text. Throughout this book you've been exposed to ERb templates as a means of generating XHTML documents. Yet ERb is not limited to this task and can be used to generate any other type of document that requires a mix of static and dynamic content (that is, Ruby code).

For the sake of convenience, Rails ships with two other built-in template engines: Builder and RJS. And whereas ERb is normally used for XHTML pages, Builder is particularly handy for XML content and RJS for writing JavaScript responses in Ruby code.

To tell Rails what document type is going to be rendered, and what template engine is going to be used, you add two extensions to each template: myfile.html.erb for XHTML produced through ERb, myfile.xml.builder for XML produced through Builder, and myfile.js.rjs for JavaScript produced through RJS.

Note that for historical reasons, the old extension format is still accepted. If you work with legacy code, you may find the equivalent myfile.rhtml, myfile.rxml, and myfile.rjs. As you upgrade an existing project to Rails 2.2 or a newer version, you can safely rename them.

9.2.1. ERb

ASP.NET developers are used to thinking in terms of controls that are positioned within a page. In ERb templates, the approach is entirely different. In fact, most templates contain regular XHTML code intermingled with Ruby code that gets evaluated at runtime. Expressions evaluated inline are contained between the tags <%= and %>, whereas Ruby code that doesn't need to produce an output is contained between <% and %>. The amount of Ruby code is kept to a minimum, because the view layer is supposed to have as little application logic as possible, and focus on the presentation of the content instead.

In many code bases you'll often find -%> as the closing tag. As mentioned before, this is used to trim the output from extra newlines so that it results in more compact XHTML code.

This means that a typical ERb template will evaluate instance variables declared in the associated controller, display flash messages, and dynamically control the structure of the page with a few conditional statements and iterations/loops (to display data contained within a collection of objects).

Because there are no "fancy controls" available in Rails, and you don't want business logic in the view, ERb templates rely instead on predefined helpers like the form_for or link_to or user-made helpers.

There is a secondary and very practical reason why ERb templates are supposed to be fairly straightforward: the view templates are often handed over to the designer, who has to be able to understand and modify them to make the page look as he or she would prefer it to. Most of their work will probably be accomplished through CSS files, but keeping the view clean from complex logic can only facilitate their job, and in turn avoid accidental changes that can break your application.

As a general rule of thumb, try to centralize the information within the controller, so that the view is limited to the presentation of the data. For example, consider the following trivial template:

<h2>Discount Coupon</h2>
<p>Your coupon <%= @coupon %> is valid until <%= @expiration = 7.days.from_now %>.</p>

<!-- some more HTML -->

<p>Don't forget to take advantage of your discount before <%= @expiration %>.</p>

7.days.from_now is not standard Ruby. This highly readable expression is possible thanks to the extensions provided by ActiveSupport.

Notice how you are able to assign a variable (@expiration) and then evaluate it a few lines later. This works but it has a problem. Variables are better assigned in a centralized place, and that place is the controller. In fact, tomorrow you might decide that the coupons are going to expire in 14 days instead and you'll be forced to track each view template that hard codes 7.days.from_now to change it. Furthermore, as the application's requirements change, you may require the application of different coupon expiration policies depending on the customer that's currently logged in. Remember the mantra, Don't Repeat Yourself!

An initial quick refactoring leads you to place @expiration = 7.days.from_now within the controller's action and replace <%= @expiration = 7.days.from_now %> in the template with <%= @expiration %>.

To improve it further, you'd probably also want to get rid of that magic number (7), and use a constant to store that value instead.

I discussed this point amply while developing the blog example, but it warrants repeating. There is a big security risk in simply using <%= when the evaluated expression is originated by a user. In fact, the expression could contain malicious script code and this would be interpreted nevertheless. For this reason, most Rails users almost have an automatic reflex to place an h after the opening tag (that is, <%=h), so that the html_escape helper, whose alias is h, is invoked on the expression that's being evaluated. html_escape escapes HTML tag characters so that "<script>" is rendered in the response as " &lt;script&gt;".

NOTE

Always use <%=h as the opening tag when you want to evaluate any expression that could potentially be unsafe (for example, comes from the user). If you need to allow certain tags, consider using an alternative helper like sanitize.

9.2.2. Builder

Builder templates are called as such because of the homonymous library they use, the aim of which is to provide a simple way to generate XML. The basic concept is that you can use regular Ruby code and a special xml object to generate any XML content. Arbitrarily named methods are translated into tags, the first parameter passed to these methods is translated into the value contained within the pair of tags, and any other parameter passed in a hash form will be an attribute. It's also possible to nest tags by using Ruby blocks.

The following example should clarify this:

xml.message do
 xml.time(Time.now)
 xml.from(@sender)
 xml.to(@receiver)
 xml.body("Don't forget the milk", :type => "plain")
end

This produces an XML document like the following:

<message>
  <time>Tue Nov 5 22:23:00 −0500 2008</time>
  <from>Jessica</from>
  <to>Tony</to>
  <body type="plain">Don't forget the milk</book>
</message>

The do after xml.message indicates that all the element's content within the block should be contained within the <message></message> tags. Also notice how :type was passed as a second parameter to add the type attribute to the body element.

A much more practical example of how to use Builder templates is provided in the section titled "Adding an RSS and Atom Feed," in which an RSS and Atom feed will be added to your blog application.

This library is quite flexible and you are welcome to check out further information about its usage online at http://builder.rubyforge.org.

9.2.3. RJS

RJS templates are used to generate a JavaScript that's executed by the browser in response to an Ajax request. Much like other template types, when an Ajax request is received, Rails looks for the associated .js.rjs file within appviews and the subfolder named after the current controller.

Notice that you may find yourself in situations where you wish to use ERb, as opposed to RJS, as the template engine for your JavaScript. This typically happens when you need to insert JavaScript code in a template. In this case the file would have the extension .js.erb.

RJS templates are usually employed when the page needs to be updated without a refresh, and are particularly useful when grouping a series of changes to the page, which are triggered by a simple action like clicking a link or a button.

Although exploring the vast topic of Ajax programming is beyond the scope of this book, a small example that showcases how to work with RJS templates is shown in the second half of this chapter, in the section "Adding a Sprinkle of Ajax."

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.83.199