Using sublayouts is necessary to have clean, concise markup. The way to do it is to figure out the most markup that is shared between views, and make it a sublayout.
Suppose for example you want to surround the pages served with a custom seasonal theming for user profiles.
Here is an example app/views folder structure:
layouts/
application.html.erb
users.html.erb
halloween.html.erb
users/
show.html.erb
index.html.erb
Before we nest layouts, its important to know how Rails chooses the layout. Rails will first look for the layout chosen in the controller. Remember the action name is available with action_name.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
layout 'erb_file_name_in_layouts_folder', only: [:show, :index]
# or use a helper method for more control
layout :choose_layout
...
private
def choose_layout
if ['show', 'index'].include?(action_name)
'special_layout'
elsif action_name == 'edit'
'weird_layout'
else
'normal_layout'
end
end
end
If the layout is not explicitly declared in the controller, then Rails will look for a layout in the app/views/layouts folder with the name of the controller. In this example, users.html.erb. Otherwise it will finally default to application.html.erb.
Another important thing to know is yielding by symbol. Suppose in application.html.erb you provide a place for scripts to be added to the head.
<!-- app/views/layouts/application.html.erb -->
...
<head>
<%= yield :head %>
</head>
...
And then in any view, content can be appended inside the head element. For example:
<!-- app/views/layouts/artists/show.html.erb -->
...
<% content_for :head do %>
<script type="text/javascript">
console.log("This is in users#show");
</script>
<% end %>
The trick for sublayouts, is that every time you would yield content, instead choose a unique name (the file name works well) and then check if there has been content passed for the given symbol.
For example:
<!-- app/views/layouts/application.html.erb -->
<!-- Where you would normally call yield -->
<%= content_for?(:application) ? yield(:application) : yield %>
What this does is check it content has been passed for :application. If there is content, yield that, otherwise yield the view being rendered. Now if you want to do a sublayout just provide content for :application.
<!-- app/views/layouts/halloween.html.erb -->
<% content_for :application do %>
<!-- Fancy images and stuff -->
<div id="content">
<%= content_for?(:halloween) ? yield(:halloween) : yield %>
</div>
<% end %>
<%= render template: 'layouts/application' %>
And specify the halloween layout in the controller. Finally, lets select the halloween layout if it is seasonally correct.
# app/helpers/application_helper.rb
module ApplicationHelper
def season_of(date)
case date
when Date.new(date.year, 10, 24)..Date.new(date.year, 10, 31)
:halloween
end
end
end
<!-- app/views/layouts/artists.html.erb -->
<% layout = season_of(Date.today) || :application %>
<% content_for layout do %>
<div>
<!-- content -->
<%= content_for?(:artists) ? yield(:artists) : yield %>
</div>
<% end %>
<%= render template: "layouts/#{ layout }" %>
And that's all. There's no limit to how many layouts you can nest, so keep those views clean.