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 theyield :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
andcontent_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 oneyield
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