actionview 4.2.11.1 → 6.0.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +201 -192
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +144 -37
- data/lib/action_view/buffers.rb +18 -1
- data/lib/action_view/cache_expiry.rb +53 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +54 -20
- data/lib/action_view/digestor.rb +88 -85
- data/lib/action_view/flows.rb +11 -12
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
- data/lib/action_view/helpers/asset_url_helper.rb +171 -67
- data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
- data/lib/action_view/helpers/cache_helper.rb +112 -42
- data/lib/action_view/helpers/capture_helper.rb +20 -13
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +230 -129
- data/lib/action_view/helpers/debug_helper.rb +7 -6
- data/lib/action_view/helpers/form_helper.rb +755 -129
- data/lib/action_view/helpers/form_options_helper.rb +130 -75
- data/lib/action_view/helpers/form_tag_helper.rb +116 -71
- data/lib/action_view/helpers/javascript_helper.rb +30 -14
- data/lib/action_view/helpers/number_helper.rb +84 -59
- data/lib/action_view/helpers/output_safety_helper.rb +36 -4
- data/lib/action_view/helpers/rendering_helper.rb +11 -8
- data/lib/action_view/helpers/sanitize_helper.rb +30 -31
- data/lib/action_view/helpers/tag_helper.rb +201 -75
- data/lib/action_view/helpers/tags/base.rb +138 -98
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +2 -1
- data/lib/action_view/helpers/tags/date_select.rb +37 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +3 -2
- data/lib/action_view/helpers/tags/month_field.rb +2 -1
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +14 -9
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +4 -2
- data/lib/action_view/helpers/tags/text_field.rb +8 -8
- data/lib/action_view/helpers/tags/time_field.rb +2 -1
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +15 -16
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +2 -1
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/text_helper.rb +56 -38
- data/lib/action_view/helpers/translation_helper.rb +91 -47
- data/lib/action_view/helpers/url_helper.rb +160 -105
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +65 -61
- data/lib/action_view/log_subscriber.rb +61 -10
- data/lib/action_view/lookup_context.rb +147 -89
- data/lib/action_view/model_naming.rb +3 -1
- data/lib/action_view/path_set.rb +28 -23
- data/lib/action_view/railtie.rb +62 -6
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +71 -13
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
- data/lib/action_view/renderer/partial_renderer.rb +239 -225
- data/lib/action_view/renderer/renderer.rb +22 -8
- data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
- data/lib/action_view/renderer/template_renderer.rb +79 -73
- data/lib/action_view/rendering.rb +68 -44
- data/lib/action_view/routing_url_for.rb +33 -22
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
- data/lib/action_view/template/handlers/erb.rb +24 -86
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +38 -8
- data/lib/action_view/template/html.rb +19 -10
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +217 -193
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +11 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +146 -90
- data/lib/action_view/test_case.rb +52 -32
- data/lib/action_view/testing/resolvers.rb +46 -34
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +48 -31
- data/lib/action_view.rb +11 -8
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +38 -29
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
4
|
+
require "action_view/renderer/partial_renderer/collection_caching"
|
2
5
|
|
3
6
|
module ActionView
|
4
7
|
class PartialIteration
|
@@ -49,12 +52,12 @@ module ActionView
|
|
49
52
|
# <%= render partial: "ad", locals: { ad: ad } %>
|
50
53
|
# <% end %>
|
51
54
|
#
|
52
|
-
# This would first render
|
53
|
-
# render
|
55
|
+
# This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
|
56
|
+
# render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
|
54
57
|
#
|
55
58
|
# == The :as and :object options
|
56
59
|
#
|
57
|
-
# By default
|
60
|
+
# By default ActionView::PartialRenderer doesn't have any local variables.
|
58
61
|
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
|
59
62
|
#
|
60
63
|
# <%= render partial: "account", object: @buyer %>
|
@@ -73,7 +76,7 @@ module ActionView
|
|
73
76
|
#
|
74
77
|
# <%= render partial: "account", locals: { user: @buyer } %>
|
75
78
|
#
|
76
|
-
# == Rendering a collection of partials
|
79
|
+
# == \Rendering a collection of partials
|
77
80
|
#
|
78
81
|
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
|
79
82
|
# render a sub template for each of the elements. This pattern has been implemented as a single method that
|
@@ -82,7 +85,7 @@ module ActionView
|
|
82
85
|
#
|
83
86
|
# <%= render partial: "ad", collection: @advertisements %>
|
84
87
|
#
|
85
|
-
# This will render
|
88
|
+
# This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
|
86
89
|
# iteration object will automatically be made available to the template with a name of the form
|
87
90
|
# +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
|
88
91
|
# the collection and the total size of the collection. The iteration object also has two convenience methods,
|
@@ -97,37 +100,34 @@ module ActionView
|
|
97
100
|
#
|
98
101
|
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
|
99
102
|
#
|
100
|
-
# If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil
|
101
|
-
# to specify a text which will displayed instead by using this form:
|
103
|
+
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
|
104
|
+
# to specify a text which will be displayed instead by using this form:
|
102
105
|
#
|
103
106
|
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
|
104
107
|
#
|
105
|
-
#
|
106
|
-
# just keep domain objects, like Active Records, in there.
|
107
|
-
#
|
108
|
-
# == Rendering shared partials
|
108
|
+
# == \Rendering shared partials
|
109
109
|
#
|
110
110
|
# Two controllers can share a set of partials and render them like this:
|
111
111
|
#
|
112
112
|
# <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
|
113
113
|
#
|
114
|
-
# This will render the partial
|
114
|
+
# This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
|
115
115
|
#
|
116
|
-
# == Rendering objects that respond to
|
116
|
+
# == \Rendering objects that respond to +to_partial_path+
|
117
117
|
#
|
118
118
|
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
|
119
|
-
# and pick the proper path by checking
|
119
|
+
# and pick the proper path by checking +to_partial_path+ method.
|
120
120
|
#
|
121
121
|
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
|
122
122
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
123
123
|
# <%= render partial: @account %>
|
124
124
|
#
|
125
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
125
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
|
126
126
|
# # that's why we can replace:
|
127
127
|
# # <%= render partial: "posts/post", collection: @posts %>
|
128
128
|
# <%= render partial: @posts %>
|
129
129
|
#
|
130
|
-
# == Rendering the default case
|
130
|
+
# == \Rendering the default case
|
131
131
|
#
|
132
132
|
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
|
133
133
|
# defaults of render to render partials. Examples:
|
@@ -142,34 +142,34 @@ module ActionView
|
|
142
142
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
143
143
|
# <%= render @account %>
|
144
144
|
#
|
145
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
145
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
|
146
146
|
# # that's why we can replace:
|
147
147
|
# # <%= render partial: "posts/post", collection: @posts %>
|
148
148
|
# <%= render @posts %>
|
149
149
|
#
|
150
|
-
# == Rendering partials with layouts
|
150
|
+
# == \Rendering partials with layouts
|
151
151
|
#
|
152
152
|
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
|
153
153
|
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
|
154
154
|
# of users:
|
155
155
|
#
|
156
|
-
# <%# app/views/users/index.html.erb
|
156
|
+
# <%# app/views/users/index.html.erb %>
|
157
157
|
# Here's the administrator:
|
158
158
|
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
|
159
159
|
#
|
160
160
|
# Here's the editor:
|
161
161
|
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
|
162
162
|
#
|
163
|
-
# <%# app/views/users/_user.html.erb
|
163
|
+
# <%# app/views/users/_user.html.erb %>
|
164
164
|
# Name: <%= user.name %>
|
165
165
|
#
|
166
|
-
# <%# app/views/users/_administrator.html.erb
|
166
|
+
# <%# app/views/users/_administrator.html.erb %>
|
167
167
|
# <div id="administrator">
|
168
168
|
# Budget: $<%= user.budget %>
|
169
169
|
# <%= yield %>
|
170
170
|
# </div>
|
171
171
|
#
|
172
|
-
# <%# app/views/users/_editor.html.erb
|
172
|
+
# <%# app/views/users/_editor.html.erb %>
|
173
173
|
# <div id="editor">
|
174
174
|
# Deadline: <%= user.deadline %>
|
175
175
|
# <%= yield %>
|
@@ -232,7 +232,7 @@ module ActionView
|
|
232
232
|
#
|
233
233
|
# You can also apply a layout to a block within any template:
|
234
234
|
#
|
235
|
-
# <%# app/views/users/_chief.html.erb
|
235
|
+
# <%# app/views/users/_chief.html.erb %>
|
236
236
|
# <%= render(layout: "administrator", locals: { user: chief }) do %>
|
237
237
|
# Title: <%= chief.title %>
|
238
238
|
# <% end %>
|
@@ -249,13 +249,13 @@ module ActionView
|
|
249
249
|
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
|
250
250
|
# an array to layout and treat it as an enumerable.
|
251
251
|
#
|
252
|
-
# <%# app/views/users/_user.html.erb
|
252
|
+
# <%# app/views/users/_user.html.erb %>
|
253
253
|
# <div class="user">
|
254
254
|
# Budget: $<%= user.budget %>
|
255
255
|
# <%= yield user %>
|
256
256
|
# </div>
|
257
257
|
#
|
258
|
-
# <%# app/views/users/index.html.erb
|
258
|
+
# <%# app/views/users/index.html.erb %>
|
259
259
|
# <%= render layout: @users do |user| %>
|
260
260
|
# Title: <%= user.title %>
|
261
261
|
# <% end %>
|
@@ -264,14 +264,14 @@ module ActionView
|
|
264
264
|
#
|
265
265
|
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
|
266
266
|
#
|
267
|
-
# <%# app/views/users/_user.html.erb
|
267
|
+
# <%# app/views/users/_user.html.erb %>
|
268
268
|
# <div class="user">
|
269
269
|
# <%= yield user, :header %>
|
270
270
|
# Budget: $<%= user.budget %>
|
271
271
|
# <%= yield user, :footer %>
|
272
272
|
# </div>
|
273
273
|
#
|
274
|
-
# <%# app/views/users/index.html.erb
|
274
|
+
# <%# app/views/users/index.html.erb %>
|
275
275
|
# <%= render layout: @users do |user, section| %>
|
276
276
|
# <%- case section when :header -%>
|
277
277
|
# Title: <%= user.title %>
|
@@ -280,8 +280,10 @@ module ActionView
|
|
280
280
|
# <%- end -%>
|
281
281
|
# <% end %>
|
282
282
|
class PartialRenderer < AbstractRenderer
|
283
|
-
|
284
|
-
|
283
|
+
include CollectionCaching
|
284
|
+
|
285
|
+
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
|
286
|
+
h[k] = Concurrent::Map.new
|
285
287
|
end
|
286
288
|
|
287
289
|
def initialize(*)
|
@@ -290,259 +292,271 @@ module ActionView
|
|
290
292
|
end
|
291
293
|
|
292
294
|
def render(context, options, block)
|
293
|
-
|
294
|
-
|
295
|
+
as = as_variable(options)
|
296
|
+
setup(context, options, as, block)
|
295
297
|
|
296
|
-
@
|
297
|
-
if @
|
298
|
-
@
|
298
|
+
if @path
|
299
|
+
if @has_object || @collection
|
300
|
+
@variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
|
301
|
+
@template_keys = retrieve_template_keys(@variable)
|
299
302
|
else
|
300
|
-
|
303
|
+
@template_keys = @locals.keys
|
301
304
|
end
|
305
|
+
template = find_partial(@path, @template_keys)
|
306
|
+
@variable ||= template.variable
|
307
|
+
else
|
308
|
+
if options[:cached]
|
309
|
+
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
|
310
|
+
end
|
311
|
+
template = nil
|
302
312
|
end
|
303
313
|
|
304
314
|
if @collection
|
305
|
-
|
306
|
-
render_collection
|
307
|
-
end
|
315
|
+
render_collection(context, template)
|
308
316
|
else
|
309
|
-
|
310
|
-
render_partial
|
311
|
-
end
|
317
|
+
render_partial(context, template)
|
312
318
|
end
|
313
319
|
end
|
314
320
|
|
315
321
|
private
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
+
def render_collection(view, template)
|
323
|
+
identifier = (template && template.identifier) || @path
|
324
|
+
instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
|
325
|
+
return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
|
326
|
+
|
327
|
+
spacer = if @options.key?(:spacer_template)
|
328
|
+
spacer_template = find_template(@options[:spacer_template], @locals.keys)
|
329
|
+
build_rendered_template(spacer_template.render(view, @locals), spacer_template)
|
330
|
+
else
|
331
|
+
RenderedTemplate::EMPTY_SPACER
|
332
|
+
end
|
333
|
+
|
334
|
+
collection_body = if template
|
335
|
+
cache_collection_render(payload, view, template) do
|
336
|
+
collection_with_template(view, template)
|
337
|
+
end
|
338
|
+
else
|
339
|
+
collection_without_template(view)
|
340
|
+
end
|
341
|
+
build_rendered_collection(collection_body, spacer)
|
342
|
+
end
|
322
343
|
end
|
323
344
|
|
324
|
-
|
325
|
-
|
326
|
-
|
345
|
+
def render_partial(view, template)
|
346
|
+
instrument(:partial, identifier: template.identifier) do |payload|
|
347
|
+
locals, block = @locals, @block
|
348
|
+
object, as = @object, @variable
|
327
349
|
|
328
|
-
|
329
|
-
|
330
|
-
|
350
|
+
if !block && (layout = @options[:layout])
|
351
|
+
layout = find_template(layout.to_s, @template_keys)
|
352
|
+
end
|
331
353
|
|
332
|
-
|
333
|
-
|
334
|
-
end
|
354
|
+
object = locals[as] if object.nil? # Respect object when object is false
|
355
|
+
locals[as] = object if @has_object
|
335
356
|
|
336
|
-
|
337
|
-
|
357
|
+
content = template.render(view, locals) do |*name|
|
358
|
+
view._layout_for(*name, &block)
|
359
|
+
end
|
338
360
|
|
339
|
-
|
340
|
-
|
361
|
+
content = layout.render(view, locals) { content } if layout
|
362
|
+
payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
|
363
|
+
build_rendered_template(content, template, layout)
|
364
|
+
end
|
341
365
|
end
|
342
366
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
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
|
377
|
-
|
378
|
-
if @collection
|
379
|
-
paths = @collection_data = @collection.map { |o| partial_path(o) }
|
380
|
-
@path = paths.uniq.one? ? paths.first : nil
|
367
|
+
# Sets up instance variables needed for rendering a partial. This method
|
368
|
+
# finds the options and details and extracts them. The method also contains
|
369
|
+
# logic that handles the type of object passed in as the partial.
|
370
|
+
#
|
371
|
+
# If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
|
372
|
+
# set to that string. Otherwise, the +options[:partial]+ object must
|
373
|
+
# respond to +to_partial_path+ in order to setup the path.
|
374
|
+
def setup(context, options, as, block)
|
375
|
+
@options = options
|
376
|
+
@block = block
|
377
|
+
|
378
|
+
@locals = options[:locals] || {}
|
379
|
+
@details = extract_details(options)
|
380
|
+
|
381
|
+
partial = options[:partial]
|
382
|
+
|
383
|
+
if String === partial
|
384
|
+
@has_object = options.key?(:object)
|
385
|
+
@object = options[:object]
|
386
|
+
@collection = collection_from_options
|
387
|
+
@path = partial
|
381
388
|
else
|
382
|
-
@
|
389
|
+
@has_object = true
|
390
|
+
@object = partial
|
391
|
+
@collection = collection_from_object || collection_from_options
|
392
|
+
|
393
|
+
if @collection
|
394
|
+
paths = @collection_data = @collection.map { |o| partial_path(o, context) }
|
395
|
+
if paths.uniq.length == 1
|
396
|
+
@path = paths.first
|
397
|
+
else
|
398
|
+
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
|
399
|
+
@path = nil
|
400
|
+
end
|
401
|
+
else
|
402
|
+
@path = partial_path(@object, context)
|
403
|
+
end
|
383
404
|
end
|
384
|
-
end
|
385
405
|
|
386
|
-
|
387
|
-
raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
|
388
|
-
as = as.to_sym
|
406
|
+
self
|
389
407
|
end
|
390
408
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
409
|
+
def as_variable(options)
|
410
|
+
if as = options[:as]
|
411
|
+
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
|
412
|
+
as.to_sym
|
413
|
+
end
|
396
414
|
end
|
397
415
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
collection = @options[:collection]
|
404
|
-
collection.respond_to?(:to_ary) ? collection.to_ary : []
|
416
|
+
def collection_from_options
|
417
|
+
if @options.key?(:collection)
|
418
|
+
collection = @options[:collection]
|
419
|
+
collection ? collection.to_a : []
|
420
|
+
end
|
405
421
|
end
|
406
|
-
end
|
407
422
|
|
408
|
-
|
409
|
-
|
410
|
-
|
423
|
+
def collection_from_object
|
424
|
+
@object.to_ary if @object.respond_to?(:to_ary)
|
425
|
+
end
|
411
426
|
|
412
|
-
|
413
|
-
|
414
|
-
|
427
|
+
def find_partial(path, template_keys)
|
428
|
+
find_template(path, template_keys)
|
429
|
+
end
|
415
430
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
431
|
+
def find_template(path, locals)
|
432
|
+
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
|
433
|
+
@lookup_context.find_template(path, prefixes, true, locals, @details)
|
434
|
+
end
|
420
435
|
|
421
|
-
|
422
|
-
|
423
|
-
|
436
|
+
def collection_with_template(view, template)
|
437
|
+
locals = @locals
|
438
|
+
as, counter, iteration = @variable, @variable_counter, @variable_iteration
|
424
439
|
|
425
|
-
|
426
|
-
|
427
|
-
|
440
|
+
if layout = @options[:layout]
|
441
|
+
layout = find_template(layout, @template_keys)
|
442
|
+
end
|
428
443
|
|
429
|
-
|
430
|
-
|
444
|
+
partial_iteration = PartialIteration.new(@collection.size)
|
445
|
+
locals[iteration] = partial_iteration
|
431
446
|
|
432
|
-
|
433
|
-
|
434
|
-
|
447
|
+
@collection.map do |object|
|
448
|
+
locals[as] = object
|
449
|
+
locals[counter] = partial_iteration.index
|
435
450
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
451
|
+
content = template.render(view, locals)
|
452
|
+
content = layout.render(view, locals) { content } if layout
|
453
|
+
partial_iteration.iterate!
|
454
|
+
build_rendered_template(content, template, layout)
|
455
|
+
end
|
440
456
|
end
|
441
|
-
end
|
442
457
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
458
|
+
def collection_without_template(view)
|
459
|
+
locals, collection_data = @locals, @collection_data
|
460
|
+
cache = {}
|
461
|
+
keys = @locals.keys
|
447
462
|
|
448
|
-
|
463
|
+
partial_iteration = PartialIteration.new(@collection.size)
|
449
464
|
|
450
|
-
|
451
|
-
|
452
|
-
|
465
|
+
@collection.map do |object|
|
466
|
+
index = partial_iteration.index
|
467
|
+
path, as, counter, iteration = collection_data[index]
|
453
468
|
|
454
|
-
|
455
|
-
|
456
|
-
|
469
|
+
locals[as] = object
|
470
|
+
locals[counter] = index
|
471
|
+
locals[iteration] = partial_iteration
|
457
472
|
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
473
|
+
template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
|
474
|
+
content = template.render(view, locals)
|
475
|
+
partial_iteration.iterate!
|
476
|
+
build_rendered_template(content, template)
|
477
|
+
end
|
462
478
|
end
|
463
|
-
end
|
464
479
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
480
|
+
# Obtains the path to where the object's partial is located. If the object
|
481
|
+
# responds to +to_partial_path+, then +to_partial_path+ will be called and
|
482
|
+
# will provide the path. If the object does not respond to +to_partial_path+,
|
483
|
+
# then an +ArgumentError+ is raised.
|
484
|
+
#
|
485
|
+
# If +prefix_partial_path_with_controller_namespace+ is true, then this
|
486
|
+
# method will prefix the partial paths with a namespace.
|
487
|
+
def partial_path(object, view)
|
488
|
+
object = object.to_model if object.respond_to?(:to_model)
|
489
|
+
|
490
|
+
path = if object.respond_to?(:to_partial_path)
|
491
|
+
object.to_partial_path
|
492
|
+
else
|
493
|
+
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
|
494
|
+
end
|
495
|
+
|
496
|
+
if view.prefix_partial_path_with_controller_namespace
|
497
|
+
prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
|
498
|
+
else
|
499
|
+
path
|
500
|
+
end
|
479
501
|
end
|
480
502
|
|
481
|
-
|
482
|
-
prefixed_partial_names
|
483
|
-
else
|
484
|
-
path
|
503
|
+
def prefixed_partial_names
|
504
|
+
@prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
|
485
505
|
end
|
486
|
-
end
|
487
506
|
|
488
|
-
|
489
|
-
|
490
|
-
|
507
|
+
def merge_prefix_into_object_path(prefix, object_path)
|
508
|
+
if prefix.include?(?/) && object_path.include?(?/)
|
509
|
+
prefixes = []
|
510
|
+
prefix_array = File.dirname(prefix).split("/")
|
511
|
+
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
|
491
512
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
|
513
|
+
prefix_array.each_with_index do |dir, index|
|
514
|
+
break if dir == object_path_array[index]
|
515
|
+
prefixes << dir
|
516
|
+
end
|
497
517
|
|
498
|
-
|
499
|
-
|
500
|
-
|
518
|
+
(prefixes << object_path).join("/")
|
519
|
+
else
|
520
|
+
object_path
|
501
521
|
end
|
502
|
-
|
503
|
-
(prefixes << object_path).join("/")
|
504
|
-
else
|
505
|
-
object_path
|
506
522
|
end
|
507
|
-
end
|
508
523
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
524
|
+
def retrieve_template_keys(variable)
|
525
|
+
keys = @locals.keys
|
526
|
+
keys << variable
|
527
|
+
if @collection
|
528
|
+
keys << @variable_counter
|
529
|
+
keys << @variable_iteration
|
530
|
+
end
|
531
|
+
keys
|
515
532
|
end
|
516
|
-
keys
|
517
|
-
end
|
518
533
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
534
|
+
def retrieve_variable(path, as)
|
535
|
+
variable = as || begin
|
536
|
+
base = path[-1] == "/" ? "" : File.basename(path)
|
537
|
+
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
|
538
|
+
$1.to_sym
|
539
|
+
end
|
540
|
+
if @collection
|
541
|
+
variable_counter = :"#{variable}_counter"
|
542
|
+
variable_iteration = :"#{variable}_iteration"
|
543
|
+
end
|
544
|
+
[variable, variable_counter, variable_iteration]
|
528
545
|
end
|
529
|
-
[variable, variable_counter, variable_iteration]
|
530
|
-
end
|
531
546
|
|
532
|
-
|
533
|
-
|
534
|
-
"and is followed by any combination of letters, numbers and underscores."
|
547
|
+
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
|
548
|
+
"make sure your partial name starts with underscore."
|
535
549
|
|
536
|
-
|
537
|
-
|
538
|
-
|
550
|
+
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
|
551
|
+
"make sure it starts with lowercase letter, " \
|
552
|
+
"and is followed by any combination of letters, numbers and underscores."
|
539
553
|
|
540
|
-
|
541
|
-
|
542
|
-
|
554
|
+
def raise_invalid_identifier(path)
|
555
|
+
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
|
556
|
+
end
|
543
557
|
|
544
|
-
|
545
|
-
|
546
|
-
|
558
|
+
def raise_invalid_option_as(as)
|
559
|
+
raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
|
560
|
+
end
|
547
561
|
end
|
548
562
|
end
|