Nested layouts in Ruby on Rails
- Published August 12th, 2006 in Tips & Tricks, Ruby
Sometimes it's quite useful to have nested layouts. For instance, suppose you have a main layout that should be applied crosswide to your website. This way, what changes is only a tiny part usually defined as @content_for_layout.
Nonetheless, you may need to customize even further that content placer. Eventually with a new layout, but always inheriting the main one.
This feature is not native to Rails. Fortunately I came accross a suggestion on a wiki on how to do accomplish such. Since wikis are somewhat momentary, I hereby copy it to here with the due credits (Maxim Kulkin, inspired by ASP.NET 2.0 Master Pages implementation).
Start by creating a file like nested_layouts.rb and put anywhere you like (usually inside /lib or /conf). Put the following as the content:
For Rails < 1.2
-
module ActionView
-
module Helpers
-
module NestedLayoutsHelper
-
def inside_layout(layout, &block)
-
layout = layout.include?('/') ? layout : "layouts/#{layout}"
-
-
concat(@template.render_file(layout, true, '@content_for_layout' => capture(&block)), block.binding)
-
end
-
end
-
end
-
end
-
-
ActionView::Base.class_eval do
-
include ActionView::Helpers::NestedLayoutsHelper
-
end
For Rails > 1.2 (Hands down to Matthew Bass for the tip).
-
module ActionView
-
module Helpers
-
module NestedLayoutsHelper
-
def inside_layout(layout, &block)
-
layout = layout.include?('/') ? layout : "layouts/#{layout}"
-
-
@template.instance_variable_set("@content_for_layout", capture(&block))
-
concat(@template.render(:file => layout, :user_full_path => true), block.binding)
-
end
-
end
-
end
-
end
-
-
ActionView::Base.class_eval do
-
include ActionView::Helpers::NestedLayoutsHelper
-
end
Which does nothing until we actually call it. Put the following line inside environment.rb
-
require "#{RAILS_ROOT}/config/nested_layout.rb"
and then restart the webserver.
Now you're ready to make full usage of rails nested layouts.
In controller write
-
class FooController <ApplicationController
-
layout 'inner'
-
end
Then inside your ‘layouts/inner.rhtml’ do
-
<% inside_layout 'outer' do %>
-
Inner layout header
-
<%= @content_for_layout %>
-
Inner layout footer
-
<% end %>
The outer layout can also nest itself inside a higher level layout.




Hi,
This looks like a very good technique. I have tried to use it on my site but I get the following error:
ActionView::TemplateError (undefined method `inside_layout’ for #:0×3405380>) on line #1 of app/views/layouts/store.rhtml:
That’s weird because I required the file in /lib and the require doesn’t throw any errors, so the method must be there, right?
Thanks,
Sean
Okay, I discovered that this is required in the controller:
helper “lib/NestedLayouts”
Now I just need to find out how to access controller variables. e.g. in my controller I have
def index
@releases = Release.find :all
end
And I get the following error:
ActionView::TemplateError (You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each) on line #3 of app/views/store/index.rhtml:
1:
2: Recent Items
3:
Obviously the technique is no good if you can’t acess variable from the controller methods… oh well.
After reviewing the code more carefully I discovered that the problem was simply that I had omiited the last 3 lines that go in the /lib file. Sorry.
On another note, I discovered that the technique is not compatible with Cod Hale’s Content-only caching for Rails plugin: http://blog.codahale.com/2006/04/10/content-only-caching-for-rails/
It seems the plugin cache’s content_for_layout and then pipes that to the layout, but since the cached version has already had the inner layout added in by this technique, the inner layouts appear twice. I wonder if there’s any way around that?
Thanks,
Sean
Hi Sean, sorry for taking so much for answering. I’ve been off.
I’m deeply interested in the content caching you’ve linked to but I must confess I haven’t tried it yet with the inner layout technique. I’ll do it as soon as possible since I have one major update going on one of my websites.
Please let me know if you did any breakthrough on this issue. I’ll keep you updated through here and through your email.
Cheers,
Mário
Mário
Just to let you know, you say to call the file ‘nested_layouts.rb’, but the require specifies ‘nested_layout.rb’ (with only one ’s’).
Origially, I just wanted to say that I had the same trip-up as Jeremy.. but then I pressed submit without filling out my email and was taken to a very un-friendly validation error page with no links.
After clicking back.. my message was lost.
Please consider a more friendly approach to your comment form validation.
Have there been any updates to this bit of code? It seems that in Rails 1.2, whenever you return to a page that has been rendered before with this code, the content shows up as blank and you have to restart the web server in order for it to work again. Anyone else experiencing this?
Rick,
I’m experiencing the very same you described. I haven’t yet figured a solution and in the meanwhile I’m reverting to Rails 1.1.6.
Rick and mlopes, I’m experiencing the same problem as well on Rails 1.2.2. Haven’t found a workaround yet. Has anyone else managed to fix this problem?
I managed to solve this problem by using #set_instance_var to set @content_for_layout in the template instead of passing it in as a local. Something like this:
@template.instance_variable_set(”@content_for_layout”, content_for_layout)
concat(@template.render(:file => layout, :user_full_path => true), block.binding)