aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib/action_view')
-rw-r--r--actionview/lib/action_view/base.rb4
-rw-r--r--actionview/lib/action_view/digestor.rb45
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb39
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb4
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb21
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb7
-rw-r--r--actionview/lib/action_view/layouts.rb424
-rw-r--r--actionview/lib/action_view/log_subscriber.rb2
-rw-r--r--actionview/lib/action_view/railtie.rb13
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb141
-rw-r--r--actionview/lib/action_view/view_paths.rb96
18 files changed, 773 insertions, 47 deletions
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 08253de3f4..caade8f43b 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -2,6 +2,10 @@ require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
+require 'action_view/helpers'
+require 'action_view/context'
+require 'action_view/template'
+require 'action_view/lookup_context'
module ActionView #:nodoc:
# = Action View Base
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index a674e4d7ea..af158a630b 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,20 +1,47 @@
require 'thread_safe'
require 'action_view/dependency_tracker'
+require 'monitor'
module ActionView
class Digestor
cattr_reader(:cache)
- @@cache = ThreadSafe::Cache.new
-
- def self.digest(name, format, finder, options = {})
- cache_key = ([name, format] + Array.wrap(options[:dependencies])).join('.')
- @@cache.fetch(cache_key) do
- @@cache[cache_key] ||= nil if options[:partial] # Prevent re-entry
+ @@cache = ThreadSafe::Cache.new
+ @@digest_monitor = Monitor.new
+
+ class << self
+ def digest(name, format, finder, options = {})
+ details_key = finder.details_key.hash
+ dependencies = Array.wrap(options[:dependencies])
+ cache_key = ([name, details_key, format] + dependencies).join('.')
+
+ # this is a correctly done double-checked locking idiom
+ # (ThreadSafe::Cache's lookups have volatile semantics)
+ @@cache[cache_key] || @@digest_monitor.synchronize do
+ @@cache.fetch(cache_key) do # re-check under lock
+ compute_and_store_digest(cache_key, name, format, finder, options)
+ end
+ end
+ end
- klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor
- digest = klass.new(name, format, finder, options).digest
+ private
+ def compute_and_store_digest(cache_key, name, format, finder, options) # called under @@digest_monitor lock
+ klass = if options[:partial] || name.include?("/_")
+ # Prevent re-entry or else recursive templates will blow the stack.
+ # There is no need to worry about other threads seeing the +false+ value,
+ # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
+ pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
+ PartialDigestor
+ else
+ Digestor
+ end
- @@cache[cache_key] = digest # Store the value
+ # Store the actual digest if config.cache_template_loading is true
+ klass.new(name, format, finder, options).digest.tap do |digest|
+ @@cache[cache_key] = digest if ActionView::Resolver.caching?
+ end
+ rescue Exception
+ @@cache.delete_pair(cache_key, false) if pre_stored # something went wrong, make sure not to corrupt the @@cache
+ raise
end
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 2b3a3c6a29..a13d0021ea 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -26,7 +26,8 @@ module ActionView
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
# root. Relative paths are idiomatic, use absolute paths only when needed.
#
- # When passing paths, the ".js" extension is optional.
+ # When passing paths, the ".js" extension is optional. If you do not want ".js"
+ # appended to the path <tt>extname: false</tt> can be set on the options.
#
# You can modify the HTML attributes of the script tag by passing a hash as the
# last argument.
@@ -37,6 +38,9 @@ module ActionView
# javascript_include_tag "xmlhr"
# # => <script src="/assets/xmlhr.js?1284139606"></script>
#
+ # javascript_include_tag "template.jst", extname: false
+ # # => <script src="/assets/template.jst?1284139606"></script>
+ #
# javascript_include_tag "xmlhr.js"
# # => <script src="/assets/xmlhr.js?1284139606"></script>
#
@@ -51,8 +55,7 @@ module ActionView
# # => <script src="http://www.example.com/xmlhr.js"></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!('protocol').symbolize_keys
-
+ path_options = options.extract!('protocol', 'extname').symbolize_keys
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 0b957adb91..c830ab23e3 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -143,9 +143,9 @@ module ActionView
"#{source}#{tail}"
end
- alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
- # Computes the full URL to a asset in the public directory. This
+ # Computes the full URL to an asset in the public directory. This
# will use +asset_path+ internally, so most of their behaviors
# will be the same.
def asset_url(source, options = {})
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 2a38e5c446..b3af1d4da4 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -4,7 +4,7 @@ module ActionView
module CacheHelper
# This helper exposes a method for caching fragments of a view
# rather than an entire action or page. This technique is useful
- # caching pieces like menus, lists of newstopics, static HTML
+ # caching pieces like menus, lists of new topics, static HTML
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 8a4830d887..ead7871fc5 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -442,10 +442,11 @@ module ActionView
object = convert_to_model(object)
as = options[:as]
+ namespace = options[:namespace]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
class: as ? "#{action}_#{as}" : dom_class(object, action),
- id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
+ id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
method: method
)
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 4e9ef94ff3..fcd151ac32 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -97,14 +97,17 @@ module ActionView
# Create a select tag and a series of contained option tags for the provided object and method.
# The option currently held by the object will be selected, provided that the object is available.
#
- # There are two possible formats for the choices parameter, corresponding to other helpers' output:
- # * A flat collection: see options_for_select
- # * A nested collection: see grouped_options_for_select
+ # There are two possible formats for the +choices+ parameter, corresponding to other helpers' output:
+ #
+ # * A flat collection (see +options_for_select+).
+ #
+ # * A nested collection (see +grouped_options_for_select+).
+ #
+ # For example:
#
- # Example with @post.person_id => 1:
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
#
- # could become:
+ # would become:
#
# <select name="post[person_id]">
# <option value=""></option>
@@ -113,6 +116,8 @@ module ActionView
# <option value="3">Tobias</option>
# </select>
#
+ # assuming the associated person has ID 1.
+ #
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
# to the database. Instead, a second model object is created when the create request is received.
@@ -346,8 +351,8 @@ module ActionView
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
- html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
+ html_attributes[:selected] = option_value_selected?(value, selected)
+ html_attributes[:disabled] = disabled && option_value_selected?(value, disabled)
html_attributes[:value] = value
content_tag_string(:option, text, html_attributes)
@@ -384,8 +389,8 @@ module ActionView
end
selected, disabled = extract_selected_and_disabled(selected)
select_deselect = {
- :selected => extract_values_from_collection(collection, value_method, selected),
- :disabled => extract_values_from_collection(collection, value_method, disabled)
+ selected: extract_values_from_collection(collection, value_method, selected),
+ disabled: extract_values_from_collection(collection, value_method, disabled)
}
options_for_select(options, select_deselect)
@@ -444,7 +449,7 @@ module ActionView
option_tags = options_from_collection_for_select(
group.send(group_method), option_key_method, option_value_method, selected_key)
- content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
+ content_tag(:optgroup, option_tags, label: group.send(group_label_method))
end.join.html_safe
end
@@ -516,16 +521,20 @@ module ActionView
body = "".html_safe
if prompt
- body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
+ body.safe_concat content_tag(:option, prompt_text(prompt), value: "")
end
grouped_options.each do |container|
+ html_attributes = option_html_attributes(container)
+
if divider
label = divider
else
label, container = container
end
- body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
+
+ html_attributes = { label: label }.merge!(html_attributes)
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes)
end
body
@@ -561,7 +570,7 @@ module ActionView
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
- zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
+ zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true)
zone_options.safe_concat "\n"
zones = zones - priority_zones
@@ -744,7 +753,7 @@ module ActionView
end
def prompt_text(prompt)
- prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select')
end
end
@@ -752,7 +761,7 @@ module ActionView
# Wraps ActionView::Helpers::FormOptionsHelper#select for form builders:
#
# <%= form_for @post do |f| %>
- # <%= f.select :person_id, Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) %>
+ # <%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %>
# <%= f.submit %>
# <% end %>
#
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
index f767957fa9..77c3e6d394 100644
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/record_tag_helper.rb
@@ -1,3 +1,5 @@
+require 'action_view/record_identifier'
+
module ActionView
# = Action View Record Tag Helpers
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index c81156c0c8..9ee83ee7c2 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -10,7 +10,7 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
- content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
+ content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index baa5ff768e..e910879ebf 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -5,8 +5,8 @@ module ActionView
def render
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
- options["type"] ||= field_type
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
+ options["type"] ||= field_type
+ options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
options["value"] &&= ERB::Util.html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 147f9fd8ed..c23d605c5f 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -150,17 +150,19 @@ module ActionView
def excerpt(text, phrase, options = {})
return unless text && phrase
- separator = options.fetch(:separator, "")
+ separator = options[:separator] || ''
phrase = Regexp.escape(phrase)
regex = /#{phrase}/i
return unless matches = text.match(regex)
phrase = matches[0]
- text.split(separator).each do |value|
- if value.match(regex)
- regex = phrase = value
- break
+ unless separator.empty?
+ text.split(separator).each do |value|
+ if value.match(regex)
+ regex = phrase = value
+ break
+ end
end
end
@@ -169,7 +171,8 @@ module ActionView
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
- prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
+ affix = [first_part, separator, phrase, separator, second_part].join.strip
+ [prefix, affix, postfix].join
end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
@@ -215,7 +218,7 @@ module ActionView
def word_wrap(text, options = {})
line_width = options.fetch(:line_width, 80)
- text.split("\n").collect do |line|
+ text.split("\n").collect! do |line|
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -254,7 +257,7 @@ module ActionView
# # => "<p>Unblinkable.</p>"
#
# simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
- # # => "<p><blink>Blinkable!</span> It's true.</p>"
+ # # => "<p><blink>Blinkable!</blink> It's true.</p>"
def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
@@ -264,7 +267,7 @@ module ActionView
if paragraphs.empty?
content_tag(wrapper_tag, nil, html_options)
else
- paragraphs.map { |paragraph|
+ paragraphs.map! { |paragraph|
content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
}.join("\n\n").html_safe
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index daa9a393b3..1920a94567 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -16,7 +16,7 @@ module ActionView
# provided here will only work in the context of a request
# (link_to_unless_current, for instance), which must be provided
# as a method called #request on the context.
-
+ BUTTON_TAG_METHOD_VERBS = %w{patch put delete}
extend ActiveSupport::Concern
include TagHelper
@@ -289,7 +289,7 @@ module ActionView
remote = html_options.delete('remote')
method = html_options.delete('method').to_s
- method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
@@ -528,12 +528,13 @@ module ActionView
return false unless request.get? || request.head?
- url_string = url_for(options)
+ url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
request_uri = url_string.index("?") ? request.fullpath : request.path
+ request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
if url_string =~ /^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
new file mode 100644
index 0000000000..d8de1d95df
--- /dev/null
+++ b/actionview/lib/action_view/layouts.rb
@@ -0,0 +1,424 @@
+require "action_view/rendering"
+require "active_support/core_ext/module/remove_method"
+
+module ActionView
+ # 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 <tt>app/views/layouts</tt>.
+ # 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 <tt>app/views/layouts/posts.html.erb</tt>,
+ # 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
+ # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
+ #
+ # Since all your controllers inherit from ApplicationController, they will use
+ # <tt>app/views/layouts/application.html.erb</tt> 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 to nil, resetting the parent configuration.
+ # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> 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 <tt>app/views/layouts/</tt> folder. But you can point
+ # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
+ #
+ # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks 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 fallback "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
+ # <tt>:only</tt> and <tt>:except</tt> 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 <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>.
+ #
+ # == 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 <tt>:layout</tt> option to the <tt>render</tt> 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.
+ module Layouts
+ extend ActiveSupport::Concern
+
+ include ActionView::Rendering
+
+ included do
+ class_attribute :_layout, :_layout_conditions, :instance_accessor => false
+ self._layout = nil
+ self._layout_conditions = {}
+ _write_layout_method
+ end
+
+ delegate :_layout_conditions, to: :class
+
+ module ClassMethods
+ def inherited(klass) # :nodoc:
+ super
+ klass._write_layout_method
+ end
+
+ # This module is mixed in if layout conditions are provided. This means
+ # that if no layout conditions are used, this method is not used
+ module LayoutConditions # :nodoc:
+ private
+
+ # Determines whether the current action has a layout definition by
+ # checking the action name against the :only and :except conditions
+ # set by the <tt>layout</tt> method.
+ #
+ # ==== Returns
+ # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
+ def _conditional_layout?
+ return unless super
+
+ conditions = _layout_conditions
+
+ if only = conditions[:only]
+ only.include?(action_name)
+ elsif except = conditions[:except]
+ !except.include?(action_name)
+ else
+ true
+ end
+ end
+ end
+
+ # Specify the layout to use for this class.
+ #
+ # If the specified layout is a:
+ # String:: the String is the template name
+ # Symbol:: call the method specified by the symbol, which will return the template name
+ # false:: There is no layout
+ # true:: raise an ArgumentError
+ # nil:: Force default layout behavior with inheritance
+ #
+ # ==== Parameters
+ # * <tt>layout</tt> - The layout to use.
+ #
+ # ==== Options (conditions)
+ # * :only - A list of actions to apply this layout to.
+ # * :except - Apply this layout to all actions but this one.
+ def layout(layout, conditions = {})
+ include LayoutConditions unless conditions.empty?
+
+ conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
+ self._layout_conditions = conditions
+
+ self._layout = layout
+ _write_layout_method
+ end
+
+ # If no layout is supplied, look for a template named the return
+ # value of this method.
+ #
+ # ==== Returns
+ # * <tt>String</tt> - A template name
+ def _implied_layout_name # :nodoc:
+ controller_path
+ end
+
+ # Creates a _layout method to be called by _default_layout .
+ #
+ # If a layout is not explicitly mentioned then look for a layout with the controller's name.
+ # if nothing is found then try same procedure to find super class's layout.
+ def _write_layout_method # :nodoc:
+ remove_possible_method(:_layout)
+
+ prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
+ name_clause = if name
+ default_behavior
+ else
+ <<-RUBY
+ super
+ RUBY
+ end
+
+ layout_definition = case _layout
+ when String
+ _layout.inspect
+ when Symbol
+ <<-RUBY
+ #{_layout}.tap do |layout|
+ return #{default_behavior} if layout.nil?
+ unless layout.is_a?(String) || !layout
+ raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
+ "should have returned a String, false, or nil"
+ end
+ end
+ RUBY
+ when Proc
+ define_method :_layout_from_proc, &_layout
+ protected :_layout_from_proc
+ <<-RUBY
+ result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
+ return #{default_behavior} if result.nil?
+ result
+ RUBY
+ when false
+ nil
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
+ when nil
+ name_clause
+ end
+
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout
+ if _conditional_layout?
+ #{layout_definition}
+ else
+ #{name_clause}
+ end
+ end
+ private :_layout
+ RUBY
+ end
+ end
+
+ def _normalize_options(options) # :nodoc:
+ super
+
+ if _include_layout?(options)
+ layout = options.delete(:layout) { :default }
+ options[:layout] = _layout_for_option(layout)
+ end
+ end
+
+ attr_internal_writer :action_has_layout
+
+ def initialize(*) # :nodoc:
+ @_action_has_layout = true
+ super
+ end
+
+ # Controls whether an action should be rendered using a layout.
+ # If you want to disable any <tt>layout</tt> 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 <tt>action_has_layout</tt> attribute
+ # to false before rendering.
+ def action_has_layout?
+ @_action_has_layout
+ end
+
+ private
+
+ def _conditional_layout?
+ true
+ end
+
+ # This will be overwritten by _write_layout_method
+ def _layout; end
+
+ # Determine the layout for a given name, taking into account the name type.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of the template
+ def _layout_for_option(name)
+ case name
+ when String then _normalize_layout(name)
+ when Proc then name
+ when true then Proc.new { _default_layout(true) }
+ when :default then Proc.new { _default_layout(false) }
+ when false, nil then nil
+ else
+ raise ArgumentError,
+ "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
+ end
+ end
+
+ def _normalize_layout(value)
+ value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
+ end
+
+ # Returns the default layout for this controller.
+ # Optionally raises an exception if the layout could not be found.
+ #
+ # ==== Parameters
+ # * <tt>require_layout</tt> - If set to true and layout is not found,
+ # an ArgumentError exception is raised (defaults to false)
+ #
+ # ==== Returns
+ # * <tt>template</tt> - The template object for the default layout (or nil)
+ def _default_layout(require_layout = false)
+ begin
+ value = _layout if action_has_layout?
+ rescue NameError => e
+ raise e, "Could not render layout: #{e.message}"
+ 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
+
+ def _include_layout?(options)
+ (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
+ end
+ end
+end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index fd9a543e0a..354a136894 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -1,3 +1,5 @@
+require 'active_support/log_subscriber'
+
module ActionView
# = Action View Log Subscriber
#
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index e80e0ed9b0..4113448af0 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -35,5 +35,18 @@ module ActionView
end
end
end
+
+ initializer "action_view.setup_action_pack", before: :add_view_paths do |app|
+ ActiveSupport.on_load(:action_controller) do
+ ActionController::Base.superclass.send(:include, ActionView::Layouts)
+ ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
+ end
+ end
+
+ initializer "action_view.setup_action_mailer", before: :add_view_paths do |app|
+ ActiveSupport.on_load(:action_mailer) do
+ ActionMailer::Base.send(:include, ActionView::Layouts)
+ end
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 4d5c5db80c..668831dff3 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -11,7 +11,7 @@ module ActionView
prepend_formats(template.formats)
unless context.rendered_format
- context.rendered_format = template.formats.first || formats.last
+ context.rendered_format = template.formats.first || formats.first
end
render_template(template, options[:layout], options[:locals])
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
new file mode 100644
index 0000000000..18a0788f8e
--- /dev/null
+++ b/actionview/lib/action_view/rendering.rb
@@ -0,0 +1,141 @@
+require "action_view/view_paths"
+
+module ActionView
+ # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
+ # it will trigger the lookup_context and consequently expire the cache.
+ class I18nProxy < ::I18n::Config #:nodoc:
+ attr_reader :original_config, :lookup_context
+
+ def initialize(original_config, lookup_context)
+ original_config = original_config.original_config if original_config.respond_to?(:original_config)
+ @original_config, @lookup_context = original_config, lookup_context
+ end
+
+ def locale
+ @original_config.locale
+ end
+
+ def locale=(value)
+ @lookup_context.locale = value
+ end
+ end
+
+ module Rendering
+ extend ActiveSupport::Concern
+ include ActionView::ViewPaths
+
+ # Overwrite process to setup I18n proxy.
+ def process(*) #:nodoc:
+ old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
+ super
+ ensure
+ I18n.config = old_config
+ end
+
+ module ClassMethods
+ def view_context_class
+ @view_context_class ||= begin
+ routes = respond_to?(:_routes) && _routes
+ helpers = respond_to?(:_helpers) && _helpers
+
+ Class.new(ActionView::Base) do
+ if routes
+ include routes.url_helpers
+ include routes.mounted_helpers
+ end
+
+ if helpers
+ include helpers
+ end
+ end
+ end
+ end
+ end
+
+ attr_internal_writer :view_context_class
+
+ def view_context_class
+ @_view_context_class ||= self.class.view_context_class
+ end
+
+ # An instance of a view class. The default view class is ActionView::Base
+ #
+ # The view class must have the following methods:
+ # View.new[lookup_context, assigns, controller]
+ # Create a new ActionView instance for a controller
+ # View#render[options]
+ # Returns String with the rendered template
+ #
+ # Override this method in a module to change the default behavior.
+ def view_context
+ view_context_class.new(view_renderer, view_assigns, self)
+ end
+
+ # Returns an object that is able to render templates.
+ # :api: private
+ def view_renderer
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
+ end
+
+ # Find and renders a template based on the options given.
+ # :api: private
+ def _render_template(options) #:nodoc:
+ lookup_context.rendered_format = nil if options[:formats]
+ view_renderer.render(view_context, options)
+ end
+
+ def render_to_body(options = {})
+ _process_options(options)
+ _render_template(options)
+ end
+
+ def rendered_format
+ Mime[lookup_context.rendered_format]
+ end
+
+ private
+
+ # Assign the rendered format to lookup context.
+ def _process_format(format) #:nodoc:
+ super
+ lookup_context.formats = [format.to_sym]
+ lookup_context.rendered_format = lookup_context.formats.first
+ end
+
+ # Normalize args by converting render "foo" to render :action => "foo" and
+ # render "foo/bar" to render :file => "foo/bar".
+ # :api: private
+ def _normalize_args(action=nil, options={})
+ options = super(action, options)
+ case action
+ when NilClass
+ when Hash
+ options = action
+ when String, Symbol
+ action = action.to_s
+ key = action.include?(?/) ? :file : :action
+ options[key] = action
+ else
+ options[:partial] = action
+ end
+
+ options
+ end
+
+ # Normalize options.
+ # :api: private
+ def _normalize_options(options)
+ options = super(options)
+ if options[:partial] == true
+ options[:partial] = action_name
+ end
+
+ if (options.keys & [:partial, :file, :template]).empty?
+ options[:prefixes] ||= _prefixes
+ end
+
+ options[:template] ||= (options[:action] || action_name).to_s
+ options
+ end
+ end
+end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
new file mode 100644
index 0000000000..6c349feb1d
--- /dev/null
+++ b/actionview/lib/action_view/view_paths.rb
@@ -0,0 +1,96 @@
+require 'action_view/base'
+
+module ActionView
+ module ViewPaths
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :_view_paths
+ self._view_paths = ActionView::PathSet.new
+ self._view_paths.freeze
+ end
+
+ delegate :template_exists?, :view_paths, :formats, :formats=,
+ :locale, :locale=, :to => :lookup_context
+
+ module ClassMethods
+ def parent_prefixes
+ @parent_prefixes ||= begin
+ parent_controller = superclass
+ prefixes = []
+
+ until parent_controller.abstract?
+ prefixes << parent_controller.controller_path
+ parent_controller = parent_controller.superclass
+ end
+
+ prefixes
+ end
+ end
+ end
+
+ # The prefixes used in render "foo" shortcuts.
+ def _prefixes
+ @_prefixes ||= begin
+ parent_prefixes = self.class.parent_prefixes
+ parent_prefixes.dup.unshift(controller_path)
+ end
+ end
+
+ # LookupContext is the object responsible to hold all information required to lookup
+ # templates, i.e. view paths and details. Check ActionView::LookupContext for more
+ # information.
+ def lookup_context
+ @_lookup_context ||=
+ ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
+ end
+
+ def details_for_lookup
+ { }
+ end
+
+ def append_view_path(path)
+ lookup_context.view_paths.push(*path)
+ end
+
+ def prepend_view_path(path)
+ lookup_context.view_paths.unshift(*path)
+ end
+
+ module ClassMethods
+ # Append a path to the list of view paths for this controller.
+ #
+ # ==== Parameters
+ # * <tt>path</tt> - If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::PathSet for more information)
+ def append_view_path(path)
+ self._view_paths = view_paths + Array(path)
+ end
+
+ # Prepend a path to the list of view paths for this controller.
+ #
+ # ==== Parameters
+ # * <tt>path</tt> - If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::PathSet for more information)
+ def prepend_view_path(path)
+ self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
+ end
+
+ # A list of all of the default view paths for this controller.
+ def view_paths
+ _view_paths
+ end
+
+ # Set the view paths.
+ #
+ # ==== Parameters
+ # * <tt>paths</tt> - If a PathSet is provided, use that;
+ # otherwise, process the parameter into a PathSet.
+ def view_paths=(paths)
+ self._view_paths = ActionView::PathSet.new(Array(paths))
+ end
+ end
+ end
+end