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
yieldto use theyield :content_nameform. - Refactor your nested layouts to put the boilerplate inside
content_for :content_nameblocks. - You'll need to have conditionals to switch between
yieldandcontent_forto ensure it will work on nested and non nested contexts. - There can be only one
yieldfor 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
yieldas desired (no restriction to oneyieldin the stack) - No conditional
content_for/yield(and no refactoring old code, Spree friendly) - No need of
content_forblock 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