Module: AbstractController::Layouts

Extended by:
ActiveSupport::Concern
Includes:
Rendering
Defined in:
lib/abstract_controller/layouts.rb

Overview

Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in repeated setups. The inclusion pattern has pages that look like this:

<%= render "shared/header" %>
Hello World
<%= render "shared/footer" %>

This approach is a decent way of keeping common structures isolated from the changing content, but it’s verbose and if you ever want to change the structure of these two includes, you’ll have to change all the templates.

With layouts, you can flip it around and have the common structure know where to insert changing content. This means that the header and footer are only mentioned in one place, like this:

// The header part of this layout
<%= yield %>
// The footer part of this layout

And then you have content pages that look like this:

hello world

At rendering time, the content page is computed and then inserted in the layout, like this:

// The header part of this layout
hello world
// The footer part of this layout

Accessing shared variables

Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with references that won’t materialize before rendering time:

<h1><%= @page_title %></h1>
<%= yield %>

…and content pages that fulfill these references at rendering time:

<% @page_title = "Welcome" %>
Off-world colonies offers you a chance to start a new life

The result after rendering is:

<h1>Welcome</h1>
Off-world colonies offers you a chance to start a new life

Layout assignment

You can either specify a layout declaratively (using the #layout class method) or give it the same name as your controller, and place it in app/views/layouts. If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.

For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, that template will be used for all actions in PostsController and controllers inheriting from PostsController.

If you use a module, for instance Weblog::PostsController, you will need a template named app/views/layouts/weblog/posts.html.erb.

Since all your controllers inherit from ApplicationController, they will use app/views/layouts/application.html.erb if no other layout is specified or provided.

Inheritance Examples

class BankController < ActionController::Base
  layout "bank_standard"

class InformationController < BankController

class TellerController < BankController
  # teller.html.erb exists

class TillController < TellerController

class VaultController < BankController
  layout :access_level_layout

class EmployeeController < BankController
  layout nil

The InformationController uses “bank_standard” inherited from the BankController, the VaultController overwrites and picks the layout dynamically, and the EmployeeController doesn’t want to use a layout at all.

The TellerController uses teller.html.erb, and TillController inherits that layout and uses it as well.

Types of layouts

Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can be done either by specifying a method reference as a symbol or using an inline method (as a proc).

The method reference is the preferred approach to variable layouts and is used like this:

class WeblogController < ActionController::Base
  layout :writers_and_readers

  def index
    # fetching posts
  end

  private
    def writers_and_readers
      logged_in? ? "writer_layout" : "reader_layout"
    end

Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing is logged in or not.

If you want to use an inline method, such as a proc, do something like this:

class WeblogController < ActionController::Base
  layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
end

Of course, the most common way of specifying a layout is still just as a plain template name:

class WeblogController < ActionController::Base
  layout "weblog_standard"
end

If no directory is specified for the template name, the template will by default be looked for in app/views/layouts/. Otherwise, it will be looked up relative to the template root.

Conditional layouts

If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The :only and :except options can be passed to the layout call. For example:

class WeblogController < ActionController::Base
  layout "weblog_standard", :except => :rss

  # ...

end

This will assign “weblog_standard” as the WeblogController’s layout except for the rss action, which will not wrap a layout around the rendered view.

Both the :only and :except condition can accept an arbitrary number of method references, so #:except => [ :rss, :text_only ] is valid, as is :except => :rss.

Using a different layout in the action render call

If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. Sometimes you’ll have exceptions where one action wants to use a different layout than the rest of the controller. You can do this by passing a :layout option to the render call. For example:

class WeblogController < ActionController::Base
  layout "weblog_standard"

  def help
    render :action => "help", :layout => "help"
  end
end

This will render the help action with the “help” layout instead of the controller-wide “weblog_standard” layout.

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Attributes included from Rendering

#view_context_class

Instance Method Summary collapse

Methods included from Rendering

#_prefix, #_render_template, #process, #render, #render_to_body, #render_to_string, #view_context

Methods included from ViewPaths

#append_view_path, #details_for_lookup, #lookup_context, #prepend_view_path

Instance Attribute Details

#action_has_layout=(value) ⇒ Object (writeonly)

Sets the attribute action_has_layout

Parameters:

  • value

    the value to set the attribute action_has_layout to.



295
296
297
# File 'lib/abstract_controller/layouts.rb', line 295

def action_has_layout=(value)
  @action_has_layout = value
end

Instance Method Details

#_normalize_options(options) ⇒ Object



285
286
287
288
289
290
291
292
293
# File 'lib/abstract_controller/layouts.rb', line 285

def _normalize_options(options)
  super

  if _include_layout?(options)
    layout = options.key?(:layout) ? options.delete(:layout) : :default
    value = _layout_for_option(layout)
    options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value
  end
end

#action_has_layout?Boolean

Returns:

  • (Boolean)


302
303
304
# File 'lib/abstract_controller/layouts.rb', line 302

def action_has_layout?
  @action_has_layout
end

#initializeObject



297
298
299
300
# File 'lib/abstract_controller/layouts.rb', line 297

def initialize(*)
  @action_has_layout = true
  super
end