partials.rb 12.2 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 174
    extend ActiveSupport::Memoizable

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    def _render_partial(options = {}) #:nodoc:
      options[:locals] ||= {}

      case path = partial = options[:partial]        
      when *_array_like_objects
        return _render_partial_collection(partial, options)
      else
        if partial.is_a?(ActionView::Helpers::FormBuilder)
          path = partial.class.to_s.demodulize.underscore.sub(/_builder$/, '')
          options[:locals].merge!(path.to_sym => partial)
        elsif !partial.is_a?(String)
          options[:object] = object = partial
          path = ActionController::RecordIdentifier.partial_path(object, controller_path)
        end
        _, _, prefix, object = parts = partial_parts(path, options)
        template = find_by_parts(*parts)
        _render_partial_object(template, options, (object unless object == true))
      end
    end

195
    private
196 197 198
      def partial_parts(name, options)
        segments = name.split("/")
        parts = segments.pop.split(".")
199

200 201 202 203 204 205 206
        case parts.size
        when 1
          parts
        when 2, 3
          extension = parts.delete_at(1).to_sym
          if formats.include?(extension)
            self.formats.replace [extension]
R
Ryan Bates 已提交
207
          end
208 209 210 211 212 213 214 215 216
          parts.pop if parts.size == 2
        end

        path = parts.join(".")
        prefix = segments[0..-1].join("/")
        prefix = prefix.blank? ? controller_path : prefix
        parts = [path, formats, prefix]
        parts.push options[:object] || true
      end
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

      def _render_partial_with_block(layout, block, options)
        @_proc_for_layout = block
        concat(_render_partial(options.merge(:partial => layout)))
      ensure
        @_proc_for_layout = nil
      end
  
      def _render_partial_with_layout(layout, options)
        if layout
          prefix = controller && !layout.include?("/") ? controller.controller_path : nil
          layout = find_by_parts(layout, formats, prefix, true)
        end
        content = _render_partial(options)
        return _render_content_with_layout(content, layout, options[:locals])
      end
    
      def _deprecated_ivar_assign(template)
        if respond_to?(:controller)
          ivar = :"@#{template.variable_name}"
          object =
            if controller.instance_variable_defined?(ivar)
              ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
                controller.instance_variable_get(ivar),
                "#{ivar} will no longer be implicitly assigned to #{template.variable_name}")
            end
        end
      end
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284

      def _render_partial_with_block(layout, block, options)
        @_proc_for_layout = block
        concat(_render_partial(options.merge(:partial => layout)))
      ensure
        @_proc_for_layout = nil
      end
  
      def _render_partial_with_layout(layout, options)
        if layout
          prefix = controller && !layout.include?("/") ? controller.controller_path : nil
          layout = find_by_parts(layout, formats, prefix, true)
        end
        content = _render_partial(options)
        return _render_content_with_layout(content, layout, options[:locals])
      end
    
      def _deprecated_ivar_assign(template)
        if respond_to?(:controller)
          ivar = :"@#{template.variable_name}"
          object =
            if controller.instance_variable_defined?(ivar)
              ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
                controller.instance_variable_get(ivar),
                "#{ivar} will no longer be implicitly assigned to #{template.variable_name}")
            end
        end
      end

      def _array_like_objects
        array_like = [Array]
        if defined?(ActiveRecord)
          array_like.push(ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope)
        end
        array_like
      end

      def _render_partial_object(template, options, object = nil)
        if options.key?(:collection)
          _render_partial_collection(options.delete(:collection), options, template)
285
        else
286 287 288 289 290
          locals = (options[:locals] ||= {})
          object ||= locals[:object] || locals[template.variable_name]
          
          _set_locals(object, locals, template, options)          
          _render_template(template, locals)
291
        end
292
      end
D
Initial  
David Heinemeier Hansson 已提交
293

294 295 296 297 298
      def _set_locals(object, locals, template, options)
        object ||= _deprecated_ivar_assign(template)
        locals[:object] = locals[template.variable_name] = object
        locals[options[:as]] = object if options[:as]
      end
299

300 301 302 303
      def _render_partial_collection(collection, options = {}, passed_template = nil) #:nodoc:
        return nil if collection.blank?
        
        spacer = options[:spacer_template] ? _render_partial(:partial => options[:spacer_template]) : ''
304

305 306 307 308 309 310 311 312 313 314 315 316
        locals = (options[:locals] ||= {})
        index, @_partial_path = 0, nil
        collection.map do |object|
          template = passed_template || begin
            _partial_path = 
              ActionController::RecordIdentifier.partial_path(object, controller_path)
            template = _pick_partial_template(_partial_path)
          end

          _set_locals(object, locals, template, options)
          locals[template.counter_name] = index
          
317
          index += 1
318
          _render_template(template, locals)
319
        end.join(spacer)
320 321
      end

322
      def _pick_partial_template(partial_path) #:nodoc:
323 324
        prefix = controller_path unless partial_path.include?('/')
        find_by_parts(partial_path, formats, prefix, true)
325
      end
326
      memoize :_pick_partial_template
D
Initial  
David Heinemeier Hansson 已提交
327 328
  end
end