aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/renderer/partial_renderer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_view/renderer/partial_renderer.rb')
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb202
1 files changed, 117 insertions, 85 deletions
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 245a19deec..37f93a13fc 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/object/blank'
+require 'thread_safe'
module ActionView
# = Action View Partials
@@ -10,16 +10,16 @@ module ActionView
#
# In a template for Advertiser#account:
#
- # <%= render :partial => "account" %>
+ # <%= render partial: "account" %>
#
# This would render "advertiser/_account.html.erb".
#
# In another template for Advertiser#buy, we could have:
#
- # <%= render :partial => "account", :locals => { :account => @buyer } %>
+ # <%= render partial: "account", locals: { account: @buyer } %>
#
# <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :locals => { :ad => ad } %>
+ # <%= render partial: "ad", locals: { ad: ad } %>
# <% end %>
#
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
@@ -30,21 +30,21 @@ module ActionView
# By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
#
- # <%= render :partial => "account", :object => @buyer %>
+ # <%= render partial: "account", object: @buyer %>
#
- # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
# equivalent to:
#
- # <%= render :partial => "account", :locals => { :account => @buyer } %>
+ # <%= render partial: "account", locals: { account: @buyer } %>
#
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
# wanted it to be +user+ instead of +account+ we'd do:
#
- # <%= render :partial => "account", :object => @buyer, :as => 'user' %>
+ # <%= render partial: "account", object: @buyer, as: 'user' %>
#
# This is equivalent to
#
- # <%= render :partial => "account", :locals => { :user => @buyer } %>
+ # <%= render partial: "account", locals: { user: @buyer } %>
#
# == Rendering a collection of partials
#
@@ -53,7 +53,7 @@ module ActionView
# accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
# example in "Using partials" can be rewritten with a single line:
#
- # <%= render :partial => "ad", :collection => @advertisements %>
+ # <%= render partial: "ad", collection: @advertisements %>
#
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
# iteration counter will automatically be made available to the template with a name of the form
@@ -64,12 +64,12 @@ module ActionView
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
#
- # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
+ # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
#
# If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
# to specify a text which will displayed instead by using this form:
#
- # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
+ # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
#
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
# just keep domain objects, like Active Records, in there.
@@ -78,7 +78,7 @@ module ActionView
#
# Two controllers can share a set of partials and render them like this:
#
- # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
+ # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
@@ -88,32 +88,32 @@ module ActionView
# and pick the proper path by checking `to_partial_path` method.
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
- # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
- # <%= render :partial => @account %>
+ # # <%= render partial: "accounts/account", locals: { account: @account} %>
+ # <%= render partial: @account %>
#
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
# # that's why we can replace:
- # # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render :partial => @posts %>
+ # # <%= render partial: "posts/post", collection: @posts %>
+ # <%= render partial: @posts %>
#
# == Rendering the default case
#
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
# defaults of render to render partials. Examples:
#
- # # Instead of <%= render :partial => "account" %>
+ # # Instead of <%= render partial: "account" %>
# <%= render "account" %>
#
- # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
- # <%= render "account", :account => @buyer %>
+ # # Instead of <%= render partial: "account", locals: { account: @buyer } %>
+ # <%= render "account", account: @buyer %>
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
- # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
+ # # <%= render partial: "accounts/account", locals: { account: @account} %>
# <%= render @account %>
#
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
# # that's why we can replace:
- # # <%= render :partial => "posts/post", :collection => @posts %>
+ # # <%= render partial: "posts/post", collection: @posts %>
# <%= render @posts %>
#
# == Rendering partials with layouts
@@ -124,10 +124,10 @@ module ActionView
#
# <%# app/views/users/index.html.erb &>
# Here's the administrator:
- # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
+ # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
#
# Here's the editor:
- # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
+ # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
#
# <%# app/views/users/_user.html.erb &>
# Name: <%= user.name %>
@@ -158,8 +158,8 @@ module ActionView
# Name: <%= user.name %>
# </div>
#
- # If a collection is given, the layout will be rendered once for each item in the collection. Just think
- # these two snippets have the same output:
+ # If a collection is given, the layout will be rendered once for each item in
+ # the collection. Just think these two snippets have the same output:
#
# <%# app/views/users/_user.html.erb %>
# Name: <%= user.name %>
@@ -169,7 +169,7 @@ module ActionView
# <ul>
# <% users.each do |user| -%>
# <li>
- # <%= render :partial => "user", :locals => { :user => user } %>
+ # <%= render partial: "user", locals: { user: user } %>
# </li>
# <% end -%>
# </ul>
@@ -181,10 +181,10 @@ module ActionView
#
# <%# app/views/users/index.html.erb %>
# <ul>
- # <%= render :partial => "user", :layout => "li_layout", :collection => users %>
+ # <%= render partial: "user", layout: "li_layout", collection: users %>
# </ul>
#
- # Given two users whose names are Alice and Bob, these snippets return:
+ # Given two users whose names are Alice and Bob, these snippets return:
#
# <ul>
# <li>
@@ -195,10 +195,14 @@ module ActionView
# </li>
# </ul>
#
+ # The current object being rendered, as well as the object_counter, will be
+ # available as local variables inside the layout template under the same names
+ # as available in the partial.
+ #
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
- # <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
+ # <%= render(layout: "administrator", locals: { user: chief }) do %>
# Title: <%= chief.title %>
# <% end %>
#
@@ -221,7 +225,7 @@ module ActionView
# </div>
#
# <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user| %>
+ # <%= render layout: @users do |user| %>
# Title: <%= user.title %>
# <% end %>
#
@@ -237,7 +241,7 @@ module ActionView
# </div>
#
# <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user, section| %>
+ # <%= render layout: @users do |user, section| %>
# <%- case section when :header -%>
# Title: <%= user.title %>
# <%- when :footer -%>
@@ -245,18 +249,27 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer
- PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
+ PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
+ h[k] = ThreadSafe::Cache.new
+ end
def initialize(*)
super
@context_prefix = @lookup_context.prefixes.first
- @partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
setup(context, options, block)
identifier = (@template = find_partial) ? @template.identifier : @path
+ @lookup_context.rendered_format ||= begin
+ if @template && @template.formats.present?
+ @template.formats.first
+ else
+ formats.first
+ end
+ end
+
if @collection
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
render_collection
@@ -272,26 +285,19 @@ module ActionView
return nil if @collection.blank?
if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template]).render(@view, @locals)
- end
-
- if layout = @options[:layout]
- layout = find_template(layout)
+ spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
end
result = @template ? collection_with_template : collection_without_template
-
- result.map!{|content| layout.render(@view, @locals) { content } } if layout
-
result.join(spacer).html_safe
end
def render_partial
- locals, view, block = @locals, @view, @block
+ view, locals, block = @view, @locals, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout)
+ layout = find_template(layout.to_s, @template_keys)
end
object ||= locals[as]
@@ -316,7 +322,7 @@ module ActionView
@block = block
@details = extract_details(options)
- @lookup_context.rendered_format ||= formats.first
+ prepend_formats(options[:formats])
if String === partial
@object = options[:object]
@@ -333,16 +339,16 @@ module ActionView
end
end
- if @path
- @variable, @variable_counter = retrieve_variable(@path)
- else
- paths.map! { |path| retrieve_variable(path).unshift(path) }
+ if as = options[:as]
+ raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
+ as = as.to_sym
end
- if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
- raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.")
+ if @path
+ @variable, @variable_counter = retrieve_variable(@path, as)
+ @template_keys = retrieve_template_keys
+ else
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
end
self
@@ -356,56 +362,55 @@ module ActionView
end
def collection_from_object
- if @object.respond_to?(:to_ary)
- @object.to_ary
- end
+ @object.to_ary if @object.respond_to?(:to_ary)
end
def find_partial
if path = @path
- locals = @locals.keys
- locals << @variable
- locals << @variable_counter if @collection
- find_template(path, locals)
+ find_template(path, @template_keys)
end
end
- def find_template(path=@path, locals=@locals.keys)
+ def find_template(path, locals)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
- segments, locals, template = [], @locals, @template
+ view, locals, template = @view, @locals, @template
as, counter = @variable, @variable_counter
- locals[counter] = -1
+ if layout = @options[:layout]
+ layout = find_template(layout, @template_keys)
+ end
+
+ index = -1
+ @collection.map do |object|
+ locals[as] = object
+ locals[counter] = (index += 1)
- @collection.each do |object|
- locals[counter] += 1
- locals[as] = object
- segments << template.render(@view, locals)
+ content = template.render(view, locals)
+ content = layout.render(view, locals) { content } if layout
+ content
end
-
- segments
end
-
def collection_without_template
- segments, locals, collection_data = [], @locals, @collection_data
- index, template, cache = -1, nil, {}
- keys = @locals.keys
+ view, locals, collection_data = @view, @locals, @collection_data
+ cache = {}
+ keys = @locals.keys
- @collection.each_with_index do |object, i|
- path, *data = collection_data[i]
- template = (cache[path] ||= find_template(path, keys + data))
- locals[data[0]] = object
- locals[data[1]] = (index += 1)
- segments << template.render(@view, locals)
- end
+ index = -1
+ @collection.map do |object|
+ index += 1
+ path, as, counter = collection_data[index]
+
+ locals[as] = object
+ locals[counter] = index
- @template = template
- segments
+ template = (cache[path] ||= find_template(path, keys + [as, counter]))
+ template.render(view, locals)
+ end
end
def partial_path(object = @object)
@@ -417,7 +422,15 @@ module ActionView
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
end
- @partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ if @view.prefix_partial_path_with_controller_namespace
+ prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ else
+ path
+ end
+ end
+
+ def prefixed_partial_names
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
end
def merge_prefix_into_object_path(prefix, object_path)
@@ -437,10 +450,29 @@ module ActionView
end
end
- def retrieve_variable(path)
- variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ def retrieve_template_keys
+ keys = @locals.keys
+ keys << @variable
+ keys << @variable_counter if @collection
+ keys
+ end
+
+ def retrieve_variable(path, as)
+ variable = as || begin
+ base = path[-1] == "/" ? "" : File.basename(path)
+ raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
+ $1.to_sym
+ end
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end
+
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores."
+
+ def raise_invalid_identifier(path)
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
+ end
end
end