actionview 4.2.11.1 → 7.0.2.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.

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -215
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +116 -43
  6. data/lib/action_view/buffers.rb +20 -3
  7. data/lib/action_view/cache_expiry.rb +66 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  10. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  11. data/lib/action_view/dependency_tracker.rb +21 -122
  12. data/lib/action_view/digestor.rb +92 -85
  13. data/lib/action_view/flows.rb +15 -16
  14. data/lib/action_view/gem_version.rb +6 -4
  15. data/lib/action_view/helpers/active_model_helper.rb +17 -12
  16. data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
  17. data/lib/action_view/helpers/asset_url_helper.rb +180 -74
  18. data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
  19. data/lib/action_view/helpers/cache_helper.rb +156 -43
  20. data/lib/action_view/helpers/capture_helper.rb +21 -14
  21. data/lib/action_view/helpers/controller_helper.rb +16 -5
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  24. data/lib/action_view/helpers/date_helper.rb +288 -132
  25. data/lib/action_view/helpers/debug_helper.rb +9 -6
  26. data/lib/action_view/helpers/form_helper.rb +956 -173
  27. data/lib/action_view/helpers/form_options_helper.rb +178 -97
  28. data/lib/action_view/helpers/form_tag_helper.rb +220 -101
  29. data/lib/action_view/helpers/javascript_helper.rb +33 -19
  30. data/lib/action_view/helpers/number_helper.rb +88 -63
  31. data/lib/action_view/helpers/output_safety_helper.rb +38 -6
  32. data/lib/action_view/helpers/rendering_helper.rb +21 -10
  33. data/lib/action_view/helpers/sanitize_helper.rb +31 -32
  34. data/lib/action_view/helpers/tag_helper.rb +332 -71
  35. data/lib/action_view/helpers/tags/base.rb +123 -99
  36. data/lib/action_view/helpers/tags/check_box.rb +21 -20
  37. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  39. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  40. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  41. data/lib/action_view/helpers/tags/collection_select.rb +5 -3
  42. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  43. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  44. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  45. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  46. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  48. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  49. data/lib/action_view/helpers/tags/file_field.rb +18 -0
  50. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  51. data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
  52. data/lib/action_view/helpers/tags/label.rb +7 -2
  53. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  54. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  55. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  56. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  57. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  58. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  59. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  60. data/lib/action_view/helpers/tags/select.rb +11 -10
  61. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  62. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  63. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  64. data/lib/action_view/helpers/tags/time_field.rb +12 -2
  65. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  66. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  67. data/lib/action_view/helpers/tags/translator.rb +15 -16
  68. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  69. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  70. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  71. data/lib/action_view/helpers/tags.rb +5 -2
  72. data/lib/action_view/helpers/text_helper.rb +80 -51
  73. data/lib/action_view/helpers/translation_helper.rb +120 -69
  74. data/lib/action_view/helpers/url_helper.rb +398 -171
  75. data/lib/action_view/helpers.rb +29 -27
  76. data/lib/action_view/layouts.rb +68 -63
  77. data/lib/action_view/log_subscriber.rb +77 -10
  78. data/lib/action_view/lookup_context.rb +137 -113
  79. data/lib/action_view/model_naming.rb +4 -2
  80. data/lib/action_view/path_set.rb +28 -32
  81. data/lib/action_view/railtie.rb +74 -13
  82. data/lib/action_view/record_identifier.rb +53 -26
  83. data/lib/action_view/render_parser.rb +188 -0
  84. data/lib/action_view/renderer/abstract_renderer.rb +152 -15
  85. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  86. data/lib/action_view/renderer/object_renderer.rb +34 -0
  87. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  88. data/lib/action_view/renderer/partial_renderer.rb +51 -333
  89. data/lib/action_view/renderer/renderer.rb +68 -11
  90. data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
  91. data/lib/action_view/renderer/template_renderer.rb +87 -74
  92. data/lib/action_view/rendering.rb +73 -47
  93. data/lib/action_view/ripper_ast_parser.rb +198 -0
  94. data/lib/action_view/routing_url_for.rb +35 -24
  95. data/lib/action_view/tasks/cache_digests.rake +25 -0
  96. data/lib/action_view/template/error.rb +151 -41
  97. data/lib/action_view/template/handlers/builder.rb +12 -13
  98. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  99. data/lib/action_view/template/handlers/erb.rb +29 -89
  100. data/lib/action_view/template/handlers/html.rb +11 -0
  101. data/lib/action_view/template/handlers/raw.rb +4 -4
  102. data/lib/action_view/template/handlers.rb +14 -10
  103. data/lib/action_view/template/html.rb +12 -13
  104. data/lib/action_view/template/inline.rb +22 -0
  105. data/lib/action_view/template/raw_file.rb +25 -0
  106. data/lib/action_view/template/renderable.rb +24 -0
  107. data/lib/action_view/template/resolver.rb +139 -300
  108. data/lib/action_view/template/sources/file.rb +17 -0
  109. data/lib/action_view/template/sources.rb +13 -0
  110. data/lib/action_view/template/text.rb +10 -12
  111. data/lib/action_view/template/types.rb +28 -26
  112. data/lib/action_view/template.rb +123 -91
  113. data/lib/action_view/template_details.rb +66 -0
  114. data/lib/action_view/template_path.rb +64 -0
  115. data/lib/action_view/test_case.rb +70 -53
  116. data/lib/action_view/testing/resolvers.rb +25 -35
  117. data/lib/action_view/unbound_template.rb +57 -0
  118. data/lib/action_view/version.rb +3 -1
  119. data/lib/action_view/view_paths.rb +73 -58
  120. data/lib/action_view.rb +16 -11
  121. data/lib/assets/compiled/rails-ujs.js +746 -0
  122. metadata +52 -32
  123. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  124. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -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
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
5
+ module ActionView
6
+ module CollectionCaching # :nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # Fallback cache store if Action View is used without Rails.
11
+ # Otherwise overridden in Railtie to use Rails.cache.
12
+ mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
13
+ end
14
+
15
+ private
16
+ def will_cache?(options, view)
17
+ options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching
18
+ end
19
+
20
+ def cache_collection_render(instrumentation_payload, view, template, collection)
21
+ return yield(collection) unless will_cache?(@options, view)
22
+
23
+ collection_iterator = collection
24
+
25
+ # Result is a hash with the key represents the
26
+ # key used for cache lookup and the value is the item
27
+ # on which the partial is being rendered
28
+ keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection)
29
+
30
+ # Pull all partials from cache
31
+ # Result is a hash, key matches the entry in
32
+ # `keyed_collection` where the cache was retrieved and the
33
+ # value is the value that was present in the cache
34
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
35
+ instrumentation_payload[:cache_hits] = cached_partials.size
36
+
37
+ # Extract the items for the keys that are not found
38
+ collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
39
+
40
+ rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection))
41
+
42
+ index = 0
43
+ keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
44
+ # This block is called once
45
+ # for every cache miss while preserving order.
46
+ rendered_partials[index].tap { index += 1 }
47
+ end
48
+
49
+ ordered_keys.map do |key|
50
+ keyed_partials[key]
51
+ end
52
+ end
53
+
54
+ def callable_cache_key?
55
+ @options[:cached].respond_to?(:call)
56
+ end
57
+
58
+ def collection_by_cache_keys(view, template, collection)
59
+ seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
60
+
61
+ digest_path = view.digest_path_from_template(template)
62
+
63
+ collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
64
+ key = expanded_cache_key(seed.call(item), view, template, digest_path)
65
+ ordered_keys << key
66
+ hash[key] = item
67
+ end
68
+ end
69
+
70
+ def expanded_cache_key(key, view, template, digest_path)
71
+ key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
72
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
73
+ end
74
+
75
+ # `order_by` is an enumerable object containing keys of the cache,
76
+ # all keys are passed in whether found already or not.
77
+ #
78
+ # `cached_partials` is a hash. If the value exists
79
+ # it represents the rendered partial from the cache
80
+ # otherwise `Hash#fetch` will take the value of its block.
81
+ #
82
+ # This method expects a block that will return the rendered
83
+ # partial. An example is to render all results
84
+ # for each element that was not found in the cache and store it as an array.
85
+ # Order it so that the first empty cache element in `cached_partials`
86
+ # corresponds to the first element in `rendered_partials`.
87
+ #
88
+ # If the partial is not already cached it will also be
89
+ # written back to the underlying cache store.
90
+ def fetch_or_cache_partial(cached_partials, template, order_by:)
91
+ order_by.index_with do |cache_key|
92
+ if content = cached_partials[cache_key]
93
+ build_rendered_template(content, template)
94
+ else
95
+ yield.tap do |rendered_partial|
96
+ collection_cache.write(cache_key, rendered_partial.body)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,33 +1,8 @@
1
- require 'thread_safe'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActionView
4
- class PartialIteration
5
- # The number of iterations that will be done by the partial.
6
- attr_reader :size
7
-
8
- # The current iteration of the partial.
9
- attr_reader :index
10
-
11
- def initialize(size)
12
- @size = size
13
- @index = 0
14
- end
15
-
16
- # Check if this is the first iteration of the partial.
17
- def first?
18
- index == 0
19
- end
20
-
21
- # Check if this is the last iteration of the partial.
22
- def last?
23
- index == size - 1
24
- end
25
-
26
- def iterate! # :nodoc:
27
- @index += 1
28
- end
29
- end
3
+ require "action_view/renderer/partial_renderer/collection_caching"
30
4
 
5
+ module ActionView
31
6
  # = Action View Partials
32
7
  #
33
8
  # There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -49,12 +24,12 @@ module ActionView
49
24
  # <%= render partial: "ad", locals: { ad: ad } %>
50
25
  # <% end %>
51
26
  #
52
- # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
53
- # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
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.
54
29
  #
55
30
  # == The :as and :object options
56
31
  #
57
- # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
32
+ # By default ActionView::PartialRenderer doesn't have any local variables.
58
33
  # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
59
34
  #
60
35
  # <%= render partial: "account", object: @buyer %>
@@ -73,7 +48,7 @@ module ActionView
73
48
  #
74
49
  # <%= render partial: "account", locals: { user: @buyer } %>
75
50
  #
76
- # == Rendering a collection of partials
51
+ # == \Rendering a collection of partials
77
52
  #
78
53
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
79
54
  # render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -82,7 +57,7 @@ module ActionView
82
57
  #
83
58
  # <%= render partial: "ad", collection: @advertisements %>
84
59
  #
85
- # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
60
+ # This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
86
61
  # iteration object will automatically be made available to the template with a name of the form
87
62
  # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
88
63
  # the collection and the total size of the collection. The iteration object also has two convenience methods,
@@ -97,37 +72,34 @@ module ActionView
97
72
  #
98
73
  # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
99
74
  #
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:
75
+ # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
76
+ # to specify a text which will be displayed instead by using this form:
102
77
  #
103
78
  # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
104
79
  #
105
- # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
106
- # just keep domain objects, like Active Records, in there.
107
- #
108
- # == Rendering shared partials
80
+ # == \Rendering shared partials
109
81
  #
110
82
  # Two controllers can share a set of partials and render them like this:
111
83
  #
112
84
  # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
113
85
  #
114
- # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
86
+ # This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
115
87
  #
116
- # == Rendering objects that respond to `to_partial_path`
88
+ # == \Rendering objects that respond to +to_partial_path+
117
89
  #
118
90
  # 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 `to_partial_path` method.
91
+ # and pick the proper path by checking +to_partial_path+ method.
120
92
  #
121
93
  # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
122
94
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
123
95
  # <%= render partial: @account %>
124
96
  #
125
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
97
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
126
98
  # # that's why we can replace:
127
99
  # # <%= render partial: "posts/post", collection: @posts %>
128
100
  # <%= render partial: @posts %>
129
101
  #
130
- # == Rendering the default case
102
+ # == \Rendering the default case
131
103
  #
132
104
  # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
133
105
  # defaults of render to render partials. Examples:
@@ -142,34 +114,34 @@ module ActionView
142
114
  # # <%= render partial: "accounts/account", locals: { account: @account} %>
143
115
  # <%= render @account %>
144
116
  #
145
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
117
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
146
118
  # # that's why we can replace:
147
119
  # # <%= render partial: "posts/post", collection: @posts %>
148
120
  # <%= render @posts %>
149
121
  #
150
- # == Rendering partials with layouts
122
+ # == \Rendering partials with layouts
151
123
  #
152
124
  # Partials can have their own layouts applied to them. These layouts are different than the ones that are
153
125
  # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
154
126
  # of users:
155
127
  #
156
- # <%# app/views/users/index.html.erb &>
128
+ # <%# app/views/users/index.html.erb %>
157
129
  # Here's the administrator:
158
130
  # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
159
131
  #
160
132
  # Here's the editor:
161
133
  # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
162
134
  #
163
- # <%# app/views/users/_user.html.erb &>
135
+ # <%# app/views/users/_user.html.erb %>
164
136
  # Name: <%= user.name %>
165
137
  #
166
- # <%# app/views/users/_administrator.html.erb &>
138
+ # <%# app/views/users/_administrator.html.erb %>
167
139
  # <div id="administrator">
168
140
  # Budget: $<%= user.budget %>
169
141
  # <%= yield %>
170
142
  # </div>
171
143
  #
172
- # <%# app/views/users/_editor.html.erb &>
144
+ # <%# app/views/users/_editor.html.erb %>
173
145
  # <div id="editor">
174
146
  # Deadline: <%= user.deadline %>
175
147
  # <%= yield %>
@@ -232,7 +204,7 @@ module ActionView
232
204
  #
233
205
  # You can also apply a layout to a block within any template:
234
206
  #
235
- # <%# app/views/users/_chief.html.erb &>
207
+ # <%# app/views/users/_chief.html.erb %>
236
208
  # <%= render(layout: "administrator", locals: { user: chief }) do %>
237
209
  # Title: <%= chief.title %>
238
210
  # <% end %>
@@ -245,304 +217,50 @@ module ActionView
245
217
  # </div>
246
218
  #
247
219
  # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
248
- #
249
- # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
250
- # an array to layout and treat it as an enumerable.
251
- #
252
- # <%# app/views/users/_user.html.erb &>
253
- # <div class="user">
254
- # Budget: $<%= user.budget %>
255
- # <%= yield user %>
256
- # </div>
257
- #
258
- # <%# app/views/users/index.html.erb &>
259
- # <%= render layout: @users do |user| %>
260
- # Title: <%= user.title %>
261
- # <% end %>
262
- #
263
- # This will render the layout for each user and yield to the block, passing the user, each time.
264
- #
265
- # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
266
- #
267
- # <%# app/views/users/_user.html.erb &>
268
- # <div class="user">
269
- # <%= yield user, :header %>
270
- # Budget: $<%= user.budget %>
271
- # <%= yield user, :footer %>
272
- # </div>
273
- #
274
- # <%# app/views/users/index.html.erb &>
275
- # <%= render layout: @users do |user, section| %>
276
- # <%- case section when :header -%>
277
- # Title: <%= user.title %>
278
- # <%- when :footer -%>
279
- # Deadline: <%= user.deadline %>
280
- # <%- end -%>
281
- # <% end %>
282
220
  class PartialRenderer < AbstractRenderer
283
- PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
284
- h[k] = ThreadSafe::Cache.new
285
- end
286
-
287
- def initialize(*)
288
- super
289
- @context_prefix = @lookup_context.prefixes.first
290
- end
291
-
292
- def render(context, options, block)
293
- setup(context, options, block)
294
- identifier = (@template = find_partial) ? @template.identifier : @path
295
-
296
- @lookup_context.rendered_format ||= begin
297
- if @template && @template.formats.present?
298
- @template.formats.first
299
- else
300
- formats.first
301
- end
302
- end
303
-
304
- if @collection
305
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
306
- render_collection
307
- end
308
- else
309
- instrument(:partial, :identifier => identifier) do
310
- render_partial
311
- end
312
- end
313
- end
314
-
315
- private
221
+ include CollectionCaching
316
222
 
317
- def render_collection
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
- result = @template ? collection_with_template : collection_without_template
325
- result.join(spacer).html_safe
223
+ def initialize(lookup_context, options)
224
+ super(lookup_context)
225
+ @options = options
226
+ @locals = @options[:locals] || {}
227
+ @details = extract_details(@options)
326
228
  end
327
229
 
328
- def render_partial
329
- view, locals, block = @view, @locals, @block
330
- object, as = @object, @variable
230
+ def render(partial, context, block)
231
+ template = find_template(partial, template_keys(partial))
331
232
 
332
233
  if !block && (layout = @options[:layout])
333
- layout = find_template(layout.to_s, @template_keys)
334
- end
335
-
336
- object = locals[as] if object.nil? # Respect object when object is false
337
- locals[as] = object
338
-
339
- content = @template.render(view, locals) do |*name|
340
- view._layout_for(*name, &block)
234
+ layout = find_template(layout.to_s, template_keys(partial))
341
235
  end
342
236
 
343
- content = layout.render(view, locals){ content } if layout
344
- content
237
+ render_partial_template(context, @locals, template, layout, block)
345
238
  end
346
239
 
347
240
  private
348
-
349
- # Sets up instance variables needed for rendering a partial. This method
350
- # finds the options and details and extracts them. The method also contains
351
- # logic that handles the type of object passed in as the partial.
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
377
-
378
- if @collection
379
- paths = @collection_data = @collection.map { |o| partial_path(o) }
380
- @path = paths.uniq.one? ? paths.first : nil
381
- else
382
- @path = partial_path
383
- end
384
- end
385
-
386
- if as = options[:as]
387
- raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
388
- as = as.to_sym
389
- end
390
-
391
- if @path
392
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
393
- @template_keys = retrieve_template_keys
394
- else
395
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
396
- end
397
-
398
- self
399
- end
400
-
401
- def collection_from_options
402
- if @options.key?(:collection)
403
- collection = @options[:collection]
404
- collection.respond_to?(:to_ary) ? collection.to_ary : []
405
- end
406
- end
407
-
408
- def collection_from_object
409
- @object.to_ary if @object.respond_to?(:to_ary)
410
- end
411
-
412
- def find_partial
413
- find_template(@path, @template_keys) if @path
414
- end
415
-
416
- def find_template(path, locals)
417
- prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
418
- @lookup_context.find_template(path, prefixes, true, locals, @details)
419
- end
420
-
421
- def collection_with_template
422
- view, locals, template = @view, @locals, @template
423
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
424
-
425
- if layout = @options[:layout]
426
- layout = find_template(layout, @template_keys)
427
- end
428
-
429
- partial_iteration = PartialIteration.new(@collection.size)
430
- locals[iteration] = partial_iteration
431
-
432
- @collection.map do |object|
433
- locals[as] = object
434
- locals[counter] = partial_iteration.index
435
-
436
- content = template.render(view, locals)
437
- content = layout.render(view, locals) { content } if layout
438
- partial_iteration.iterate!
439
- content
440
- end
441
- end
442
-
443
- def collection_without_template
444
- view, locals, collection_data = @view, @locals, @collection_data
445
- cache = {}
446
- keys = @locals.keys
447
-
448
- partial_iteration = PartialIteration.new(@collection.size)
449
-
450
- @collection.map do |object|
451
- index = partial_iteration.index
452
- path, as, counter, iteration = collection_data[index]
453
-
454
- locals[as] = object
455
- locals[counter] = index
456
- locals[iteration] = partial_iteration
457
-
458
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
459
- content = template.render(view, locals)
460
- partial_iteration.iterate!
461
- content
462
- end
463
- end
464
-
465
- # Obtains the path to where the object's partial is located. If the object
466
- # responds to +to_partial_path+, then +to_partial_path+ will be called and
467
- # will provide the path. If the object does not respond to +to_partial_path+,
468
- # then an +ArgumentError+ is raised.
469
- #
470
- # If +prefix_partial_path_with_controller_namespace+ is true, then this
471
- # method will prefix the partial paths with a namespace.
472
- def partial_path(object = @object)
473
- object = object.to_model if object.respond_to?(:to_model)
474
-
475
- path = if object.respond_to?(:to_partial_path)
476
- object.to_partial_path
477
- else
478
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
479
- end
480
-
481
- if @view.prefix_partial_path_with_controller_namespace
482
- prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
483
- else
484
- path
241
+ def template_keys(_)
242
+ @locals.keys
485
243
  end
486
- end
487
-
488
- def prefixed_partial_names
489
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
490
- end
491
-
492
- def merge_prefix_into_object_path(prefix, object_path)
493
- if prefix.include?(?/) && object_path.include?(?/)
494
- prefixes = []
495
- prefix_array = File.dirname(prefix).split('/')
496
- object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
497
244
 
498
- prefix_array.each_with_index do |dir, index|
499
- break if dir == object_path_array[index]
500
- prefixes << dir
245
+ def render_partial_template(view, locals, template, layout, block)
246
+ ActiveSupport::Notifications.instrument(
247
+ "render_partial.action_view",
248
+ identifier: template.identifier,
249
+ layout: layout && layout.virtual_path
250
+ ) do |payload|
251
+ content = template.render(view, locals, add_to_stack: !block) do |*name|
252
+ view._layout_for(*name, &block)
253
+ end
254
+
255
+ content = layout.render(view, locals) { content } if layout
256
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
257
+ build_rendered_template(content, template)
501
258
  end
502
-
503
- (prefixes << object_path).join("/")
504
- else
505
- object_path
506
259
  end
507
- end
508
260
 
509
- def retrieve_template_keys
510
- keys = @locals.keys
511
- keys << @variable if @has_object || @collection
512
- if @collection
513
- keys << @variable_counter
514
- keys << @variable_iteration
261
+ def find_template(path, locals)
262
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
263
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
515
264
  end
516
- keys
517
- end
518
-
519
- def retrieve_variable(path, as)
520
- variable = as || begin
521
- base = path[-1] == "/" ? "" : File.basename(path)
522
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
523
- $1.to_sym
524
- end
525
- if @collection
526
- variable_counter = :"#{variable}_counter"
527
- variable_iteration = :"#{variable}_iteration"
528
- end
529
- [variable, variable_counter, variable_iteration]
530
- end
531
-
532
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
533
- "make sure your partial name starts with underscore, " +
534
- "and is followed by any combination of letters, numbers and underscores."
535
-
536
- OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
537
- "make sure it starts with lowercase letter, " +
538
- "and is followed by any combination of letters, numbers and underscores."
539
-
540
- def raise_invalid_identifier(path)
541
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
542
- end
543
-
544
- def raise_invalid_option_as(as)
545
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
546
- end
547
265
  end
548
266
  end