Action View - The Rails 4 Way (2014)

The Rails 4 Way (2014)

Chapter 10. Action View

The very powerful and the very stupid have one thing in common. Instead of altering their views to fit the facts, they alter the facts to fit their views…which can be very uncomfortable if you happen to be one of the facts that needs altering.

—Doctor Who

Controllers are the skeleton and musculature of your Rails application. In which case, models form the heart and mind, and your view templates (based on Action View, the third major component of Rails) are your application’s skin—the part that is visible to the outside world.

Action View is the Rails API for putting together the visual component of your application, namely the HTML and associated content that will be rendered in a web browser whenever someone uses your Rails application. Actually, in this brave new world of REST resources, Action View is involved in generating almost any sort of output you generate.

Action View contains a full-featured templating system based on a Ruby library named ERb. It takes data prepared by the controller layer and interleaves it with view code to create a presentation layer for the end user. It’s also one of the first things you learn about Rails and part of the standard Ruby library. I much prefer a templating solution named Haml38 and have used it all over the book for examples. I think Haml is such a superior choice over ERb, that this edition does not cover ERb at all.

In this chapter, we cover the fundamentals of the Action View framework, from effective use of partials, to the significant performance boosts possible via caching. If you need to learn Haml, it’s covered in detail in Chapter 12.

10.1 Layouts and Templates

Rails has easy conventions for template usage, related to the location of templates with the Rails project directories.

The app/views directory contains subdirectories corresponding to the name of controllers in your application. Within each controller’s view subdirectory, you place a template named to match its corresponding action.

The special app/views/layout directory holds layout templates, intended to be reusable containers for your views. Again, naming conventions are used to determine which templates to render, except that this time it is the name of the controller that is used for matching.

10.1.1 Template Filename Conventions

The filename of a template in Rails carries a lot of significance. It’s parts, delimited with periods, correspond to the following information:

· name (usually maps to action)

· locale (optional)

· content type

· templating engine(s)

· variant (optional, new in Rails 4.1)

10.1.2 Layouts

Action View decides which layout to render based on the inheritance hierarchy of controllers being executed. Most Rails applications have an application.html.haml file in their layout directory. It shares its name with the ApplicationController, which is typically extended by all the other controllers in an application; therefore it is picked up as the default layout for all views.

It is picked up, unless of course, a more specific layout template is in place, but quite often it makes sense to use just one application-wide template, such as the simple one shown in Listing 10.1.

Listing 10.1: A simple general-purpose application.html.haml layout template


1 !!! 5

2 %html

3 %head

4 %meta{ charset: 'utf-8' }

5 %title TR4W Time and Expenses Sample Application

6 = csrf_meta_tag

7 = stylesheet_link_tag 'application', media: 'all'

8 %body

9 = yield


10.1.3 Yielding Content

The Ruby language’s built-in yield keyword is put to good use in making layout and action templates collaborate. Notice the use of yield at the end of the layout template:

1 %body

2 = yield

In this case, yield by itself is a special message to the rendering system. It marks where to insert the output of the action’s rendered output, which is usually the template corresponding to that action.

You can add extra places in your layout where you want to be able to yield content, by including additional yield invocations—just make sure to pass a unique identifier as the argument. A good example is a layout that has left and right sidebar content (simplified, of course):

1 %body

2 .left.sidebar

3 = yield :left

4 .content

5 = yield

6 .right.sidebar

7 = yield :right

The .content div receives the main template markup generated. But how do you give Rails content for the left and right sidebars? Easy—just use the content_for method anywhere in your template code. I usually stick it at the top of the template so that it’s obvious.

1 - content_for :left do

2 %h2 Navigation

3 %ul

4 %li ...

5

6 - content_for :right do

7 %h2 Help

8 %p Lorem ipsum dolor sit amet, consectetur adipisicing elit...

9

10 %h1 Page Heading

11 %p ...

Besides sidebars and other types of visible content blocks, I suggest you yield for additional content to be added to the HEAD element of your page, as shown in Listing 10.2.

Listing 10.2: Yielding additional head content


1 !!! 5

2 %html

3 %head

4 %meta{ charset: 'utf-8' }

5 %title TR4W Time and Expenses Sample Application

6 = csrf_meta_tag

7 = stylesheet_link_tag 'application', media: 'all'

8 = yield :head

9 %body

10 = yield


discussion

Kevin says…

Yielding in the HEAD element is also a great technique to include page specific meta tags, such as those required for Facebook Open Graph.

10.1.4 Conditional Output

One of the most common idioms you’ll use when coding Rails views is to conditionally output content to the view. The most elementary way to control conditional output is to use if statements.

1 - if show_subtitle?

2 %h2= article.subtitle

A lot of times you can use inline if conditions and shorten your code, since the = outputter doesn’t care if you feed it a nil value. Just add a postfix if condition to the statement:

%h2= article.subtitle if show_subtitle?

Of course, there’s a problem with the preceding example. The if statement on a separate line will eliminate the <h2> tags entirely, but the one-liner second example does not.

There are a couple of ways to deal with the problem and keep it a one-liner. First, there’s the butt-ugly solution that I’ve occasionally seen in some Rails applications, which is the only reason why I’m mentioning it here!

= "<h2>#{h(article.subtitle)}</h2>".html_safe if show_subtitle?

A more elegant solution involves Rails’ content_tag helper method, but admittedly a one-liner is probably not superior to its two-line equivalent in this case.

= content_tag('h2', article.subtitle) if show_subtitle?

Helper methods, both the ones included in Rails like content_tag and the ones that you’ll write on your own, are your main tool for building elegant view templates. Helpers are covered extensively in Chapter 11, “All About Helpers”.

10.1.5 Decent Exposure

We’ve seen how layouts and yielding content blocks work, but other than that, how should data get from the controller layer to the view? During preparation of the template, instance variables set during execution of the controller action will be copied over as instance variables of the template context. Even though it’s the standard way exposed by Rails documentation, sharing state via instance variables in controllers promotes close coupling with views.

Stephen Caudill’s Decent Exposure gem39 provides a declarative manner of exposing an interface to the state that controllers contain, thereby decreasing coupling and improving your testability and overall design.

When invoked, expose macro creates a method with the given name, evaluates the provided block and memoizes the result. This method is then declared as a helper_method so that views may have access to it and is made unroutable as an action. When no block is given, expose attempts to intuit which resource you want to acquire:

# Timesheet.find(params[:timesheet_id] || params[:id])

expose(:timesheet)

As the example shows, the symbol passed is used to guess the class name of the object you want to find—useful since almost every controller in a normal Rails uses this kind of code in the show, edit, update and destroy actions.

In a slightly more complicated scenario, you might need to find an instance of an object which doesn’t map cleanly to a simple find method.

expose(:timesheet) { client.timesheets.find(params[:id]) }

In the RESTful controller paradigm, you’ll again find yourself using this in show, edit, update and destroy actions of nested resources.

When the code has become long enough to surpass a single line (but still isn’t appropriate to extract into a model method), use a do...end style of block, as in the following that uses all three styles:

1 expose(:client)

2

3 expose(:timesheet) { client.timesheets.find(params[:id]) }

4

5 expose(:timesheet_approval_presenter) do

6 TimesheetApprovalPresenter.new(timesheet, current_user)

7 end

The previous example also demonstrates how expose declarations can depend on each other. In fact, proper use of expose should eliminate most model-lookup code from your actual controller actions.

At Hashrocket, use of Decent Exposure has proven so beneficial that it has completely replaced direct use of instance variables in controllers and views. The helper methods created by the expose macro are just referred to directly in the view.

10.1.6 Standard Instance Variables

More than just instance variables from the controller are copied over to the template. It’s not a good idea to depend on some of the following objects directly, and especially not to use them to do data operations. Others are a standard part of most Rails applications.

10.1.6.1 assigns

Want to see everything that comes across the controller-view boundary? Throw = debug(assigns) into your template and take a look at the output. The assigns attribute is essentially internal to Rails and you should not use it directly in your production code.

10.1.6.2 base_path

Local filesystem path pointing to the base directory of your application where templates are kept.

10.1.6.3 controller

The current controller instance is made available via controller, before it goes out of scope at the end of request processing. You can take advantage of the controller’s knowledge of its name (via the controller_name attribute) and the action that was just performed (via the action_nameattribute), in order to structure your CSS more effectively.

%body{ class: "#{controller.controller_name}#{controller.action_name}" }

That would result in a BODY tag looking something like this, depending on the action executed:

<body class="timesheets index">

Note

You could also replicate the functionality in the previous example by using the Haml helper method page_class.

%body{ class: page_class }

Hopefully you already know that the C in CSS stands for cascading, which refers to the fact that class names cascade down the tree of elements in your markup code and are available for creation of rules. The trick is to automatically include the controller and action name as classnames of your body element, so that you can use them to customize look and feel of the page very flexibly later on in the development cycle. For example, here’s how you would use the technique to vary the background of header elements depending on the controller path in SCSS:

1 body {

2 .timesheets.header {

3 background: image_url(timesheet-bg.png) no-repeat left top;

4 }

5

6 .expense_reports.header {

7 background: image_url(expense-reports-bg.png) no-repeat left top;

8 }

9 }

10.1.6.4 cookies

The cookies variable is a hash containing the user’s cookies. There might be situations where it’d be okay to pull values out to affect rendering, but most of the time you’ll be using cookies in your controller, not the view.

10.1.6.5 flash

The flash has popped up in larger code samples throughout the book so far, whenever you want to send the user a message from the controller layer, but only for the duration of the next request.

1 def create

2 if user.try(:authorize, params[:user][:password])

3 flash[:notice] = "Welcome, #{user.first_name}!"

4 redirect_to home_url

5 else

6 flash[:alert] = "Login invalid."

7 redirect_to :new

8 end

9 end

A common Rails practice is to use flash[:notice] to hold benign notice messages, and flash[:alert] for communication of a more serious nature.

Note

It’s so common to set flash notice and alert messages on redirects that Rails allows you to set them in the redirect_to method as optional parameters.

1 def create

2 if user.try(:authorize, params[:user][:password])

3 redirect_to home_url, notice: "Welcome, #{user.first_name}!"

4 else

5 redirect_to home_url, alert: "Bad login"

6 end

7 end

Special accessors for notices and alerts are included as helper methods on the flash object itself, since their use is so common.

1 def create

2 if user.try(:authorize, params[:user][:password])

3 flash.notice = "Welcome, #{user.first_name}!"

4 redirect_to home_url

5 else

6 flash.alert = "Login invalid."

7 redirect_to action: "new"

8 end

9 end

10.1.7 Displaying flash messages

Personally, I like to conditionally output both notice and alert messages in div elements, right at the top of my layout, and use CSS to style them, as shown in Listing 10.3:

Listing 10.3: Standardized flash notice and error placement in application.html.haml


1 %html

2 ...

3 %body

4 - if flash.notice

5 .notice= flash.notice

6 - if flash.alert

7 .notice.alert= flash.alert

8

9 = yield


The CSS for .notice defines most of the style for the element, and .alert overrides just the aspects that are different for alerts.

10.1.8 flash.now

Sometimes you want to give the user a flash message, but only for the current request. In fact, a common newbie Rails programming mistake is to set a flash notice and not redirect, thereby incorrectly showing a flash message on the following request.

It is possible to make flash cooperate with a render by using the flash.now method.

1 classReportController < ActionController::Base

2 def create

3 if report.save

4 flash.notice = "#{report.title} has been created."

5 redirect_to report_path(report)

6 else

7 flash.now.alert = "#{@post.title} could not be created."

8 render :new

9 end

10 end

11 end

The flash.now object also has notice and alert accessors, like its traditional counterpart.

10.1.8.1 logger

Have something to record for posterity in the logs while you’re rendering the view? Use the logger method to get the view’s Logger instance, the same as Rails.logger, unless you’ve changed it.

10.1.8.2 params

This is the same params hash that is available in your controller, containing the key/value pairs of your request. I’ll occasionally use a value from the params hash directly in the view, particularly when I’m dealing with pages that are subject to filtering or row sorting.

1 %p

2 Filter by month:

3 = select_tag(:month_filter,

4 options_for_select(@month_options, params[:month_filter]))

It’s very dangerous from a security perspective to put unfiltered parameter data into the output stream of your template. The following section, “Protecting the Integrity of Your View from User-Submitted Content,” covers that topic in depth.

10.1.8.3 request and response

The HTTP request and response objects are exposed to the view, but other than for debugging purposes, I can’t think of any reason why you would want to use them directly from your template.

10.1.8.4 session

The session variable is the user’s session hash. There might be situations where it’d be okay to pull values out to affect rendering, but I shudder to think that you might try to set values in the session from the view layer. Use with care, and primarily for debugging, just like request andresponse.

10.2 Partials

A partial is a fragment of template code. The Rails way is to use partials to factor view code into modular chunks that can be assembled in layouts with as little repetition as possible. In older versions of Rails, the syntax for including a partial within a template started with render :partial, but now passing a string to render within your view will get interpreted to mean you want to render a partial. Partial template names must begin with an underscore, which serves to set them apart visually within a given view template directory. However, you leave the underscore out when you refer to them.

1 %h1 Details

2 = render 'details'

10.2.1 Simple Use Cases

The simplest partial use case is simply to extract a portion of template code. Some developers divide their templates into logical parts by using partial extraction. Sometimes it is easier to understand the structure of a screen if the significant parts are factored out of it. For instance, Listing 10.4 is a simple user registration screen that has its parts factored out into partials.

Listing 10.4: Simple user registration form with partials


1 %h1 User Registration

2 = error_messages_for :user

3 = form_for :user, url: users_path do

4 .registration

5 .details.demographics

6 = render 'details'

7 = render 'demographics'

8 .location

9 = render 'location'

10 .opt_in

11 = render 'opt_in'

12 .terms

13 = render 'terms'

14 %p= submit_tag 'Register'


While we’re at it, let me pop open one of those partials. To conserve space, we’ll take a look at one of the smaller ones, the partial containing the opt-in check boxes of this particular app. The source is in Listing 10.5; notice that its name begins with an underscore.

Listing 10.5: The opt-in partial in the file app/views/users/_opt_in.html.haml


1 %fieldset#opt_in

2 %legend Spam Opt In

3 %p

4 = check_box :user, :send_event_updates

5 Send me updates about events!

6 %br

7 = check_box :user, :send_site_updates

8 Notify me about new services


Personally, I like partials to be entirely contained inside a semantically significant markup container. In the case of the opt-in partial in Listing 10.5, both check box controls are contained inside a single fieldset element, which I’ve given an id attribute. Following that rule, more as a loose guideline than anything else, helps me to mentally identify how the contents of this partial are going to fit inside the parent template. If we were dealing with other markup, perhaps outside of a form, I might choose to wrap the partial markup inside a well-identified div container, instead of afieldset.

Why not include the td markup inside the partial templates? It’s a matter of style—I like to be able to see the complete markup skeleton in one piece. In this case, the skeleton is the table structure that you see in Listing 10.4. If portions of that table were inside the partial templates, it would obfuscate the layout of the page. I do admit that this is one of those areas where personal style and preference should take precedence and I can only advise you as to what has worked for me, personally.

10.2.2 Reuse of Partials

Since the registration form is neatly factored out into its component parts, it is easy to create a simple edit form using some of its partials, as in Listing 10.6.

Listing 10.6: Simple user edit form reusing some of the same partials


1 %h1 Edit User

2 = form_for :user, url: user_path(@user), method: :put do

3 .settings

4 .details

5 = render 'details'

6 .demographics

7 = render 'demographics'

8 .opt_in

9 = render 'opt_in'

10 %p= submit_tag 'Save Settings'


If you compare Listings 10.4 and 10.6, you’ll notice that the structure of the table changed a little bit in the Edit form, and it has less content than the registration form. Perhaps the location is handled in greater detail on another screen, and certainly you don’t want to require agreement of terms every time the user changes her settings.

10.2.3 Shared Partials

Until now we’ve been considering the use of partials that reside in the same directory as their parent template. However, you can easily refer to partials that are in other directories, just by prefixing the directory name. You still leave off the underscore, which has always felt a little weird.

Let’s add a captcha partial to the bottom of the registration form from Listing 10.4, to help prevent spammers from invading our web application:

1 ...

2 .terms

3 = render 'terms'

4 .captcha

5 = render 'shared/captcha'

6 %p= submit_tag 'Register'

Since the captcha partial is used in various different parts of the application, it makes sense to let it reside in a shared folder rather than any particular view folder. However, you do have to be a little bit careful when you move existing template code into a shared partial. It’s quite possible to inadvertently craft a partial that depends implicitly on where it’s rendered.

For example, take the case of the Rails-talk mailing list member with a troublesome partial defined in login/_login.html.haml:

1 = form_tag do

2 %fieldset

3 %label

4 Username:

5 = text_field_tag :username, params[:username]

6 %br

7 %label

8 Password:

9 = password_field_tag :password, params[:password]

10 %br

11 = submit_tag "Login"

The login form submission worked when he rendered this partial as part of the login controller’s login action (“the login page”), but not when it was included as part of the view for any other section of his website. The problem is that form_tag (covered in the next chapter) normally takes an optional action parameter telling it where to post its information. If you leave out the action, the form will post back to its current URL, which will vary for shared partials, depending on where they’re being used from.

10.2.4 Passing Variables to Partials

Partials inherit the method exposed to their parent templates implicitly. That’s why the form helpers used in the partials of Listings 10.4 and 10.6 work: They rely implicitly on an user method to be in scope. I feel it’s fine to use this implicit sharing in some cases, particularly when the partials are tightly bound to their parent templates. It would be especially true in cases where the only reason you broke out a partial in the first place was to reduce the size and complexity of a particularly large template.

However, once you get into the practice of breaking out partial templates for reuse, depending on implicit context gets a lot more dicey. That’s why Rails supports the passing of locally scoped variables to partial templates, as in the following snippet:

= render 'shared/address', form: form

The values of the optional hash are converted into locally scoped variables (no @ sign) in the partial. Listing 10.7 is a variation on the registration template. This time we’re using the version of form_for that yields a block parameter representing the form to its form helper methods. We’ll pass that form parameter on, too.

Listing 10.7: Simple user registration template passing form as local variable


1 %h1 User Registration

2 = form_for :user, url: users_path do |form|

3 .registration

4 .details.address.demographics

5 = render 'details', form: form

6 = render 'shared/address', form: form

7 %p= form.submit 'Register'


And finally, in Listing 10.8 we have the shared address form.

Listing 10.8: A simple shared address partial using local variable


1 %fieldset.address

2 %legend Address

3 %p

4 %label Street

5 %br

6 = form.text_area :street, rows: 2, cols: 40

7 %p

8 %label City

9 %br

10 = form.text_field :city

11 %p

12 %label State

13 %br

14 = form.text_field :state, size: 2

15 %p

16 %label Zip

17 %br

18 = form.text_field :zip, size: 15


The form helper methods, which we’ll cover in Chapter 11, “All About Helpers”, have a variation in which they are called on the form variable yielded by the form_for method. That is exactly what we passed on to these partials

10.2.4.1 The local_assigns Hash

If you need to check for the presence of a certain local variable in a partial, you need to do it by checking the local_assigns hash that is part of every template. Using defined? variable won’t work due to limitations of the rendering system.

1 - if local_assigns.has_key? :special

2 = special

10.2.5 Rendering an Object

The render method also provides a shorthand syntax to render an object into a partial, which strictly depends on Rails naming conventions.

= render entry

The partial corresponding to the last code snippet is named _entry.html.haml and gets a local variable named entry. This is equivalent to the following:

= render partial: 'entry', object: entry

To set a different local variable name other than the name of the partial, one could use the locals hash as seen earlier in the chapter, or specify the desired name through the :as option.

= render partial: 'entry', object: some_entry, as: :item

10.2.6 Rendering Collections

One of the best uses of partials is to render collections. Once you get into the habit of rendering collections with partials, you won’t want to go back to the relative ugliness of cluttering your templates with for loops and each. When the render method gets an Enumerable as its first argument, it assumes that you want to render a collection of partials.

render entries

Simple and precise yet very dependent on a naming conventions. The objects being rendered are exposed to the partial template as a local variable named the same as the partial template itself. In turn the template should be named according to the class of the objects being rendered.

The partial corresponding to the last code snippet is named _entry.html.haml and gets a local variable named entry.

1 = div_for(entry) do

2 = entry.description

3 #{distance_of_time_in_words_to_now entry.created_at} ago

discussion

Kevin says…

If the collection passed into the render method is empty, nil is returned. Using this knowledge, you can write code such as

= render(entries) || "No entires exist"

to provide fallback content.

Since the partial template used is based on the class of each item, you can easily render a heterogeneous collection of objects. This technique is particularly useful in conjunction with collections of STI subclasses.

If you want to override that behavior, then revert to the older partial syntax and specify the :partial and :collection options explicitly like

partial: 'entry', collection: @entries

10.2.6.1 The partial_counter Variable

There’s another variable set for collection-rendered partials that doesn’t get much attention. It’s a 0-indexed counter variable that tracks the number of times a partial has been rendered. It’s useful for rendering numbered lists of things. The name of the variable is the name of the partial, plus_counter.

1 = div_for(entry) do

2 "#{entry_counter}:#{entry.description}

3 #{distance_of_time_in_words_to_now entry.created_at} ago"

10.2.6.2 Sharing Collection Partials

If you wanted to use the same partial that you use with a collection, except with a single entry object, you’d have to pass it that single instance via the locals hash described in the preceding section, like this:

render 'entry', entry: some_entry

10.2.7 Logging

If you take a look at your development log, you’ll notice that it shows which partials have been rendered and how long they took.

Rendering template within layouts/application

Rendering listings/index

Rendered listings/_listing 0.6ms)

Rendered listings/_listing 0.3ms)

Rendered listings/_listing 0.2ms)

Rendered listings/_listing 0.2ms)

Rendered listings/_listing 0.2ms)

Rendered layouts/_login 2.4ms)

Rendered layouts/_header 3.3ms)

Rendered layouts/_footer 0.1ms)

10.3 Conclusion

In this chapter, we’ve covered the Action View framework with a detailed explanation of templating and how the Rails rendering system works. We’ve also covered the use of partials in-depth, since their use is essential for effective Rails programming.

Now it’s time to cover the mechanism whereby you can inject a whole bunch of smarts into your view layer without cluttering up your templates: Helpers.