actionview 5.1.4 → 6.1.1
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 +5 -5
- data/CHANGELOG.md +199 -168
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -5
- data/lib/action_view.rb +10 -4
- data/lib/action_view/base.rb +87 -23
- data/lib/action_view/buffers.rb +17 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +7 -11
- data/lib/action_view/dependency_tracker.rb +12 -4
- data/lib/action_view/digestor.rb +24 -23
- data/lib/action_view/flows.rb +2 -1
- data/lib/action_view/gem_version.rb +4 -2
- data/lib/action_view/helpers.rb +4 -2
- data/lib/action_view/helpers/active_model_helper.rb +9 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +220 -57
- data/lib/action_view/helpers/asset_url_helper.rb +28 -23
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -2
- data/lib/action_view/helpers/cache_helper.rb +39 -28
- data/lib/action_view/helpers/capture_helper.rb +13 -7
- data/lib/action_view/helpers/controller_helper.rb +3 -1
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +5 -3
- data/lib/action_view/helpers/date_helper.rb +78 -33
- data/lib/action_view/helpers/debug_helper.rb +4 -2
- data/lib/action_view/helpers/form_helper.rb +357 -106
- data/lib/action_view/helpers/form_options_helper.rb +45 -39
- data/lib/action_view/helpers/form_tag_helper.rb +42 -27
- data/lib/action_view/helpers/javascript_helper.rb +28 -12
- data/lib/action_view/helpers/number_helper.rb +16 -8
- data/lib/action_view/helpers/output_safety_helper.rb +3 -1
- data/lib/action_view/helpers/rendering_helper.rb +20 -9
- data/lib/action_view/helpers/sanitize_helper.rb +15 -19
- data/lib/action_view/helpers/tag_helper.rb +100 -24
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/tags/base.rb +30 -21
- data/lib/action_view/helpers/tags/check_box.rb +3 -2
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -1
- data/lib/action_view/helpers/tags/collection_select.rb +3 -1
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +5 -4
- data/lib/action_view/helpers/tags/datetime_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- 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 +3 -1
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +6 -5
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +2 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +2 -0
- data/lib/action_view/helpers/tags/radio_button.rb +3 -2
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +2 -0
- data/lib/action_view/helpers/tags/select.rb +4 -3
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +3 -1
- data/lib/action_view/helpers/tags/text_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- 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 +3 -6
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +11 -10
- data/lib/action_view/helpers/translation_helper.rb +102 -52
- data/lib/action_view/helpers/url_helper.rb +150 -32
- data/lib/action_view/layouts.rb +15 -15
- data/lib/action_view/log_subscriber.rb +32 -15
- data/lib/action_view/lookup_context.rb +67 -39
- data/lib/action_view/model_naming.rb +2 -0
- data/lib/action_view/path_set.rb +5 -12
- data/lib/action_view/railtie.rb +46 -21
- data/lib/action_view/record_identifier.rb +4 -3
- data/lib/action_view/renderer/abstract_renderer.rb +144 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer.rb +33 -283
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +64 -17
- data/lib/action_view/renderer/renderer.rb +61 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +14 -8
- data/lib/action_view/renderer/template_renderer.rb +36 -26
- data/lib/action_view/rendering.rb +57 -38
- data/lib/action_view/routing_url_for.rb +15 -12
- data/lib/action_view/tasks/cache_digests.rake +2 -0
- data/lib/action_view/template.rb +69 -76
- data/lib/action_view/template/error.rb +32 -18
- data/lib/action_view/template/handlers.rb +4 -2
- data/lib/action_view/template/handlers/builder.rb +5 -6
- data/lib/action_view/template/handlers/erb.rb +20 -19
- data/lib/action_view/template/handlers/erb/erubi.rb +17 -9
- data/lib/action_view/template/handlers/html.rb +3 -1
- data/lib/action_view/template/handlers/raw.rb +4 -2
- data/lib/action_view/template/html.rb +8 -7
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +194 -152
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +5 -4
- data/lib/action_view/template/types.rb +3 -1
- data/lib/action_view/test_case.rb +38 -30
- data/lib/action_view/testing/resolvers.rb +20 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +2 -0
- data/lib/action_view/view_paths.rb +61 -40
- data/lib/assets/compiled/rails-ujs.js +84 -23
- metadata +34 -23
- data/lib/action_view/helpers/record_tag_helper.rb +0 -21
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +0 -9
- data/lib/action_view/template/handlers/erb/erubis.rb +0 -81
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/renderer/partial_renderer"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class PartialIteration
|
7
|
+
# The number of iterations that will be done by the partial.
|
8
|
+
attr_reader :size
|
9
|
+
|
10
|
+
# The current iteration of the partial.
|
11
|
+
attr_reader :index
|
12
|
+
|
13
|
+
def initialize(size)
|
14
|
+
@size = size
|
15
|
+
@index = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if this is the first iteration of the partial.
|
19
|
+
def first?
|
20
|
+
index == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if this is the last iteration of the partial.
|
24
|
+
def last?
|
25
|
+
index == size - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def iterate! # :nodoc:
|
29
|
+
@index += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CollectionRenderer < PartialRenderer # :nodoc:
|
34
|
+
include ObjectRendering
|
35
|
+
|
36
|
+
class CollectionIterator # :nodoc:
|
37
|
+
include Enumerable
|
38
|
+
|
39
|
+
def initialize(collection)
|
40
|
+
@collection = collection
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&blk)
|
44
|
+
@collection.each(&blk)
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@collection.size
|
49
|
+
end
|
50
|
+
|
51
|
+
def length
|
52
|
+
@collection.respond_to?(:length) ? @collection.length : size
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class SameCollectionIterator < CollectionIterator # :nodoc:
|
57
|
+
def initialize(collection, path, variables)
|
58
|
+
super(collection)
|
59
|
+
@path = path
|
60
|
+
@variables = variables
|
61
|
+
end
|
62
|
+
|
63
|
+
def from_collection(collection)
|
64
|
+
self.class.new(collection, @path, @variables)
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_with_info
|
68
|
+
return enum_for(:each_with_info) unless block_given?
|
69
|
+
variables = [@path] + @variables
|
70
|
+
@collection.each { |o| yield(o, variables) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
|
75
|
+
def initialize(collection, path, variables, relation)
|
76
|
+
super(collection, path, variables)
|
77
|
+
relation.skip_preloading! unless relation.loaded?
|
78
|
+
@relation = relation
|
79
|
+
end
|
80
|
+
|
81
|
+
def from_collection(collection)
|
82
|
+
self.class.new(collection, @path, @variables, @relation)
|
83
|
+
end
|
84
|
+
|
85
|
+
def each_with_info
|
86
|
+
return super unless block_given?
|
87
|
+
@relation.preload_associations(@collection)
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class MixedCollectionIterator < CollectionIterator # :nodoc:
|
93
|
+
def initialize(collection, paths)
|
94
|
+
super(collection)
|
95
|
+
@paths = paths
|
96
|
+
end
|
97
|
+
|
98
|
+
def each_with_info
|
99
|
+
return enum_for(:each_with_info) unless block_given?
|
100
|
+
@collection.each_with_index { |o, i| yield(o, @paths[i]) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def render_collection_with_partial(collection, partial, context, block)
|
105
|
+
iter_vars = retrieve_variable(partial)
|
106
|
+
|
107
|
+
collection = if collection.respond_to?(:preload_associations)
|
108
|
+
PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
|
109
|
+
else
|
110
|
+
SameCollectionIterator.new(collection, partial, iter_vars)
|
111
|
+
end
|
112
|
+
|
113
|
+
template = find_template(partial, @locals.keys + iter_vars)
|
114
|
+
|
115
|
+
layout = if !block && (layout = @options[:layout])
|
116
|
+
find_template(layout.to_s, @locals.keys + iter_vars)
|
117
|
+
end
|
118
|
+
|
119
|
+
render_collection(collection, context, partial, template, layout, block)
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_collection_derive_partial(collection, context, block)
|
123
|
+
paths = collection.map { |o| partial_path(o, context) }
|
124
|
+
|
125
|
+
if paths.uniq.length == 1
|
126
|
+
# Homogeneous
|
127
|
+
render_collection_with_partial(collection, paths.first, context, block)
|
128
|
+
else
|
129
|
+
if @options[:cached]
|
130
|
+
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
|
131
|
+
end
|
132
|
+
|
133
|
+
paths.map! { |path| retrieve_variable(path).unshift(path) }
|
134
|
+
collection = MixedCollectionIterator.new(collection, paths)
|
135
|
+
render_collection(collection, context, nil, nil, nil, block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def retrieve_variable(path)
|
141
|
+
variable = local_variable(path)
|
142
|
+
[variable, :"#{variable}_counter", :"#{variable}_iteration"]
|
143
|
+
end
|
144
|
+
|
145
|
+
def render_collection(collection, view, path, template, layout, block)
|
146
|
+
identifier = (template && template.identifier) || path
|
147
|
+
ActiveSupport::Notifications.instrument(
|
148
|
+
"render_collection.action_view",
|
149
|
+
identifier: identifier,
|
150
|
+
layout: layout && layout.virtual_path,
|
151
|
+
count: collection.length
|
152
|
+
) do |payload|
|
153
|
+
spacer = if @options.key?(:spacer_template)
|
154
|
+
spacer_template = find_template(@options[:spacer_template], @locals.keys)
|
155
|
+
build_rendered_template(spacer_template.render(view, @locals), spacer_template)
|
156
|
+
else
|
157
|
+
RenderedTemplate::EMPTY_SPACER
|
158
|
+
end
|
159
|
+
|
160
|
+
collection_body = if template
|
161
|
+
cache_collection_render(payload, view, template, collection) do |filtered_collection|
|
162
|
+
collection_with_template(view, template, layout, filtered_collection)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
collection_with_template(view, nil, layout, collection)
|
166
|
+
end
|
167
|
+
|
168
|
+
return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
|
169
|
+
|
170
|
+
build_rendered_collection(collection_body, spacer)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def collection_with_template(view, template, layout, collection)
|
175
|
+
locals = @locals
|
176
|
+
cache = {}
|
177
|
+
|
178
|
+
partial_iteration = PartialIteration.new(collection.size)
|
179
|
+
|
180
|
+
collection.each_with_info.map do |object, (path, as, counter, iteration)|
|
181
|
+
index = partial_iteration.index
|
182
|
+
|
183
|
+
locals[as] = object
|
184
|
+
locals[counter] = index
|
185
|
+
locals[iteration] = partial_iteration
|
186
|
+
|
187
|
+
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
|
188
|
+
|
189
|
+
content = _template.render(view, locals)
|
190
|
+
content = layout.render(view, locals) { content } if layout
|
191
|
+
partial_iteration.iterate!
|
192
|
+
build_rendered_template(content, _template)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class ObjectRenderer < PartialRenderer # :nodoc:
|
5
|
+
include ObjectRendering
|
6
|
+
|
7
|
+
def initialize(lookup_context, options)
|
8
|
+
super
|
9
|
+
@object = nil
|
10
|
+
@local_name = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_object_with_partial(object, partial, context, block)
|
14
|
+
@object = object
|
15
|
+
@local_name = local_variable(partial)
|
16
|
+
render(partial, context, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_object_derive_partial(object, context, block)
|
20
|
+
path = partial_path(object, context)
|
21
|
+
render_object_with_partial(object, path, context, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def template_keys(path)
|
26
|
+
super + [@local_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_partial_template(view, locals, template, layout, block)
|
30
|
+
locals[@local_name || template.variable] = @object
|
31
|
+
super(view, locals, template, layout, block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,34 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "action_view/renderer/partial_renderer/collection_caching"
|
3
4
|
|
4
5
|
module ActionView
|
5
|
-
class PartialIteration
|
6
|
-
# The number of iterations that will be done by the partial.
|
7
|
-
attr_reader :size
|
8
|
-
|
9
|
-
# The current iteration of the partial.
|
10
|
-
attr_reader :index
|
11
|
-
|
12
|
-
def initialize(size)
|
13
|
-
@size = size
|
14
|
-
@index = 0
|
15
|
-
end
|
16
|
-
|
17
|
-
# Check if this is the first iteration of the partial.
|
18
|
-
def first?
|
19
|
-
index == 0
|
20
|
-
end
|
21
|
-
|
22
|
-
# Check if this is the last iteration of the partial.
|
23
|
-
def last?
|
24
|
-
index == size - 1
|
25
|
-
end
|
26
|
-
|
27
|
-
def iterate! # :nodoc:
|
28
|
-
@index += 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
6
|
# = Action View Partials
|
33
7
|
#
|
34
8
|
# There's also a convenience method for rendering sub templates within the current controller that depends on a
|
@@ -50,12 +24,12 @@ module ActionView
|
|
50
24
|
# <%= render partial: "ad", locals: { ad: ad } %>
|
51
25
|
# <% end %>
|
52
26
|
#
|
53
|
-
# This would first render
|
54
|
-
# render
|
27
|
+
# This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
|
28
|
+
# render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
|
55
29
|
#
|
56
30
|
# == The :as and :object options
|
57
31
|
#
|
58
|
-
# By default
|
32
|
+
# By default ActionView::PartialRenderer doesn't have any local variables.
|
59
33
|
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
|
60
34
|
#
|
61
35
|
# <%= render partial: "account", object: @buyer %>
|
@@ -83,7 +57,7 @@ module ActionView
|
|
83
57
|
#
|
84
58
|
# <%= render partial: "ad", collection: @advertisements %>
|
85
59
|
#
|
86
|
-
# This will render
|
60
|
+
# This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
|
87
61
|
# iteration object will automatically be made available to the template with a name of the form
|
88
62
|
# +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
|
89
63
|
# the collection and the total size of the collection. The iteration object also has two convenience methods,
|
@@ -98,32 +72,29 @@ module ActionView
|
|
98
72
|
#
|
99
73
|
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
|
100
74
|
#
|
101
|
-
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil
|
75
|
+
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
|
102
76
|
# to specify a text which will be displayed instead by using this form:
|
103
77
|
#
|
104
78
|
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
|
105
79
|
#
|
106
|
-
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
|
107
|
-
# just keep domain objects, like Active Records, in there.
|
108
|
-
#
|
109
80
|
# == \Rendering shared partials
|
110
81
|
#
|
111
82
|
# Two controllers can share a set of partials and render them like this:
|
112
83
|
#
|
113
84
|
# <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
|
114
85
|
#
|
115
|
-
# This will render the partial
|
86
|
+
# This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
|
116
87
|
#
|
117
|
-
# == \Rendering objects that respond to
|
88
|
+
# == \Rendering objects that respond to +to_partial_path+
|
118
89
|
#
|
119
90
|
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
|
120
|
-
# and pick the proper path by checking
|
91
|
+
# and pick the proper path by checking +to_partial_path+ method.
|
121
92
|
#
|
122
93
|
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
|
123
94
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
124
95
|
# <%= render partial: @account %>
|
125
96
|
#
|
126
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
97
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
|
127
98
|
# # that's why we can replace:
|
128
99
|
# # <%= render partial: "posts/post", collection: @posts %>
|
129
100
|
# <%= render partial: @posts %>
|
@@ -143,7 +114,7 @@ module ActionView
|
|
143
114
|
# # <%= render partial: "accounts/account", locals: { account: @account} %>
|
144
115
|
# <%= render @account %>
|
145
116
|
#
|
146
|
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on
|
117
|
+
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
|
147
118
|
# # that's why we can replace:
|
148
119
|
# # <%= render partial: "posts/post", collection: @posts %>
|
149
120
|
# <%= render @posts %>
|
@@ -283,268 +254,47 @@ module ActionView
|
|
283
254
|
class PartialRenderer < AbstractRenderer
|
284
255
|
include CollectionCaching
|
285
256
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
super
|
292
|
-
@context_prefix = @lookup_context.prefixes.first
|
257
|
+
def initialize(lookup_context, options)
|
258
|
+
super(lookup_context)
|
259
|
+
@options = options
|
260
|
+
@locals = @options[:locals] || {}
|
261
|
+
@details = extract_details(@options)
|
293
262
|
end
|
294
263
|
|
295
|
-
def render(
|
296
|
-
|
297
|
-
@template = find_partial
|
264
|
+
def render(partial, context, block)
|
265
|
+
template = find_template(partial, template_keys(partial))
|
298
266
|
|
299
|
-
|
300
|
-
|
301
|
-
@template.formats.first
|
302
|
-
else
|
303
|
-
formats.first
|
304
|
-
end
|
267
|
+
if !block && (layout = @options[:layout])
|
268
|
+
layout = find_template(layout.to_s, template_keys(partial))
|
305
269
|
end
|
306
270
|
|
307
|
-
|
308
|
-
render_collection
|
309
|
-
else
|
310
|
-
render_partial
|
311
|
-
end
|
271
|
+
render_partial_template(context, @locals, template, layout, block)
|
312
272
|
end
|
313
273
|
|
314
274
|
private
|
315
|
-
|
316
|
-
|
317
|
-
instrument(:collection, count: @collection.size) do |payload|
|
318
|
-
return nil if @collection.blank?
|
319
|
-
|
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
|
275
|
+
def template_keys(_)
|
276
|
+
@locals.keys
|
328
277
|
end
|
329
278
|
|
330
|
-
def
|
331
|
-
instrument(
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
end
|
338
|
-
|
339
|
-
object = locals[as] if object.nil? # Respect object when object is false
|
340
|
-
locals[as] = object if @has_object
|
341
|
-
|
342
|
-
content = @template.render(view, locals) do |*name|
|
279
|
+
def render_partial_template(view, locals, template, layout, block)
|
280
|
+
ActiveSupport::Notifications.instrument(
|
281
|
+
"render_partial.action_view",
|
282
|
+
identifier: template.identifier,
|
283
|
+
layout: layout && layout.virtual_path
|
284
|
+
) do |payload|
|
285
|
+
content = template.render(view, locals, add_to_stack: !block) do |*name|
|
343
286
|
view._layout_for(*name, &block)
|
344
287
|
end
|
345
288
|
|
346
289
|
content = layout.render(view, locals) { content } if layout
|
347
|
-
payload[:cache_hit] = view.view_renderer.cache_hits[
|
348
|
-
content
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
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
|
388
|
-
|
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
|
290
|
+
payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
|
291
|
+
build_rendered_template(content, template)
|
392
292
|
end
|
393
|
-
|
394
|
-
if @path
|
395
|
-
@variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
|
396
|
-
@template_keys = retrieve_template_keys
|
397
|
-
else
|
398
|
-
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
|
399
|
-
end
|
400
|
-
|
401
|
-
self
|
402
|
-
end
|
403
|
-
|
404
|
-
def collection_from_options
|
405
|
-
if @options.key?(:collection)
|
406
|
-
collection = @options[:collection]
|
407
|
-
collection ? collection.to_a : []
|
408
|
-
end
|
409
|
-
end
|
410
|
-
|
411
|
-
def collection_from_object
|
412
|
-
@object.to_ary if @object.respond_to?(:to_ary)
|
413
|
-
end
|
414
|
-
|
415
|
-
def find_partial
|
416
|
-
find_template(@path, @template_keys) if @path
|
417
293
|
end
|
418
294
|
|
419
295
|
def find_template(path, locals)
|
420
296
|
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
|
421
297
|
@lookup_context.find_template(path, prefixes, true, locals, @details)
|
422
298
|
end
|
423
|
-
|
424
|
-
def collection_with_template
|
425
|
-
view, locals, template = @view, @locals, @template
|
426
|
-
as, counter, iteration = @variable, @variable_counter, @variable_iteration
|
427
|
-
|
428
|
-
if layout = @options[:layout]
|
429
|
-
layout = find_template(layout, @template_keys)
|
430
|
-
end
|
431
|
-
|
432
|
-
partial_iteration = PartialIteration.new(@collection.size)
|
433
|
-
locals[iteration] = partial_iteration
|
434
|
-
|
435
|
-
@collection.map do |object|
|
436
|
-
locals[as] = object
|
437
|
-
locals[counter] = partial_iteration.index
|
438
|
-
|
439
|
-
content = template.render(view, locals)
|
440
|
-
content = layout.render(view, locals) { content } if layout
|
441
|
-
partial_iteration.iterate!
|
442
|
-
content
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
def collection_without_template
|
447
|
-
view, locals, collection_data = @view, @locals, @collection_data
|
448
|
-
cache = {}
|
449
|
-
keys = @locals.keys
|
450
|
-
|
451
|
-
partial_iteration = PartialIteration.new(@collection.size)
|
452
|
-
|
453
|
-
@collection.map do |object|
|
454
|
-
index = partial_iteration.index
|
455
|
-
path, as, counter, iteration = collection_data[index]
|
456
|
-
|
457
|
-
locals[as] = object
|
458
|
-
locals[counter] = index
|
459
|
-
locals[iteration] = partial_iteration
|
460
|
-
|
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
|
466
|
-
end
|
467
|
-
|
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
|
489
|
-
end
|
490
|
-
|
491
|
-
def prefixed_partial_names
|
492
|
-
@prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
|
493
|
-
end
|
494
|
-
|
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
|
500
|
-
|
501
|
-
prefix_array.each_with_index do |dir, index|
|
502
|
-
break if dir == object_path_array[index]
|
503
|
-
prefixes << dir
|
504
|
-
end
|
505
|
-
|
506
|
-
(prefixes << object_path).join("/")
|
507
|
-
else
|
508
|
-
object_path
|
509
|
-
end
|
510
|
-
end
|
511
|
-
|
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
|
520
|
-
end
|
521
|
-
|
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]
|
533
|
-
end
|
534
|
-
|
535
|
-
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
|
536
|
-
"make sure your partial name starts with underscore."
|
537
|
-
|
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."
|
541
|
-
|
542
|
-
def raise_invalid_identifier(path)
|
543
|
-
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
|
544
|
-
end
|
545
|
-
|
546
|
-
def raise_invalid_option_as(as)
|
547
|
-
raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
|
548
|
-
end
|
549
299
|
end
|
550
300
|
end
|