Reducing Maintenance with Layouts and Partials

So far in this chapter we’ve looked at templates as isolated chunks of code and HTML. But one of the driving ideas behind Rails is honoring the DRY principle and eliminating the need for duplication. The average website, though, has lots of duplication:

  • Many pages share the same tops, tails, and sidebars.

  • Multiple pages may contain the same snippets of rendered HTML (a blog site, for example, may display an article in multiple places).

  • The same functionality may appear in multiple places. Many sites have a standard search component or a polling component that appears in most of the sites’ sidebars.

Rails provides both layouts and partials that reduce the need for duplication in these three situations.

Layouts

Rails allows you to render pages that are nested inside other rendered pages. Typically this feature is used to put the content from an action within a standard site-wide page frame (title, footer, and sidebar). In fact, if you’ve been using the generate script to create scaffold-based applications, then you’ve been using these layouts all along.

When Rails honors a request to render a template from within a controller, it actually renders two templates. Obviously, it renders the one you ask for (or the default template named after the action if you don’t explicitly render anything). But Rails also tries to find and render a layout template (we’ll talk about how it finds the layout in a second). If it finds the layout, it inserts the action-specific output into the HTML produced by the layout.

Let’s look at a layout template:

 <html>
  <head>
  <title>Form: ​<%=​ controller.​action_name​ ​%>​</title>
 <%=​ stylesheet_link_tag ​'scaffold'​ ​%>
  </head>
  <body>
 
 <%=​ ​yield​ ​:layout​ ​%>
 
  </body>
 </html>

The layout sets out a standard HTML page, with the head and body sections. It uses the current action name as the page title and includes a CSS file. In the body, there’s a call to yield. This is where the magic takes place. When the template for the action was rendered, Rails stored its content, labeling it :layout. Inside the layout template, calling yield retrieves this text. In fact, :layout is the default content returned when rendering, so you can write yield instead of yield :layout. We personally prefer the slightly more explicit version.

If the my_action.html.erb template contained this:

 <h1>​<%=​ @msg ​%>​</h1>

and the controller set @msg to Hello, World!, then the browser would see the following HTML:

 <html>
  <head>
  <title>Form: my_action</title>
  <link href=​"/stylesheets/scaffold.css"​ media=​"screen"
  rel=​"Stylesheet"​ type=​"text/css"​ />
  </head>
  <body>
 
  <h1>Hello, World!</h1>
 
  </body>
 </html>

Locating Layout Files

As you’ve probably come to expect, Rails does a good job of providing defaults for layout file locations, but you can override the defaults if you need something different.

Layouts are controller-specific. If the current request is being handled by a controller called store, Rails will by default look for a layout called store (with the usual html.erb or xml.builder extension) in the app/views/layouts directory. If you create a layout called application in the layouts directory, it will be applied to all controllers that don’t otherwise have a layout defined for them.

You can override this using the layout declaration inside a controller. The most basic invocation is to pass it the name of a layout as a string. The following declaration will make the template in the file standard.html.erb or standard.xml.builder the layout for all actions in the store controller. The layout file will be looked for in the app/views/layouts directory:

 class​ StoreController < ApplicationController
 
  layout ​"standard"
 
 # ...
 end

You can qualify which actions will have the layout applied to them using the :only and :except qualifiers:

 class​ StoreController < ApplicationController
 
  layout ​"standard"​, ​except: ​[ ​:rss​, ​:atom​ ]
 
 # ...
 end

Specifying a layout of nil turns off layouts for a controller.

Sometimes you need to change the appearance of a set of pages at runtime. For example, a blogging site might offer a different-looking side menu if the user is logged in, or a store site might have different-looking pages if the site is down for maintenance. Rails supports this need with dynamic layouts. If the parameter to the layout declaration is a symbol, it’s taken to be the name of a controller instance method that returns the name of the layout to be used:

 class​ StoreController < ApplicationController
 
  layout ​:determine_layout
 # ...
 private
 
 def​ ​determine_layout
 if​ Store.​is_closed?
 "store_down"
 else
 "standard"
 end
 end
 end

Subclasses of a controller use the parent’s layout unless they override it using the layout directive. Finally, individual actions can choose to render using a specific layout (or with no layout at all) by passing render the :layout option:

 def​ ​rss
  render(​layout: ​​false​) ​# never use a layout
 end
 def​ ​checkout
  render(​layout: ​​"layouts/simple"​)
 end

Passing Data to Layouts

Layouts have access to all the same data that’s available to conventional templates. In addition, any instance variables set in the normal template will be available in the layout (because the regular template is rendered before the layout is invoked). This might be used to parameterize headings or menus in the layout. For example, the layout might contain this:

 <html>
  <head>
  <title>​<%=​ @title ​%>​</title>
 <%=​ stylesheet_link_tag ​'scaffold'​ ​%>
  </head>
  <body>
  <h1>​<%=​ @title ​%>​</h1>
 <%=​ ​yield​ ​:layout​ ​%>
  </body>
 </html>

An individual template could set the title by assigning to the @title variable:

 <%​ @title = ​"My Wonderful Life"​ ​%>
 <p>
  Dear Diary:
 </p>
 <p>
  Yesterday I had pizza for dinner. It was nice.
 </p>

We can take this further. The same mechanism that lets us use yield :layout to embed the rendering of a template into the layout also lets you generate arbitrary content in a template, which can then be embedded into any template.

For example, different templates may need to add their own template-specific items to the standard page sidebar. We’ll use the content_for mechanism in those templates to define content and then use yield in the layout to embed this content into the sidebar.

In each regular template, use a content_for to give a name to the content rendered inside a block. This content will be stored inside Rails and will not contribute to the output generated by the template:

 <h1>Regular Template</h1>
 
 <%​ content_for(​:sidebar​) ​do​ ​%>
  <ul>
  <li>this text will be rendered</li>
  <li>and saved for later</li>
  <li>it may contain ​<%=​ ​"dynamic"​ ​%>​ stuff</li>
  </ul>
 <%​ ​end​ ​%>
 <p>
  Here's the regular stuff that will appear on
  the page rendered by this template.
 </p>

Then, in the layout, use yield :sidebar to include this block in the page’s sidebar:

 <!DOCTYPE .... >
 <html>
  <body>
  <div class=​"sidebar"​>
  <p>
  Regular sidebar stuff
  </p>
  <div class=​"page-specific-sidebar"​>
» <​%=​ yield :sidebar ​%​>
  </div>
  </div>
  </body>
 </html>

This same technique can be used to add page-specific JavaScript functions into the <head> section of a layout, create specialized menu bars, and so on.

Partial-Page Templates

Web applications commonly display information about the same application object or objects on multiple pages. A shopping cart might display an order line item on the shopping cart page and again on the order summary page. A blog application might display the contents of an article on the main index page and again at the top of a page soliciting comments. Typically this would involve copying snippets of code between the different template pages.

Rails, however, eliminates this duplication with the partial-page templates (more frequently called partials). You can think of a partial as a kind of subroutine. You invoke it one or more times from within another template, potentially passing it objects to render as parameters. When the partial template finishes rendering, it returns control to the calling template.

Internally, a partial template looks like any other template. Externally, there’s a slight difference. The name of the file containing the template code must start with an underscore character, differentiating the source of partial templates from their more complete brothers and sisters.

For example, the partial to render a blog entry might be stored in the file _article.html.erb in the normal views directory, app/views/blog:

 <div class=​"article"​>
  <div class=​"articleheader"​>
  <h3>​<%=​ article.​title​ ​%>​</h3>
  </div>
  <div class=​"articlebody"​>
 <%=​ article.​body​ ​%>
  </div>
 </div>

Other templates use the render(partial:) method to invoke this:

 <%=​ render(​partial: ​​"article"​, ​object: ​@an_article) ​%>
 <h3>Add Comment</h3>
 . . .

The :partial parameter to render is the name of the template to render (but without the leading underscore). This name must be both a valid filename and a valid Ruby identifier (so a-b and 20042501 are not valid names for partials). The :object parameter identifies an object to be passed into the partial. This object will be available within the template via a local variable with the same name as the template. In this example, the @an_article object will be passed to the template, and the template can access it using the local variable article. That’s why we could write things such as article.title in the partial.

You can set additional local variables in the template by passing render a :locals parameter. This takes a hash where the entries represent the names and values of the local variables to set:

 render(​partial: ​​'article'​,
 object: ​@an_article,
 locals: ​{ ​authorized_by: ​session[​:user_name​],
 from_ip: ​request.​remote_ip​ })

Partials and Collections

Applications commonly need to display collections of formatted entries. A blog might show a series of articles, each with text, author, date, and so on. A store might display entries in a catalog, where each has an image, a description, and a price.

The :collection parameter to render works in conjunction with the :partial parameter. The :partial parameter lets us use a partial to define the format of an individual entry, and the :collection parameter applies this template to each member of the collection.

To display a list of article model objects using our previously defined _article.html.erb partial, we could write this:

 <%=​ render(​partial: ​​"article"​, ​collection: ​@article_list) ​%>

Inside the partial, the local variable article will be set to the current article from the collection—the variable is named after the template. In addition, the variable article_counter will have its value set to the index of the current article in the collection.

The optional :spacer_template parameter lets you specify a template that will be rendered between each of the elements in the collection. For example, a view might contain the following:

 <%=​ render(​partial: ​​"animal"​,
 collection: ​​%w{ ant bee cat dog elk }​,
 spacer_template: ​​"spacer"​)
 %>

This uses _animal.html.erb to render each animal in the given list, rendering the partial _spacer.html.erb between each. If _animal.html.erb contains this:

 <p>The animal is ​<%=​ animal ​%>​</p>

and _spacer.html.erb contains this:

 <hr />

your users would see a list of animal names with a line between each.

Shared Templates

If the first option or :partial parameter to a render call is a String with no slashes, Rails assumes that the target template is in the current controller’s view directory. However, if the name contains one or more / characters, Rails assumes that the part up to the last slash is a directory name and the rest is the template name. The directory is assumed to be under app/views. This makes it easy to share partials and subtemplates across controllers.

The convention among Rails applications is to store these shared partials in a subdirectory of app/views called shared. Render shared partials using statements such as these:

 <%=​ render(​"shared/header"​, ​locals: ​{​title: ​@article.​title​}) ​%>
 <%=​ render(​partial: ​​"shared/post"​, ​object: ​@article) ​%>
 . . .

In this previous example, the @article object will be assigned to the local variable post within the template.

Partials with Layouts

Partials can be rendered with a layout, and you can apply a layout to a block within any template:

 <​%= render partial: "user", layout: "administrator" %>
 
 <%=​ render ​layout: ​​"administrator"​ ​do​ ​%>
  # ...
 <% end %>

Partial layouts are to be found directly in the app/views directory associated with the controller, along with the customary underbar prefix, such as app/views/users/_administrator.html.erb.

Partials and Controllers

It isn’t just view templates that use partials. Controllers also get in on the act. Partials give controllers the ability to generate fragments from a page using the same partial template as the view. This is particularly important when you are using Ajax support to update just part of a page from the controller—use partials, and you know your formatting for the table row or line item that you’re updating will be compatible with that used to generate its brethren initially.

Taken together, partials and layouts provide an effective way to make sure that the user interface portion of your application is maintainable. But being maintainable is only part of the story; doing so in a way that also performs well is also crucial.

What We Just Did

Views are the public face of Rails applications, and we’ve seen that Rails delivers extensive support for what you need to build robust and maintainable user and application programming interfaces.

We started with templates, of which Rails provides built-in support for three types: ERB, Builder, and SCSS. Templates make it easy for us to provide HTML, JSON, XML, CSS, and JavaScript responses to any request. We’ll discuss adding another option in Creating HTML Templates with Slim.

We dove into forms, which are the primary means by which users will interact with your application. Along the way, we covered uploading files.

We continued with helpers, which enable us to factor out complex application logic to allow our views to focus on presentation aspects. We explored a number of helpers that Rails provides, ranging from basic formatting to hypertext links, which are the final way in which users interact with HTML pages.

We completed our tour of Action View by covering two related ways of factoring out large chunks of content for reuse. We use layouts to factor out the outermost layers of a view and provide a common look and feel. We use partials to factor out common inner components, such as a single form or table.

That covers how a user with a browser will access our Rails application. Next up: covering how we define and maintain the schema of the database our application will use to store data.

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

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