Rails

How to Clean Your Nested Layouts in 5 Minutes


As a front-end developer who have worked on many projects, I've seen a lot of content duplication in views.

Let's see an example:

For Spree applications, if we want to edit our Spree base layout, we should use Spree Deface to manipulate its contents since this is the recommended way. Another way is to copy, paste and update the layout content to make those little modification such adding some wrappers or titles. The real problem when using the second approach comes when you use those modified layouts as based for new section, then you realize you need another different layout with another small modification: you have to copy, paste and edit in order to generate another layout.

Ok, but, how can I clean my layouts then?

Well, at least you could use Rails Nested Layouts which is a "solution" that basically tells you to:

  • Replace all your layouts yield to use the yield :content_name form.
  • Refactor your nested layouts to put the boilerplate inside content_for :content_name blocks.
  • You'll need to have conditionals to switch between yield and content_for to ensure it will work on nested and non nested contexts.
  • There can be only one yield for the entire stack of nested layouts.

Depending on your application, these changes could be easily done or not. In my experience… well, I've seen things … but, in the search for improvements, I found an easier way to implement nested layouts.

Creating a self-documented helper

As you will see, the helper will be in charge of rendering the current content in the given "parent layout", giving us the advantage of:

  • Using yield as desired (no restriction to one yield in the stack)
  • No conditional content_for/yield (and no refactoring old code, Spree friendly)
  • No need of content_for block for main content.

helpers/layouts_helper.rb

module LayoutsHelper
  def parent_layout(layout)
    @view_flow.set(:layout, output_buffer)
    output = render(file: "#{layout}")
    self.output_buffer = ActionView::OutputBuffer.new(output)
  end
end

All you would need to do is to call it at the bottom of the nested layout and pass the name of the parent layout.

views/layouts/contact_us.html.erb

<h1 class='contact-us-header'>
  Contact Us
</h1>

<section class='sections-box'>
  <header class='sections-header'>
    <%= yield(:contact_us_title) %>
  </header>

  <div class='sections-content'>
    <%= yield %>
  </div>
</section>

<%- parent_layout('spree/layouts/spree_application') %>

Our current content would be something like:

spree/contact_us/cancelations/show.html.erb

<% content_for :contact_us_title do %>
  <h2>Change Order Request</h2>
<% end %>

<p>
  Due to our fast shipping, we can't cancel or change an order once it is placed.
  Please see the <%= link_to 'Returns & Refunds', '/help#returns' %> policy.
  <br/>
  We apologize for the inconvenience.
  <br/>
  <b>Thank You</b>
</p>

Is there any issue here?

Overall, compatibility. It is possible that ActionView::OutputBuffer is changed or removed from future Rails versions and this will no longer works, but we won't cross the bridge before we come to it.

Conclusion

As you see, it's easy to clean our layouts: we just need to make use of some of the basics and, of course, I've used this solution for Spree but it could be used on "normal" Rails applications too.

Questions, comments and suggestions are always welcome:

twitter: mumoc | github: mumoc | skype: mumo.carlos

Best Practices
De Código, Café y Cervezas 07 – ¿Somos profesionales?
eCommerce
Get Rid of Your Spree Preferences Headaches Once and for All
Community
De Código, Café y Cervezas 06 – ActiveModel::Serializer