lib/action_view/renderer/partial_renderer.rb in actionpack-3.1.12 vs lib/action_view/renderer/partial_renderer.rb in actionpack-3.2.0.rc1
- old
+ new
@@ -25,11 +25,11 @@
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
# render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
#
# == The :as and :object options
#
- # By default <tt>ActionView::Partials::PartialRenderer</tt> doesn't have any local variables.
+ # 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 %>
#
# would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
@@ -80,20 +80,22 @@
#
# <%= 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.
#
- # == Rendering objects with the RecordIdentifier
+ # == Rendering objects that respond to `to_partial_path`
#
- # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
- # you're following its conventions for RecordIdentifier#partial_path. Examples:
+ # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
+ # and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection,
+ # all objects must return the same path.
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
# <%= render :partial => @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @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 %>
#
# == Rendering the default case
#
@@ -104,17 +106,18 @@
# <%= render "account" %>
#
# # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
# <%= render "account", :account => @buyer %>
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
- # <%= render(@account) %>
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
+ # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
+ # <%= render @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @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(@posts) %>
+ # <%= render @posts %>
#
# == Rendering partials with layouts
#
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
@@ -203,33 +206,31 @@
# Title: <%= user.title %>
# <%- when :footer -%>
# Deadline: <%= user.deadline %>
# <%- end -%>
# <% end %>
- class PartialRenderer < AbstractRenderer #:nodoc:
- PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
+ class PartialRenderer < AbstractRenderer
+ PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
- @partial_names = PARTIAL_NAMES[@lookup_context.prefixes.first]
+ @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
- wrap_formats(@path) do
- identifier = ((@template = find_partial) ? @template.identifier : @path)
-
- if @collection
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
- render_collection
- end
- else
- instrument(:partial, :identifier => identifier) do
- render_partial
- end
+ if @collection
+ instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
+ render_collection
end
+ else
+ instrument(:partial, :identifier => identifier) do
+ render_partial
+ end
end
end
def render_collection
return nil if @collection.blank?
@@ -268,10 +269,11 @@
partial = options[:partial]
@options = options
@locals = options[:locals] || {}
@block = block
+ @details = extract_details(options)
if String === partial
@object = options[:object]
@path = partial
@collection = collection
@@ -290,10 +292,17 @@
@variable, @variable_counter = retrieve_variable(@path)
else
paths.map! { |path| retrieve_variable(path).unshift(path) }
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.")
+ end
+
+ extract_format(@path, @details)
self
end
def collection
if @options.key?(:collection)
@@ -317,11 +326,11 @@
end
end
def find_template(path=@path, locals=@locals.keys)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
- @lookup_context.find_template(path, prefixes, true, locals)
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
segments, locals, template = [], @locals, @template
as, counter = @variable, @variable_counter
@@ -353,30 +362,40 @@
@template = template
segments
end
def partial_path(object = @object)
- @partial_names[object.class.name] ||= begin
- object = object.to_model if object.respond_to?(:to_model)
- object.class.model_name.partial_path.dup.tap do |partial|
- path = @lookup_context.prefixes.first
- merge_path_into_partial(path, partial)
+ object = object.to_model if object.respond_to?(:to_model)
+
+ path = if object.respond_to?(:to_partial_path)
+ object.to_partial_path
+ else
+ klass = object.class
+ if klass.respond_to?(:model_name)
+ ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
+ klass.model_name.partial_path
+ else
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object that returns a valid partial path.")
end
end
+
+ @partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
end
- def merge_path_into_partial(path, partial)
- if path.include?(?/) && partial.include?(?/)
- overlap = []
- path_array = File.dirname(path).split('/')
- partial_array = partial.split('/')[0..-3] # skip model dir & partial
+ def merge_prefix_into_object_path(prefix, object_path)
+ if prefix.include?(?/) && object_path.include?(?/)
+ prefixes = []
+ prefix_array = File.dirname(prefix).split('/')
+ object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
- path_array.each_with_index do |dir, index|
- overlap << dir if dir == partial_array[index]
+ prefix_array.each_with_index do |dir, index|
+ break if dir == object_path_array[index]
+ prefixes << dir
end
- partial.gsub!(/^#{overlap.join('/')}\//,'')
- partial.insert(0, "#{File.dirname(path)}/")
+ (prefixes << object_path).join("/")
+ else
+ object_path
end
end
def retrieve_variable(path)
variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym