actionview 4.2.11.1 → 7.0.2.4
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.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -215
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +116 -43
- data/lib/action_view/buffers.rb +20 -3
- data/lib/action_view/cache_expiry.rb +66 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +21 -122
- data/lib/action_view/digestor.rb +92 -85
- data/lib/action_view/flows.rb +15 -16
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +17 -12
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
- data/lib/action_view/helpers/asset_url_helper.rb +180 -74
- data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
- data/lib/action_view/helpers/cache_helper.rb +156 -43
- data/lib/action_view/helpers/capture_helper.rb +21 -14
- data/lib/action_view/helpers/controller_helper.rb +16 -5
- 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 +288 -132
- data/lib/action_view/helpers/debug_helper.rb +9 -6
- data/lib/action_view/helpers/form_helper.rb +956 -173
- data/lib/action_view/helpers/form_options_helper.rb +178 -97
- data/lib/action_view/helpers/form_tag_helper.rb +220 -101
- data/lib/action_view/helpers/javascript_helper.rb +33 -19
- data/lib/action_view/helpers/number_helper.rb +88 -63
- data/lib/action_view/helpers/output_safety_helper.rb +38 -6
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +31 -32
- data/lib/action_view/helpers/tag_helper.rb +332 -71
- data/lib/action_view/helpers/tags/base.rb +123 -99
- data/lib/action_view/helpers/tags/check_box.rb +21 -20
- 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 +5 -3
- 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 +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- 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 +18 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
- data/lib/action_view/helpers/tags/label.rb +7 -2
- 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 +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 +12 -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 +15 -16
- 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/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +80 -51
- data/lib/action_view/helpers/translation_helper.rb +120 -69
- data/lib/action_view/helpers/url_helper.rb +398 -171
- data/lib/action_view/helpers.rb +29 -27
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +137 -113
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +28 -32
- data/lib/action_view/railtie.rb +74 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +152 -15
- 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/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +51 -333
- data/lib/action_view/renderer/renderer.rb +68 -11
- data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
- data/lib/action_view/renderer/template_renderer.rb +87 -74
- data/lib/action_view/rendering.rb +73 -47
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +35 -24
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +151 -41
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +29 -89
- 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 +14 -10
- data/lib/action_view/template/html.rb +12 -13
- 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 +139 -300
- 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 +10 -12
- data/lib/action_view/template/types.rb +28 -26
- data/lib/action_view/template.rb +123 -91
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +70 -53
- data/lib/action_view/testing/resolvers.rb +25 -35
- data/lib/action_view/unbound_template.rb +57 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +16 -11
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +52 -32
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- 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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
53
|
-
# 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.
|
54
29
|
#
|
55
30
|
# == The :as and :object options
|
56
31
|
#
|
57
|
-
# By default
|
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
|
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
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
329
|
-
|
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,
|
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
|
-
|
344
|
-
content
|
237
|
+
render_partial_template(context, @locals, template, layout, block)
|
345
238
|
end
|
346
239
|
|
347
240
|
private
|
348
|
-
|
349
|
-
|
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
|
-
|
499
|
-
|
500
|
-
|
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
|
-
|
510
|
-
|
511
|
-
|
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
|