55 Minutes

Welcome to the 55 Minutes blog.
55 Minutes is a web development consultancy in San Francisco building apps for design-led businesses and startups. On this blog we discuss the technology tools of our trade: Ruby on Rails, Django, Sass, OS X, and more.

Easier Nested Layouts in Rails 3/4

Nested layouts in Rails are a bit of a mess. Here is a small hack that provides a simpler idiom for writing and declaring nested layouts. The code works with Rails 3.2, Rails 4.0, and Rails 4.1. Jump straight to the snippet.

A layouts recap

Any non-trivial web application will have multiple HTML layouts. For example, a login page may use a simple layout with no header and footer, whereas authenticated pages will have navigation bars and breadcrumbs. Public pages like documentation and privacy policy may use yet a different layout.

Rails offers a basic solution for this in the form of layout templates. Create a layout file with the boilerplate you need, then yield to the actual dynamic content that is being rendered:

<html>
  <head>
    ...
  </head>
  <body>
    <%= render("shared/header") %>
    <%= render("shared/alerts") %>

    <div class="container">
      <%= yield %>
    </div>

    <%= render("shared/footer") %>
  </body>
</html>

You can have as many layouts as you’d like, and Rails makes use of convention over configuration to automatically pair up layouts with corresponding controllers of the same name.

Nested layouts, the (awkward) Rails way

So far so good. But what if you want to reuse one layout within another? Here’s our common practice:

Some frameworks call this template inheritance. In our example, we might say that the application layout inherits from or extends the base layout. In Rails, this is known as nested layouts, and it is a bit awkward to use. The standard Rails practice for nested layouts is complicated and involves these considerations:

The official Rails guide provides an explanation with some examples.

A nicer, helper-based approach

If you do some searching (as you may have done to find this article!) you’ll find alternatives to nested layouts ranging from simple hacks all the way up to full blown gems that introduce their own layout DSL.

Our approach is to use a parent_layout helper, which is one of the simpler solutions.1 Instead of juggling content_for names and blocks, we can use a helper method to make Rails aware of the nesting structure. Here’s how this solution stacks up:

# Place this in app/helpers/layouts_helper.rb
module LayoutsHelper
  def parent_layout(layout)
    @view_flow.set(:layout, output_buffer)
    output = render(:file => "layouts/#{layout}")
    self.output_buffer = ActionView::OutputBuffer.new(output)
  end
end

An example

Here’s how to use parent_layout to implement the “base” and “application” layouts from our earlier example. This code is taken from our rails-starter project, which is our starting point for new Rails apps.

This is our base layout, which handles the general boilerplate. Note the yield statement which is where views or nested layouts will be rendered:

<!DOCTYPE html>
<html>
  <head>
    <%= stylesheet_link_tag("application", "data-turbolinks-track" => true) %>
    <%= javascript_include_tag("application", "data-turbolinks-track" => true) %>

    <%= yield(:head) %>

    <meta charset="utf8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <%= csrf_meta_tags %>

    <title>
      <%= yield(:title) + " – " if content_for?(:title) %>
      RailsStarter
    </title>
  </head>
  <body>

    <%= yield %>

  </body>
</html>
base.html.erb – View Gist

Our application layout extends the base layout by use of the parent_layout helper. Notice that otherwise it uses the standard yield syntax; no content_for hacks needed:

<%= render("shared/navbar") %>

<div class="container">

  <%= render("shared/alerts") %>
  <%= render("shared/page_header") %>

  <%= yield %>

  <%= render("shared/footer") %>

</div>

<% parent_layout "base" %>
application.html.erb – View Gist

That’s it! Just make sure you’ve installed the helper as explained above.

Finally, a word of caution…

This solution has been tested with Rails 3.2.17, 4.0.3, and 4.1.0.rc1. However, keep in mind that since this helper relies on private Rails internals, there is a likelihood that it will break in a future Rails version.

Notice any bugs or want to suggest an improvement? Let me know in the comments!

Notes

  1. It’s hard to attribute the parent_layout solution to a single developer, as the code seems to be constantly evolving as it bounces around various blogs and Stack Overflow answers. I originally found a version of it on the MyRailsWay blog, and have since enhanced it to work with Rails 4.

comments powered by Disqus