partials.rb 9.6 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActionView
R
Ryan Bates 已提交
2 3 4 5
  # 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.
6 7 8
  #
  # In a template for Advertiser#account:
  #
9
  #  <%= render :partial => "account" %>
10
  #
R
Ryan Bates 已提交
11 12
  # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable
  # +account+ to the template for display.
13 14 15
  #
  # In another template for Advertiser#buy, we could have:
  #
16
  #   <%= render :partial => "account", :locals => { :account => @buyer } %>
D
Initial  
David Heinemeier Hansson 已提交
17 18
  #
  #   <% for ad in @advertisements %>
19
  #     <%= render :partial => "ad", :locals => { :ad => ad } %>
D
Initial  
David Heinemeier Hansson 已提交
20 21
  #   <% end %>
  #
R
Ryan Bates 已提交
22 23
  # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then
  # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
D
Initial  
David Heinemeier Hansson 已提交
24 25 26
  #
  # == Rendering a collection of partials
  #
R
Ryan Bates 已提交
27 28 29 30
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
  # render a sub 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:
D
Initial  
David Heinemeier Hansson 已提交
31
  #
32
  #   <%= render :partial => "ad", :collection => @advertisements %>
D
Initial  
David Heinemeier Hansson 已提交
33
  #
R
Ryan Bates 已提交
34 35 36
  # This will render "advertiser/_ad.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
  # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
37
  #
R
Ryan Bates 已提交
38 39
  # 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.
40
  #
D
Initial  
David Heinemeier Hansson 已提交
41 42 43 44
  # == Rendering shared partials
  #
  # Two controllers can share a set of partials and render them like this:
  #
45
  #   <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
D
Initial  
David Heinemeier Hansson 已提交
46
  #
47
  # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
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
  # == Rendering objects with the RecordIdentifier
  #
  # 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:
  #
  #  # @account is an Account instance, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "accounts/account", :locals => { :account => @buyer } %>
  #  <%= render :partial => @account %>
  #
  #  # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
  #  # <%= 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" %>
  #  <%= 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) %>
  #
  #  # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "posts/post", :collection => @posts %>
  #  <%= render(@posts) %>
  #
81 82
  # == Rendering partials with layouts
  #
R
Ryan Bates 已提交
83 84 85
  # 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:
86
  #
87
  #   <%# app/views/users/index.html.erb &>
88 89 90 91 92 93
  #   Here's the administrator:
  #   <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
  #
  #   Here's the editor:
  #   <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
  #
94
  #   <%# app/views/users/_user.html.erb &>
95 96
  #   Name: <%= user.name %>
  #
97
  #   <%# app/views/users/_administrator.html.erb &>
98 99 100 101 102
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     <%= yield %>
  #   </div>
  #
103
  #   <%# app/views/users/_editor.html.erb &>
104
  #   <div id="editor">
105
  #     Deadline: <%= user.deadline %>
106 107 108 109 110 111 112 113 114 115 116 117 118
  #     <%= 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">
119
  #     Deadline: <%= user.deadline %>
120 121 122 123 124
  #     Name: <%= user.name %>
  #   </div>
  #
  # You can also apply a layout to a block within any template:
  #
125
  #   <%# app/views/users/_chief.html.erb &>
126 127 128 129 130 131 132 133 134 135 136
  #   <% render(:layout => "administrator", :locals => { :user => chief }) do %>
  #     Title: <%= chief.title %>
  #   <% end %>
  #
  # ...this will return:
  #
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     Title: <%= chief.name %>
  #   </div>
  #
137
  # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  #
  # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
  # an array to layout and treat it as an enumerable.
  #
  #   <%# app/views/users/_user.html.erb &>
  #   <div class="user">
  #     Budget: $<%= user.budget %>
  #     <%= yield user %>
  #   </div>
  #
  #   <%# app/views/users/index.html.erb &>
  #   <% render :layout => @users do |user| %>
  #     Title: <%= user.title %>
  #   <% end %>
  #
  # This will render the layout for each user and yield to the block, passing the user, each time.
  #
  # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
  #
  #   <%# app/views/users/_user.html.erb &>
  #   <div class="user">
  #     <%= yield user, :header %>
  #     Budget: $<%= user.budget %>
  #     <%= yield user, :footer %>
  #   </div>
  #
  #   <%# app/views/users/index.html.erb &>
  #   <% render :layout => @users do |user, section| %>
  #     <%- case section when :header -%>
  #       Title: <%= user.title %>
  #     <%- when :footer -%>
  #       Deadline: <%= user.deadline %>
  #     <%- end -%>
  #   <% end %>
D
Initial  
David Heinemeier Hansson 已提交
172
  module Partials
173
    extend ActiveSupport::Memoizable
174
    extend ActiveSupport::Concern
Y
Yehuda Katz 已提交
175

176
    included do
Y
Yehuda Katz 已提交
177
      attr_accessor :_partial
178 179
    end

180 181 182 183 184 185
    module ClassMethods
      def _partial_names
        @_partial_names ||= ActiveSupport::ConcurrentHash.new
      end
    end

Y
Yehuda Katz 已提交
186
    def render_partial(options)
187
      @assigns_added = false
188 189
      # TODO: Handle other details here.
      self.formats = options[:_details][:formats]
190
      _render_partial(options)
191
    end
192

193
    def _render_partial(options, &block) #:nodoc:
194 195
      options[:locals] ||= {}

Y
Yehuda Katz 已提交
196 197 198
      path = partial = options[:partial]

      if partial.respond_to?(:to_ary)
199
        return _render_partial_collection(partial, options, &block)
Y
Yehuda Katz 已提交
200 201
      elsif !partial.is_a?(String)
        options[:object] = object = partial
202
        path = _partial_path(object)
203
      end
Y
Yehuda Katz 已提交
204

205
      _render_partial_object(_pick_partial_template(path), options, &block)
206 207
    end

208
    private
209 210 211 212 213 214 215 216 217 218 219
      def _partial_path(object)
        self.class._partial_names[[controller.class, object.class]] ||= begin
          name = object.class.model_name
          if controller_path && controller_path.include?("/")
            File.join(File.dirname(controller_path), name.partial_path)
          else
            name.partial_path
          end
        end
      end

220 221 222 223 224 225
      def _render_partial_template(template, locals, object, options = {}, &block)
        options[:_template] = template
        locals[:object] = locals[template.variable_name] = object
        locals[options[:as]] = object if options[:as]

        _render_single_template(template, locals, &block)
226 227
      end

228
      def _render_partial_object(template, options, &block)
229
        if options.key?(:collection)
230
          _render_partial_collection(options.delete(:collection), options, template, &block)
231
        else
232
          locals = (options[:locals] ||= {})
233
          object = options[:object] || locals[:object] || locals[template.variable_name]
Y
Yehuda Katz 已提交
234

235
          _render_partial_template(template, locals, object, options, &block)
236
        end
237
      end
D
Initial  
David Heinemeier Hansson 已提交
238

239
      def _render_partial_collection(collection, options = {}, template = nil, &block) #:nodoc:
240 241
        options[:_template] ||= template

242
        return nil if collection.blank?
Y
Yehuda Katz 已提交
243

244 245 246
        if options.key?(:spacer_template)
          spacer = _render_partial(:partial => options[:spacer_template])
        end
247 248

        locals, index = options[:locals] || {}, 0
249

250
        collection.map do |object|
251 252
          tmp = template || _pick_partial_template(_partial_path(object))
          locals[tmp.counter_name] = index
253
          index += 1
Y
Yehuda Katz 已提交
254

255
          _render_partial_template(tmp, locals, object, options, &block)
256
        end.join(spacer)
257 258
      end

259
      def _pick_partial_template(partial_path) #:nodoc:
260
        prefix = controller_path unless partial_path.include?(?/)
261
        find(partial_path, {:formats => formats}, prefix, true)
262
      end
D
Initial  
David Heinemeier Hansson 已提交
263 264
  end
end