Module: ActionView::Layouts
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
::ActiveSupport::Concern
|
|
Instance Chain:
|
|
Defined in: | actionview/lib/action_view/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 "application/header" %>
Hello World
<%= render "application/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 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
# bank.html.erb exists
class ExchangeController < BankController
# exchange.html.erb exists
class CurrencyController < BankController
class InformationController < BankController
layout "information"
class TellerController < InformationController
# teller.html.erb exists
class EmployeeController < InformationController
# employee.html.erb exists
layout nil
class VaultController < BankController
layout :access_level_layout
class TillController < BankController
layout false
In these examples, we have three implicit lookup scenarios:
-
The
BankController
uses the “bank” layout. -
The
ExchangeController
uses the “exchange” layout. -
The
CurrencyController
inherits the layout from BankController.
However, when a layout is explicitly set, the explicitly set layout wins:
-
The
InformationController
uses the “information” layout, explicitly set. -
The
TellerController
also uses the “information” layout, because the parent explicitly set it. -
The
EmployeeController
uses the “employee” layout, because it set the layout tonil
, resetting the parent configuration. -
The
VaultController
chooses a layout dynamically by calling theaccess_level_layout
method. -
The
TillController
does not use a layout at all.
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
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
If an argument isn’t given to the proc, it’s evaluated in the context of the current controller anyway.
class WeblogController < ActionController::Base
layout proc { 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
The template will be looked always in app/views/layouts/
folder. But you can point layouts
folder direct also. layout "layouts/demo"
is the same as layout "demo"
.
Setting the layout to nil
forces it to be looked up in the filesystem and falls back to the parent behavior if none exists. Setting it to nil
is useful to re-enable template lookup overriding a previous configuration set in the parent:
class ApplicationController < ActionController::Base
layout "application"
end
class PostsController < ApplicationController
# Will use "application" layout
end
class CommentsController < ApplicationController
# Will search for "comments" layout and fall back to "application" layout
layout nil
end
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 for all actions except for the rss
action, which will be rendered directly, without wrapping 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 override the controller-wide “weblog_standard” layout, and will render the help action with the “help” layout instead.
Class Method Summary
::ActiveSupport::Concern
- Extended
class_methods | Define class methods from given block. |
included | Evaluate given block in context of base class, so that you can write class macros here. |
prepended | Evaluate given block in context of base class, so that you can write class macros here. |
append_features, prepend_features |
Instance Attribute Summary
- #action_has_layout=(value) rw
-
#action_has_layout? ⇒ Boolean
rw
Controls whether an action should be rendered using a layout.
- #_conditional_layout? ⇒ Boolean readonly private
Rendering
- Included
ViewPaths
- Included
Instance Method Summary
-
#_default_layout(lookup_context, formats, require_layout = false)
private
Returns the default layout for this controller.
- #_include_layout?(options) ⇒ Boolean private
-
#_layout
private
This will be overwritten by _write_layout_method.
-
#_layout_for_option(name)
private
Determine the layout for a given name, taking into account the name type.
- #_normalize_layout(value) private
- #_process_render_template_options(options) Internal use only
- #initialize Internal use only
Rendering
- Included
#initialize, #render_to_body, | |
#view_context | An instance of a view class. |
#view_context_class, | |
#_normalize_args | Normalize args by converting render “foo” to render action: “foo” and render “foo/bar” to render template: “foo/bar”. |
#_process_format | Assign the rendered format to look up context. |
#_process_render_template_options | Normalize options. |
#_render_template | Find and render a template based on the options given. |
#process | Override process to set up |
#view_renderer | Returns an object that is able to render templates. |
ViewPaths
- Included
#any_templates?, | |
#append_view_path | Append a path to the list of view paths for the current |
#details_for_lookup, | |
#lookup_context |
|
#prepend_view_path | Prepend a path to the list of view paths for the current |
#template_exists?, | |
#_prefixes | The prefixes used in render “foo” shortcuts. |
DSL Calls
included
[ GitHub ]210 211 212 213 214 215
# File 'actionview/lib/action_view/layouts.rb', line 210
included do class_attribute :_layout, instance_accessor: false class_attribute :_layout_conditions, instance_accessor: false, instance_reader: true, default: {} _write_layout_method end
Instance Attribute Details
#_conditional_layout? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'actionview/lib/action_view/layouts.rb', line 377
def _conditional_layout? true end
#action_has_layout=(value) (rw)
[ GitHub ]# File 'actionview/lib/action_view/layouts.rb', line 359
attr_internal_writer :action_has_layout
#action_has_layout? ⇒ Boolean
(rw)
Controls whether an action should be rendered using a layout. If you want to disable any layout
settings for the current action so that it is rendered without a layout then either override this method in your controller to return false for that action or set the action_has_layout
attribute to false before rendering.
# File 'actionview/lib/action_view/layouts.rb', line 372
def action_has_layout? @_action_has_layout end
Instance Method Details
#_default_layout(lookup_context, formats, require_layout = false) (private)
Returns the default layout for this controller. Optionally raises an exception if the layout could not be found.
Parameters
-
formats
- The formats accepted to this layout -
require_layout
- If set totrue
and layout is not found, anArgumentError
exception is raised (defaults tofalse
)
Returns
-
template
- The template object for the default layout (ornil
)
# File 'actionview/lib/action_view/layouts.rb', line 415
def _default_layout(lookup_context, formats, require_layout = false) begin value = _layout(lookup_context, formats) if action_has_layout? rescue NameError => e raise e, "Could not render layout: #{e.}" end if require_layout && action_has_layout? && !value raise ArgumentError, "There was no default layout for #{self.class} in #{view_paths.inspect}" end _normalize_layout(value) end
#_include_layout?(options) ⇒ Boolean
(private)
# File 'actionview/lib/action_view/layouts.rb', line 430
def _include_layout?( ) ! .keys.intersect?([:body, :plain, :html, :inline, :partial]) || .key?(:layout) end
#_layout (private)
This will be overwritten by _write_layout_method
# File 'actionview/lib/action_view/layouts.rb', line 382
def _layout(*); end
#_layout_for_option(name) (private)
Determine the layout for a given name, taking into account the name type.
Parameters
-
name
- The name of the template
# File 'actionview/lib/action_view/layouts.rb', line 388
def _layout_for_option(name) case name when String then _normalize_layout(name) when Proc then name when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) } when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) } when false, nil then nil else raise ArgumentError, "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" end end
#_normalize_layout(value) (private)
[ GitHub ]# File 'actionview/lib/action_view/layouts.rb', line 401
def _normalize_layout(value) value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value end
#_process_render_template_options(options)
# File 'actionview/lib/action_view/layouts.rb', line 350
def ( ) # :nodoc: super if _include_layout?( ) layout = .delete(:layout) { :default } [:layout] = _layout_for_option(layout) end end
#initialize
# File 'actionview/lib/action_view/layouts.rb', line 361
def initialize(*) # :nodoc: @_action_has_layout = true super end