'Nested Layouts in Rails

I'm having trouble finding a way to do the following:

Let's say in my application.html.erb I have the following

<div id="one" >
  <%= yield %>
</div>

Then I want to have another layout file asdf.html.erb

<div id="two">
  <%= yield %>
</div>

I want the final output to be

<div id="one">
  <div id="two">
     <%= yield %>
  </div>
</div>

Is it possible? Thanks.



Solution 1:[1]

By default, application.html.erb is your layout. You can render a default sub-layout by calling it as a partial from your application layout:

# app/views/layouts/application.html.erb
<div id="one" >
    <%= render "layouts/asdf" %>
</div>

# app/views/layouts/_asdf.html.erb
<div id="two">
    <%= yield %>
</div>

This will output the following:

<div id="one>
   <div id="two">
      <%= yield %>
   </div>
</div>

Alternatively, if you're looking to conditionally render layouts on a controller-by-controller basis, you should consider using nested layouts. From the documentation:

On pages generated by NewsController, you want to hide the top menu and add a right menu:

# app/views/layouts/news.html.erb
<% content_for :stylesheets do %>
  #top_menu {display: none}
  #right_menu {float: right; background-color: yellow; color: black}
<% end %>
<% content_for :content do %>
  <div id="right_menu">Right menu items here</div>
  <%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
<%= render template: "layouts/application" %>

The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.

Solution 2:[2]

The cleanest solution I found by far came from this repo : https://github.com/rwz/nestive

I did not want the whole gem. If you're like me, here's how I achieved what I wanted:

# application_helper.rb

  # From https://github.com/rwz/nestive/blob/master/lib/nestive/layout_helper.rb
  def extends(layout, &block)
    # Make sure it's a string
    layout = layout.to_s

    # If there's no directory component, presume a plain layout name
    layout = "layouts/#{layout}" unless layout.include?('/')

    # Capture the content to be placed inside the extended layout
    @view_flow.get(:layout).replace capture(&block)

    render file: layout
  end

Then you keep /layouts/application.html.erb unchanged!

And you can create other layouts. In my case /layouts/public.html.erb and /layouts/devise.html.erb:

# public.html.erb
<%= extends :application do %>
  <%= render 'partials/navbar' %>
  <div class="container margin-top">
    <%= yield %>
  </div>
<% end %>

# devise.html.erb
<%= extends :public do %>
  <div class="col-sm-6 col-sm-offset-3">
    <%= yield %>
  </div>
<% end %>

Works like a charm! I am still smiling I finally found a clean solution.

Solution 3:[3]

Rename asdf.html.erb to _asdf.html.erb and rewrite application.html.erb to this:

<div id="one">
  <%= render 'asdf' %>
</div>

More about partials here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials

Solution 4:[4]

You can also do the following to conditionally render a sub-layout:

# app/views/layouts/application.html.erb
<%= controller.controller_name.include?("foo") ? render("layouts/foo") : yield %>

# app/views/layouts/_foo.html.erb
<div class="bar">
    <%= yield %>
</div>

For scenarios with only a single sub-layout, I find this preferable to the nested layout approach outlined in the Rails guide because execution doesn't have to go from the sub-layout, to the main layout, and then back to the sub-layout. Instead, it flows more naturally, starting at the main layout, proceeding to the sub-layout, and then ending at the view.

Solution 5:[5]

If you're looking for a clean solution which doesn't couple application.html.erb to its inheriting elements, the gem nestive (as pointed out in the other an used to do that but it did not appear to work with Rails 5. But this is another way to do it: https://mattbrictson.com/easier-nested-layouts-in-rails

# 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

Then asdf.html.erb becomes

<div id="two">
    <%= yield %>
</div>
<% parent_layout 'application' %>

Be warned that this relies on Rails internals and it might stop working in a future version. This is not very likely to happen soon, as it has been working for 3 years at least (based on the date of the linked blog post).

Solution 6:[6]

I have used this approach for years:

# app/helpers/layout_helper.rb
module LayoutHelper
  def inside_layout(layout = 'application', &block)
    render inline: capture(&block), layout: "layouts/#{layout}"
  end
end

Then your asdf layout would be:

# app/views/layouts/asdf.html.erb
<%= inside_layout do %>
  <div id="two">
    <%= yield %>
  </div>
<% end %>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Augustin Riedinger
Solution 3 Kaleidoscope
Solution 4 Oleg Bulkin
Solution 5 Velizar Hristov
Solution 6 Andy Stewart