partials.rb 8.2 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActionView
2 3 4
  # There's also a convenience method for rendering sub templates within the current controller that depends on a single object
  # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being
  # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own.
5 6 7
  #
  # In a template for Advertiser#account:
  #
8
  #  <%= render :partial => "account" %>
9
  #
10
  # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +account+ to
11 12 13 14
  # the template for display.
  #
  # In another template for Advertiser#buy, we could have:
  #
15
  #   <%= render :partial => "account", :locals => { :account => @buyer } %>
D
Initial  
David Heinemeier Hansson 已提交
16 17
  #
  #   <% for ad in @advertisements %>
18
  #     <%= render :partial => "ad", :locals => { :ad => ad } %>
D
Initial  
David Heinemeier Hansson 已提交
19 20
  #   <% end %>
  #
21
  # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then render
22
  # "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
D
Initial  
David Heinemeier Hansson 已提交
23 24 25
  #
  # == Rendering a collection of partials
  #
26
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub
D
Initial  
David Heinemeier Hansson 已提交
27 28 29 30
  # template for each of the elements. This pattern has been implemented as a single method that 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:
  #
31
  #   <%= render :partial => "ad", :collection => @advertisements %>
D
Initial  
David Heinemeier Hansson 已提交
32
  #
33
  # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An iteration counter
34
  # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
D
Initial  
David Heinemeier Hansson 已提交
35
  # example above, the template would be fed +ad_counter+.
36 37 38
  #
  # 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.
39
  #
D
Initial  
David Heinemeier Hansson 已提交
40 41 42 43
  # == Rendering shared partials
  #
  # Two controllers can share a set of partials and render them like this:
  #
44
  #   <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
D
Initial  
David Heinemeier Hansson 已提交
45
  #
46
  # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
  #
  # == 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 of users:
  #
  #   <!-- app/views/users/index.html.erb -->
  #   Here's the administrator:
  #   <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
  #
  #   Here's the editor:
  #   <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
  #
  #   <!-- app/views/users/_user.html.erb -->
  #   Name: <%= user.name %>
  #
  #   <!-- app/views/users/_administrator.html.erb -->
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     <%= yield %>
  #   </div>
  #
  #   <!-- app/views/users/_editor.html.erb -->
  #   <div id="editor">
  #     Deadline: $<%= user.deadline %>
  #     <%= yield %>
  #   </div>
  #
  # ...this will return:
  #
  #   Here's the administrator:
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     Name: <%= user.name %>
  #   </div>
  #
  #   Here's the editor:
  #   <div id="editor">
  #     Deadline: $<%= user.deadline %>
  #     Name: <%= user.name %>
  #   </div>
  #
  # 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 %>
  #     Title: <%= chief.title %>
  #   <% end %>
  #
  # ...this will return:
  #
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     Title: <%= chief.name %>
  #   </div>
  #
  # As you can see, the :locals hash is shared between both the partial and its layout.
D
Initial  
David Heinemeier Hansson 已提交
104
  module Partials
105 106 107
    private
      # Deprecated, use render :partial
      def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
108 109 110 111 112 113 114 115
        case partial_path
        when String, Symbol, NilClass
          path, partial_name = partial_pieces(partial_path)
          object = extracting_object(partial_name, local_assigns, deprecated_local_assigns)
          local_assigns = extract_local_assigns(local_assigns, deprecated_local_assigns)
          local_assigns = local_assigns ? local_assigns.clone : {}
          add_counter_to_local_assigns!(partial_name, local_assigns)
          add_object_to_local_assigns!(partial_name, local_assigns, object)
116

117 118 119 120 121
          if logger
            ActionController::Base.benchmark("Rendered #{path}/_#{partial_name}", Logger::DEBUG, false) do
              render("#{path}/_#{partial_name}", local_assigns)
            end
          else
122 123
            render("#{path}/_#{partial_name}", local_assigns)
          end
124
        when Array, ActiveRecord::Associations::AssociationCollection
125 126 127 128 129 130 131
          if partial_path.any?
            path       = ActionController::RecordIdentifier.partial_path(partial_path.first)
            collection = partial_path
            render_partial_collection(path, collection, nil, local_assigns.value)
          else
            ""
          end
132
        else
133 134 135
          render_partial(
            ActionController::RecordIdentifier.partial_path(partial_path),
            local_assigns, deprecated_local_assigns)
136
        end
137
      end
D
Initial  
David Heinemeier Hansson 已提交
138

139 140 141 142 143 144 145 146 147
      # Deprecated, use render :partial, :collection
      def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil) #:nodoc:
        collection_of_partials = Array.new
        counter_name = partial_counter_name(partial_name)
        local_assigns = local_assigns ? local_assigns.clone : {}
        collection.each_with_index do |element, counter|
          local_assigns[counter_name] = counter
          collection_of_partials.push(render_partial(partial_name, element, local_assigns))
        end
D
Initial  
David Heinemeier Hansson 已提交
148

149
        return " " if collection_of_partials.empty?
150

151 152 153 154 155 156
        if partial_spacer_template
          spacer_path, spacer_name = partial_pieces(partial_spacer_template)
          collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}"))
        else
          collection_of_partials.join
        end
D
Initial  
David Heinemeier Hansson 已提交
157
      end
158 159 160

      alias_method :render_collection_of_partials, :render_partial_collection

D
Initial  
David Heinemeier Hansson 已提交
161 162 163 164
      def partial_pieces(partial_path)
        if partial_path.include?('/')
          return File.dirname(partial_path), File.basename(partial_path)
        else
165
          return controller.class.controller_path, partial_path
D
Initial  
David Heinemeier Hansson 已提交
166 167
        end
      end
168 169

      def partial_counter_name(partial_name)
170 171 172 173 174
        "#{partial_variable_name(partial_name)}_counter".intern
      end

      def partial_variable_name(partial_name)
        partial_name.split('/').last.split('.').first.intern
175
      end
176

177
      def extracting_object(partial_name, local_assigns, deprecated_local_assigns)
178
        variable_name = partial_variable_name(partial_name)
179
        if local_assigns.is_a?(Hash) || local_assigns.nil?
180
          controller.instance_variable_get("@#{variable_name}")
181 182 183 184 185
        else
          # deprecated form where object could be passed in as second parameter
          local_assigns
        end
      end
186

187 188 189
      def extract_local_assigns(local_assigns, deprecated_local_assigns)
        local_assigns.is_a?(Hash) ? local_assigns : deprecated_local_assigns
      end
190

191 192 193 194
      def add_counter_to_local_assigns!(partial_name, local_assigns)
        counter_name = partial_counter_name(partial_name)
        local_assigns[counter_name] = 1 unless local_assigns.has_key?(counter_name)
      end
195

196
      def add_object_to_local_assigns!(partial_name, local_assigns, object)
197 198
        variable_name = partial_variable_name(partial_name)
        local_assigns[variable_name] ||=
199 200 201 202
          if object.is_a?(ActionView::Base::ObjectWrapper)
            object.value
          else
            object
203
          end || controller.instance_variable_get("@#{variable_name}")
204
      end
D
Initial  
David Heinemeier Hansson 已提交
205 206
  end
end