actionview 4.2.10 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +141 -272
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/action_view/base.rb +33 -21
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +52 -20
- data/lib/action_view/digestor.rb +86 -83
- data/lib/action_view/flows.rb +9 -11
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +8 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
- data/lib/action_view/helpers/asset_url_helper.rb +160 -59
- data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
- data/lib/action_view/helpers/cache_helper.rb +90 -35
- data/lib/action_view/helpers/capture_helper.rb +7 -6
- data/lib/action_view/helpers/controller_helper.rb +3 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +156 -108
- data/lib/action_view/helpers/debug_helper.rb +3 -4
- data/lib/action_view/helpers/form_helper.rb +475 -94
- data/lib/action_view/helpers/form_options_helper.rb +87 -47
- data/lib/action_view/helpers/form_tag_helper.rb +88 -57
- data/lib/action_view/helpers/javascript_helper.rb +10 -10
- data/lib/action_view/helpers/number_helper.rb +76 -59
- data/lib/action_view/helpers/output_safety_helper.rb +34 -4
- data/lib/action_view/helpers/record_tag_helper.rb +12 -99
- data/lib/action_view/helpers/rendering_helper.rb +3 -3
- data/lib/action_view/helpers/sanitize_helper.rb +17 -14
- data/lib/action_view/helpers/tag_helper.rb +198 -73
- data/lib/action_view/helpers/tags/base.rb +132 -97
- data/lib/action_view/helpers/tags/check_box.rb +17 -17
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
- data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
- data/lib/action_view/helpers/tags/collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/date_select.rb +36 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/label.rb +5 -1
- data/lib/action_view/helpers/tags/password_field.rb +1 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
- data/lib/action_view/helpers/tags/radio_button.rb +4 -4
- data/lib/action_view/helpers/tags/search_field.rb +12 -9
- data/lib/action_view/helpers/tags/select.rb +9 -9
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +5 -6
- data/lib/action_view/helpers/tags/translator.rb +15 -13
- data/lib/action_view/helpers/text_helper.rb +47 -30
- data/lib/action_view/helpers/translation_helper.rb +60 -30
- data/lib/action_view/helpers/url_helper.rb +132 -104
- data/lib/action_view/helpers.rb +1 -1
- data/lib/action_view/layouts.rb +59 -54
- data/lib/action_view/log_subscriber.rb +56 -7
- data/lib/action_view/lookup_context.rb +76 -61
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +28 -19
- data/lib/action_view/railtie.rb +30 -6
- data/lib/action_view/record_identifier.rb +51 -25
- data/lib/action_view/renderer/abstract_renderer.rb +19 -15
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
- data/lib/action_view/renderer/partial_renderer.rb +208 -206
- data/lib/action_view/renderer/renderer.rb +2 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
- data/lib/action_view/renderer/template_renderer.rb +65 -66
- data/lib/action_view/rendering.rb +16 -9
- data/lib/action_view/routing_url_for.rb +25 -17
- data/lib/action_view/tasks/cache_digests.rake +23 -0
- data/lib/action_view/template/error.rb +14 -13
- data/lib/action_view/template/handlers/builder.rb +7 -7
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
- data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
- data/lib/action_view/template/handlers/erb.rb +9 -76
- data/lib/action_view/template/handlers/html.rb +9 -0
- data/lib/action_view/template/handlers/raw.rb +1 -3
- data/lib/action_view/template/handlers.rb +8 -6
- data/lib/action_view/template/html.rb +2 -4
- data/lib/action_view/template/resolver.rb +133 -109
- data/lib/action_view/template/text.rb +5 -8
- data/lib/action_view/template/types.rb +15 -17
- data/lib/action_view/template.rb +51 -28
- data/lib/action_view/test_case.rb +32 -27
- data/lib/action_view/testing/resolvers.rb +29 -31
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +26 -32
- data/lib/action_view.rb +5 -5
- data/lib/assets/compiled/rails-ujs.js +685 -0
- metadata +23 -23
- data/lib/action_view/tasks/dependencies.rake +0 -23
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module ActionView
|
|
2
|
+
module CollectionCaching # :nodoc:
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
# Fallback cache store if Action View is used without Rails.
|
|
7
|
+
# Otherwise overridden in Railtie to use Rails.cache.
|
|
8
|
+
mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def cache_collection_render(instrumentation_payload)
|
|
13
|
+
return yield unless @options[:cached]
|
|
14
|
+
|
|
15
|
+
keyed_collection = collection_by_cache_keys
|
|
16
|
+
cached_partials = collection_cache.read_multi(*keyed_collection.keys)
|
|
17
|
+
instrumentation_payload[:cache_hits] = cached_partials.size
|
|
18
|
+
|
|
19
|
+
@collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
|
|
20
|
+
rendered_partials = @collection.empty? ? [] : yield
|
|
21
|
+
|
|
22
|
+
index = 0
|
|
23
|
+
fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
|
|
24
|
+
rendered_partials[index].tap { index += 1 }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def callable_cache_key?
|
|
29
|
+
@options[:cached].respond_to?(:call)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def collection_by_cache_keys
|
|
33
|
+
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
|
|
34
|
+
|
|
35
|
+
@collection.each_with_object({}) do |item, hash|
|
|
36
|
+
hash[expanded_cache_key(seed.call(item))] = item
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def expanded_cache_key(key)
|
|
41
|
+
key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
|
|
42
|
+
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fetch_or_cache_partial(cached_partials, order_by:)
|
|
46
|
+
order_by.map do |cache_key|
|
|
47
|
+
cached_partials.fetch(cache_key) do
|
|
48
|
+
yield.tap do |rendered_partial|
|
|
49
|
+
collection_cache.write(cache_key, rendered_partial)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "concurrent/map"
|
|
2
|
+
require "action_view/renderer/partial_renderer/collection_caching"
|
|
2
3
|
|
|
3
4
|
module ActionView
|
|
4
5
|
class PartialIteration
|
|
@@ -73,7 +74,7 @@ module ActionView
|
|
|
73
74
|
#
|
|
74
75
|
# <%= render partial: "account", locals: { user: @buyer } %>
|
|
75
76
|
#
|
|
76
|
-
# == Rendering a collection of partials
|
|
77
|
+
# == \Rendering a collection of partials
|
|
77
78
|
#
|
|
78
79
|
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
|
|
79
80
|
# render a sub template for each of the elements. This pattern has been implemented as a single method that
|
|
@@ -97,15 +98,15 @@ module ActionView
|
|
|
97
98
|
#
|
|
98
99
|
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
|
|
99
100
|
#
|
|
100
|
-
# If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
|
|
101
|
-
# to specify a text which will displayed instead by using this form:
|
|
101
|
+
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you
|
|
102
|
+
# to specify a text which will be displayed instead by using this form:
|
|
102
103
|
#
|
|
103
104
|
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
|
|
104
105
|
#
|
|
105
106
|
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
|
|
106
107
|
# just keep domain objects, like Active Records, in there.
|
|
107
108
|
#
|
|
108
|
-
# == Rendering shared partials
|
|
109
|
+
# == \Rendering shared partials
|
|
109
110
|
#
|
|
110
111
|
# Two controllers can share a set of partials and render them like this:
|
|
111
112
|
#
|
|
@@ -113,7 +114,7 @@ module ActionView
|
|
|
113
114
|
#
|
|
114
115
|
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
|
|
115
116
|
#
|
|
116
|
-
# == Rendering objects that respond to `to_partial_path`
|
|
117
|
+
# == \Rendering objects that respond to `to_partial_path`
|
|
117
118
|
#
|
|
118
119
|
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
|
|
119
120
|
# and pick the proper path by checking `to_partial_path` method.
|
|
@@ -127,7 +128,7 @@ module ActionView
|
|
|
127
128
|
# # <%= render partial: "posts/post", collection: @posts %>
|
|
128
129
|
# <%= render partial: @posts %>
|
|
129
130
|
#
|
|
130
|
-
# == Rendering the default case
|
|
131
|
+
# == \Rendering the default case
|
|
131
132
|
#
|
|
132
133
|
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
|
|
133
134
|
# defaults of render to render partials. Examples:
|
|
@@ -147,29 +148,29 @@ module ActionView
|
|
|
147
148
|
# # <%= render partial: "posts/post", collection: @posts %>
|
|
148
149
|
# <%= render @posts %>
|
|
149
150
|
#
|
|
150
|
-
# == Rendering partials with layouts
|
|
151
|
+
# == \Rendering partials with layouts
|
|
151
152
|
#
|
|
152
153
|
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
|
|
153
154
|
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
|
|
154
155
|
# of users:
|
|
155
156
|
#
|
|
156
|
-
# <%# app/views/users/index.html.erb
|
|
157
|
+
# <%# app/views/users/index.html.erb %>
|
|
157
158
|
# Here's the administrator:
|
|
158
159
|
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
|
|
159
160
|
#
|
|
160
161
|
# Here's the editor:
|
|
161
162
|
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
|
|
162
163
|
#
|
|
163
|
-
# <%# app/views/users/_user.html.erb
|
|
164
|
+
# <%# app/views/users/_user.html.erb %>
|
|
164
165
|
# Name: <%= user.name %>
|
|
165
166
|
#
|
|
166
|
-
# <%# app/views/users/_administrator.html.erb
|
|
167
|
+
# <%# app/views/users/_administrator.html.erb %>
|
|
167
168
|
# <div id="administrator">
|
|
168
169
|
# Budget: $<%= user.budget %>
|
|
169
170
|
# <%= yield %>
|
|
170
171
|
# </div>
|
|
171
172
|
#
|
|
172
|
-
# <%# app/views/users/_editor.html.erb
|
|
173
|
+
# <%# app/views/users/_editor.html.erb %>
|
|
173
174
|
# <div id="editor">
|
|
174
175
|
# Deadline: <%= user.deadline %>
|
|
175
176
|
# <%= yield %>
|
|
@@ -232,7 +233,7 @@ module ActionView
|
|
|
232
233
|
#
|
|
233
234
|
# You can also apply a layout to a block within any template:
|
|
234
235
|
#
|
|
235
|
-
# <%# app/views/users/_chief.html.erb
|
|
236
|
+
# <%# app/views/users/_chief.html.erb %>
|
|
236
237
|
# <%= render(layout: "administrator", locals: { user: chief }) do %>
|
|
237
238
|
# Title: <%= chief.title %>
|
|
238
239
|
# <% end %>
|
|
@@ -249,13 +250,13 @@ module ActionView
|
|
|
249
250
|
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
|
|
250
251
|
# an array to layout and treat it as an enumerable.
|
|
251
252
|
#
|
|
252
|
-
# <%# app/views/users/_user.html.erb
|
|
253
|
+
# <%# app/views/users/_user.html.erb %>
|
|
253
254
|
# <div class="user">
|
|
254
255
|
# Budget: $<%= user.budget %>
|
|
255
256
|
# <%= yield user %>
|
|
256
257
|
# </div>
|
|
257
258
|
#
|
|
258
|
-
# <%# app/views/users/index.html.erb
|
|
259
|
+
# <%# app/views/users/index.html.erb %>
|
|
259
260
|
# <%= render layout: @users do |user| %>
|
|
260
261
|
# Title: <%= user.title %>
|
|
261
262
|
# <% end %>
|
|
@@ -264,14 +265,14 @@ module ActionView
|
|
|
264
265
|
#
|
|
265
266
|
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
|
|
266
267
|
#
|
|
267
|
-
# <%# app/views/users/_user.html.erb
|
|
268
|
+
# <%# app/views/users/_user.html.erb %>
|
|
268
269
|
# <div class="user">
|
|
269
270
|
# <%= yield user, :header %>
|
|
270
271
|
# Budget: $<%= user.budget %>
|
|
271
272
|
# <%= yield user, :footer %>
|
|
272
273
|
# </div>
|
|
273
274
|
#
|
|
274
|
-
# <%# app/views/users/index.html.erb
|
|
275
|
+
# <%# app/views/users/index.html.erb %>
|
|
275
276
|
# <%= render layout: @users do |user, section| %>
|
|
276
277
|
# <%- case section when :header -%>
|
|
277
278
|
# Title: <%= user.title %>
|
|
@@ -280,8 +281,10 @@ module ActionView
|
|
|
280
281
|
# <%- end -%>
|
|
281
282
|
# <% end %>
|
|
282
283
|
class PartialRenderer < AbstractRenderer
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
include CollectionCaching
|
|
285
|
+
|
|
286
|
+
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
|
|
287
|
+
h[k] = Concurrent::Map.new
|
|
285
288
|
end
|
|
286
289
|
|
|
287
290
|
def initialize(*)
|
|
@@ -291,7 +294,7 @@ module ActionView
|
|
|
291
294
|
|
|
292
295
|
def render(context, options, block)
|
|
293
296
|
setup(context, options, block)
|
|
294
|
-
|
|
297
|
+
@template = find_partial
|
|
295
298
|
|
|
296
299
|
@lookup_context.rendered_format ||= begin
|
|
297
300
|
if @template && @template.formats.present?
|
|
@@ -302,247 +305,246 @@ module ActionView
|
|
|
302
305
|
end
|
|
303
306
|
|
|
304
307
|
if @collection
|
|
305
|
-
|
|
306
|
-
render_collection
|
|
307
|
-
end
|
|
308
|
+
render_collection
|
|
308
309
|
else
|
|
309
|
-
|
|
310
|
-
render_partial
|
|
311
|
-
end
|
|
310
|
+
render_partial
|
|
312
311
|
end
|
|
313
312
|
end
|
|
314
313
|
|
|
315
314
|
private
|
|
316
315
|
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
def render_collection
|
|
317
|
+
instrument(:collection, count: @collection.size) do |payload|
|
|
318
|
+
return nil if @collection.blank?
|
|
319
319
|
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
if @options.key?(:spacer_template)
|
|
321
|
+
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
cache_collection_render(payload) do
|
|
325
|
+
@template ? collection_with_template : collection_without_template
|
|
326
|
+
end.join(spacer).html_safe
|
|
327
|
+
end
|
|
322
328
|
end
|
|
323
329
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
def render_partial
|
|
331
|
+
instrument(:partial) do |payload|
|
|
332
|
+
view, locals, block = @view, @locals, @block
|
|
333
|
+
object, as = @object, @variable
|
|
327
334
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
if !block && (layout = @options[:layout])
|
|
336
|
+
layout = find_template(layout.to_s, @template_keys)
|
|
337
|
+
end
|
|
331
338
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
end
|
|
339
|
+
object = locals[as] if object.nil? # Respect object when object is false
|
|
340
|
+
locals[as] = object if @has_object
|
|
335
341
|
|
|
336
|
-
|
|
337
|
-
|
|
342
|
+
content = @template.render(view, locals) do |*name|
|
|
343
|
+
view._layout_for(*name, &block)
|
|
344
|
+
end
|
|
338
345
|
|
|
339
|
-
|
|
340
|
-
|
|
346
|
+
content = layout.render(view, locals) { content } if layout
|
|
347
|
+
payload[:cache_hit] = view.cache_hit
|
|
348
|
+
content
|
|
349
|
+
end
|
|
341
350
|
end
|
|
342
351
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
352
|
+
# Sets up instance variables needed for rendering a partial. This method
|
|
353
|
+
# finds the options and details and extracts them. The method also contains
|
|
354
|
+
# logic that handles the type of object passed in as the partial.
|
|
355
|
+
#
|
|
356
|
+
# If +options[:partial]+ is a string, then the +@path+ instance variable is
|
|
357
|
+
# set to that string. Otherwise, the +options[:partial]+ object must
|
|
358
|
+
# respond to +to_partial_path+ in order to setup the path.
|
|
359
|
+
def setup(context, options, block)
|
|
360
|
+
@view = context
|
|
361
|
+
@options = options
|
|
362
|
+
@block = block
|
|
363
|
+
|
|
364
|
+
@locals = options[:locals] || {}
|
|
365
|
+
@details = extract_details(options)
|
|
366
|
+
|
|
367
|
+
prepend_formats(options[:formats])
|
|
368
|
+
|
|
369
|
+
partial = options[:partial]
|
|
370
|
+
|
|
371
|
+
if String === partial
|
|
372
|
+
@has_object = options.key?(:object)
|
|
373
|
+
@object = options[:object]
|
|
374
|
+
@collection = collection_from_options
|
|
375
|
+
@path = partial
|
|
376
|
+
else
|
|
377
|
+
@has_object = true
|
|
378
|
+
@object = partial
|
|
379
|
+
@collection = collection_from_object || collection_from_options
|
|
380
|
+
|
|
381
|
+
if @collection
|
|
382
|
+
paths = @collection_data = @collection.map { |o| partial_path(o) }
|
|
383
|
+
@path = paths.uniq.one? ? paths.first : nil
|
|
384
|
+
else
|
|
385
|
+
@path = partial_path
|
|
386
|
+
end
|
|
387
|
+
end
|
|
348
388
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
# If +options[:partial]+ is a string, then the +@path+ instance variable is
|
|
354
|
-
# set to that string. Otherwise, the +options[:partial]+ object must
|
|
355
|
-
# respond to +to_partial_path+ in order to setup the path.
|
|
356
|
-
def setup(context, options, block)
|
|
357
|
-
@view = context
|
|
358
|
-
@options = options
|
|
359
|
-
@block = block
|
|
360
|
-
|
|
361
|
-
@locals = options[:locals] || {}
|
|
362
|
-
@details = extract_details(options)
|
|
363
|
-
|
|
364
|
-
prepend_formats(options[:formats])
|
|
365
|
-
|
|
366
|
-
partial = options[:partial]
|
|
367
|
-
|
|
368
|
-
if String === partial
|
|
369
|
-
@has_object = options.key?(:object)
|
|
370
|
-
@object = options[:object]
|
|
371
|
-
@collection = collection_from_options
|
|
372
|
-
@path = partial
|
|
373
|
-
else
|
|
374
|
-
@has_object = true
|
|
375
|
-
@object = partial
|
|
376
|
-
@collection = collection_from_object || collection_from_options
|
|
389
|
+
if as = options[:as]
|
|
390
|
+
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
|
|
391
|
+
as = as.to_sym
|
|
392
|
+
end
|
|
377
393
|
|
|
378
|
-
if @
|
|
379
|
-
|
|
380
|
-
@
|
|
394
|
+
if @path
|
|
395
|
+
@variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
|
|
396
|
+
@template_keys = retrieve_template_keys
|
|
381
397
|
else
|
|
382
|
-
|
|
398
|
+
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
|
|
383
399
|
end
|
|
384
|
-
end
|
|
385
400
|
|
|
386
|
-
|
|
387
|
-
raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
|
|
388
|
-
as = as.to_sym
|
|
401
|
+
self
|
|
389
402
|
end
|
|
390
403
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
def collection_from_options
|
|
405
|
+
if @options.key?(:collection)
|
|
406
|
+
collection = @options[:collection]
|
|
407
|
+
collection ? collection.to_a : []
|
|
408
|
+
end
|
|
396
409
|
end
|
|
397
410
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def collection_from_options
|
|
402
|
-
if @options.key?(:collection)
|
|
403
|
-
collection = @options[:collection]
|
|
404
|
-
collection.respond_to?(:to_ary) ? collection.to_ary : []
|
|
411
|
+
def collection_from_object
|
|
412
|
+
@object.to_ary if @object.respond_to?(:to_ary)
|
|
405
413
|
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def collection_from_object
|
|
409
|
-
@object.to_ary if @object.respond_to?(:to_ary)
|
|
410
|
-
end
|
|
411
414
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
+
def find_partial
|
|
416
|
+
find_template(@path, @template_keys) if @path
|
|
417
|
+
end
|
|
415
418
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
419
|
+
def find_template(path, locals)
|
|
420
|
+
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
|
|
421
|
+
@lookup_context.find_template(path, prefixes, true, locals, @details)
|
|
422
|
+
end
|
|
420
423
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
+
def collection_with_template
|
|
425
|
+
view, locals, template = @view, @locals, @template
|
|
426
|
+
as, counter, iteration = @variable, @variable_counter, @variable_iteration
|
|
424
427
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
+
if layout = @options[:layout]
|
|
429
|
+
layout = find_template(layout, @template_keys)
|
|
430
|
+
end
|
|
428
431
|
|
|
429
|
-
|
|
430
|
-
|
|
432
|
+
partial_iteration = PartialIteration.new(@collection.size)
|
|
433
|
+
locals[iteration] = partial_iteration
|
|
431
434
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
+
@collection.map do |object|
|
|
436
|
+
locals[as] = object
|
|
437
|
+
locals[counter] = partial_iteration.index
|
|
435
438
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
439
|
+
content = template.render(view, locals)
|
|
440
|
+
content = layout.render(view, locals) { content } if layout
|
|
441
|
+
partial_iteration.iterate!
|
|
442
|
+
content
|
|
443
|
+
end
|
|
440
444
|
end
|
|
441
|
-
end
|
|
442
445
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
446
|
+
def collection_without_template
|
|
447
|
+
view, locals, collection_data = @view, @locals, @collection_data
|
|
448
|
+
cache = {}
|
|
449
|
+
keys = @locals.keys
|
|
447
450
|
|
|
448
|
-
|
|
451
|
+
partial_iteration = PartialIteration.new(@collection.size)
|
|
449
452
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
@collection.map do |object|
|
|
454
|
+
index = partial_iteration.index
|
|
455
|
+
path, as, counter, iteration = collection_data[index]
|
|
453
456
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
+
locals[as] = object
|
|
458
|
+
locals[counter] = index
|
|
459
|
+
locals[iteration] = partial_iteration
|
|
457
460
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
461
|
+
template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
|
|
462
|
+
content = template.render(view, locals)
|
|
463
|
+
partial_iteration.iterate!
|
|
464
|
+
content
|
|
465
|
+
end
|
|
462
466
|
end
|
|
463
|
-
end
|
|
464
467
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
468
|
+
# Obtains the path to where the object's partial is located. If the object
|
|
469
|
+
# responds to +to_partial_path+, then +to_partial_path+ will be called and
|
|
470
|
+
# will provide the path. If the object does not respond to +to_partial_path+,
|
|
471
|
+
# then an +ArgumentError+ is raised.
|
|
472
|
+
#
|
|
473
|
+
# If +prefix_partial_path_with_controller_namespace+ is true, then this
|
|
474
|
+
# method will prefix the partial paths with a namespace.
|
|
475
|
+
def partial_path(object = @object)
|
|
476
|
+
object = object.to_model if object.respond_to?(:to_model)
|
|
477
|
+
|
|
478
|
+
path = if object.respond_to?(:to_partial_path)
|
|
479
|
+
object.to_partial_path
|
|
480
|
+
else
|
|
481
|
+
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
if @view.prefix_partial_path_with_controller_namespace
|
|
485
|
+
prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
|
|
486
|
+
else
|
|
487
|
+
path
|
|
488
|
+
end
|
|
479
489
|
end
|
|
480
490
|
|
|
481
|
-
|
|
482
|
-
prefixed_partial_names
|
|
483
|
-
else
|
|
484
|
-
path
|
|
491
|
+
def prefixed_partial_names
|
|
492
|
+
@prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
|
|
485
493
|
end
|
|
486
|
-
end
|
|
487
494
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
495
|
+
def merge_prefix_into_object_path(prefix, object_path)
|
|
496
|
+
if prefix.include?(?/) && object_path.include?(?/)
|
|
497
|
+
prefixes = []
|
|
498
|
+
prefix_array = File.dirname(prefix).split("/")
|
|
499
|
+
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
|
|
491
500
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
|
|
501
|
+
prefix_array.each_with_index do |dir, index|
|
|
502
|
+
break if dir == object_path_array[index]
|
|
503
|
+
prefixes << dir
|
|
504
|
+
end
|
|
497
505
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
506
|
+
(prefixes << object_path).join("/")
|
|
507
|
+
else
|
|
508
|
+
object_path
|
|
501
509
|
end
|
|
502
|
-
|
|
503
|
-
(prefixes << object_path).join("/")
|
|
504
|
-
else
|
|
505
|
-
object_path
|
|
506
510
|
end
|
|
507
|
-
end
|
|
508
511
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
512
|
+
def retrieve_template_keys
|
|
513
|
+
keys = @locals.keys
|
|
514
|
+
keys << @variable if @has_object || @collection
|
|
515
|
+
if @collection
|
|
516
|
+
keys << @variable_counter
|
|
517
|
+
keys << @variable_iteration
|
|
518
|
+
end
|
|
519
|
+
keys
|
|
515
520
|
end
|
|
516
|
-
keys
|
|
517
|
-
end
|
|
518
521
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
522
|
+
def retrieve_variable(path, as)
|
|
523
|
+
variable = as || begin
|
|
524
|
+
base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
|
|
525
|
+
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
|
|
526
|
+
$1.to_sym
|
|
527
|
+
end
|
|
528
|
+
if @collection
|
|
529
|
+
variable_counter = :"#{variable}_counter"
|
|
530
|
+
variable_iteration = :"#{variable}_iteration"
|
|
531
|
+
end
|
|
532
|
+
[variable, variable_counter, variable_iteration]
|
|
528
533
|
end
|
|
529
|
-
[variable, variable_counter, variable_iteration]
|
|
530
|
-
end
|
|
531
534
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
"and is followed by any combination of letters, numbers and underscores."
|
|
535
|
+
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
|
|
536
|
+
"make sure your partial name starts with underscore."
|
|
535
537
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
538
|
+
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
|
|
539
|
+
"make sure it starts with lowercase letter, " \
|
|
540
|
+
"and is followed by any combination of letters, numbers and underscores."
|
|
539
541
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
542
|
+
def raise_invalid_identifier(path)
|
|
543
|
+
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
|
|
544
|
+
end
|
|
543
545
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
546
|
+
def raise_invalid_option_as(as)
|
|
547
|
+
raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
|
|
548
|
+
end
|
|
547
549
|
end
|
|
548
550
|
end
|
|
@@ -15,12 +15,8 @@ module ActionView
|
|
|
15
15
|
@lookup_context = lookup_context
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Main render entry point shared by
|
|
18
|
+
# Main render entry point shared by Action View and Action Controller.
|
|
19
19
|
def render(context, options)
|
|
20
|
-
if options.respond_to?(:permitted?) && !options.permitted?
|
|
21
|
-
raise ArgumentError, "render parameters are not permitted"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
if options.key?(:partial)
|
|
25
21
|
render_partial(context, options)
|
|
26
22
|
else
|
|
@@ -41,7 +37,7 @@ module ActionView
|
|
|
41
37
|
end
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
# Direct
|
|
40
|
+
# Direct access to template rendering.
|
|
45
41
|
def render_template(context, options) #:nodoc:
|
|
46
42
|
TemplateRenderer.new(@lookup_context).render(context, options)
|
|
47
43
|
end
|