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