Written by: steve ross on November 12th 2008

Many Rails developers will create a default layout in application.html.erb and use it as a consistent wrapper for all pages rendered on a site. This is one of the cool features of Rails - consistent rendering of the site's layout. But what if you want to change things for certain controllers or actions.

Layouts By Controller

This is the trivial case because all you need to do is:

    class AdminController < Application
      layout 'admin'
  
      def some_action
      end
    end

and every action within AdminController uses admin.html.erb instead of application.html.erb. But how about a controller that handles some public-facing and some administrative aspects?

Layouts With Exceptions

At first blush, this would also appear to be trivial. Just modify the code above as follows:

    class AdminController < Application
      layout 'admin', :only => [:some_action]
    
      def some_action
      end
    
      def some_public_facing_action
      end
    end

At first glance this is flawless, and of course you're testing the action you changed, so you got suckered in. One day, you go to /some_public_facing_action and WTF?? There is no layout at all.

It turns out that what the line

layout 'admin', :only => [:some_action]

really means is apply the 'admin' layout to some_action and don't apply any layout to anything else. So you think, well, How's about:

    class AdminController < Application
      layout 'admin', :only => [:some_action]
    
      def some_action
      end
    
      def some_public_facing_action
        render :layout => 'application'
      end
    end

Bzzzt! Wrong answer. Rails already believes the public facing action is exempt from layout.

Using More Than One Layout in A Controller

The best way I've found to do this is to write a helper that determines on an individual basis which methods require which layout. Here's how:

class AdminController < Application layout :smart_layout

  def some_action
  end

  def some_public_facing_action
    render :layout => 'application'
  end

  protected
  def smart_layout
    admin_actions =  [:some_action]
    admin_actions.include?(action_name) ? 'admin' : 'application'
  end
end

You may notice that I have only one action in my array, but more commonly, there will be multiple actions.

Further Reading

See Rails Guides for an in depth discussion of how layouts are inherited and controlled.