actionview 5.0.0.beta2 → 5.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -21
- data/lib/action_view/dependency_tracker.rb +13 -11
- data/lib/action_view/digestor.rb +25 -22
- data/lib/action_view/gem_version.rb +1 -1
- data/lib/action_view/helpers/cache_helper.rb +15 -30
- data/lib/action_view/helpers/form_tag_helper.rb +3 -3
- data/lib/action_view/helpers/tag_helper.rb +1 -0
- data/lib/action_view/helpers/url_helper.rb +40 -3
- data/lib/action_view/log_subscriber.rb +17 -1
- data/lib/action_view/lookup_context.rb +25 -11
- data/lib/action_view/renderer/abstract_renderer.rb +6 -2
- data/lib/action_view/renderer/partial_renderer.rb +12 -12
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +13 -34
- data/lib/action_view/tasks/dependencies.rake +2 -2
- data/lib/action_view/template.rb +0 -23
- data/lib/action_view/template/error.rb +6 -2
- data/lib/action_view/template/handlers/erb.rb +0 -25
- metadata +9 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10ff10399f3daad3dd51ca76efc01f2ba03ac135
|
4
|
+
data.tar.gz: d9d94bc8fb7bdc057093ff1ad2a6d1df9e7bf544
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d8fec2801766ff1fc3052e57be09a240280a1b28f3327c6844a312f828a8216e369c50c823fbf8d4608e5e521231cc4297650618c416047e02134c3dcf4257d
|
7
|
+
data.tar.gz: 749328a74e11735391283ca4741a6c1d1a35454726bc903fc29ffa354574ad84a61b13ea4ba079513e35cd3e7b3d30c190e408b08abf8c9f4b3c65d76bade99e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
|
2
|
+
|
3
|
+
* Collection rendering can cache and fetch multiple partials at once.
|
4
|
+
|
5
|
+
Collections rendered as:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
<%= render partial: 'notifications/notification', collection: @notifications, as: :notification, cached: true %>
|
9
|
+
```
|
10
|
+
|
11
|
+
will read several partials from cache at once. The templates in the collection
|
12
|
+
that haven't been cached already will automatically be written to cache. Works
|
13
|
+
great alongside individual template fragment caching. For instance if the
|
14
|
+
template the collection renders is cached like:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# notifications/_notification.html.erb
|
18
|
+
<% cache notification do %>
|
19
|
+
<%# ... %>
|
20
|
+
<% end %>
|
21
|
+
```
|
22
|
+
|
23
|
+
Then any collection renders shares that cache when attempting to read multiple
|
24
|
+
ones at once.
|
25
|
+
|
26
|
+
*Kasper Timm Hansen*
|
27
|
+
|
28
|
+
* Add support for nested hashes/arrays to `:params` option of `button_to` helper.
|
29
|
+
|
30
|
+
*James Coleman*
|
31
|
+
|
1
32
|
## Rails 5.0.0.beta2 (February 01, 2016) ##
|
2
33
|
|
3
34
|
* Fix stripping the digest from the automatically generated img tag alt
|
@@ -68,7 +99,7 @@
|
|
68
99
|
|
69
100
|
*Vasiliy Ermolovich*
|
70
101
|
|
71
|
-
* Add a `hidden_field` on the `collection_radio_buttons` to avoid raising
|
102
|
+
* Add a `hidden_field` on the `collection_radio_buttons` to avoid raising an error
|
72
103
|
when the only input on the form is the `collection_radio_buttons`.
|
73
104
|
|
74
105
|
*Mauro George*
|
@@ -193,26 +224,6 @@
|
|
193
224
|
|
194
225
|
*Ulisses Almeida*
|
195
226
|
|
196
|
-
* Collection rendering automatically caches and fetches multiple partials.
|
197
|
-
|
198
|
-
Collections rendered as:
|
199
|
-
|
200
|
-
```ruby
|
201
|
-
<%= render @notifications %>
|
202
|
-
<%= render partial: 'notifications/notification', collection: @notifications, as: :notification %>
|
203
|
-
```
|
204
|
-
|
205
|
-
will now read several partials from cache at once, if the template starts with a cache call:
|
206
|
-
|
207
|
-
```ruby
|
208
|
-
# notifications/_notification.html.erb
|
209
|
-
<% cache notification do %>
|
210
|
-
<%# ... %>
|
211
|
-
<% end %>
|
212
|
-
```
|
213
|
-
|
214
|
-
*Kasper Timm Hansen*
|
215
|
-
|
216
227
|
* Fixed a dependency tracker bug that caused template dependencies not
|
217
228
|
count layouts as dependencies for partials.
|
218
229
|
|
@@ -7,18 +7,20 @@ module ActionView
|
|
7
7
|
|
8
8
|
def self.find_dependencies(name, template, view_paths = nil)
|
9
9
|
tracker = @trackers[template.handler]
|
10
|
-
return [] unless tracker
|
10
|
+
return [] unless tracker
|
11
11
|
|
12
|
-
|
13
|
-
tracker.call(name, template, view_paths)
|
14
|
-
else
|
15
|
-
tracker.call(name, template)
|
16
|
-
end
|
12
|
+
tracker.call(name, template, view_paths)
|
17
13
|
end
|
18
14
|
|
19
15
|
def self.register_tracker(extension, tracker)
|
20
16
|
handler = Template.handler_for_extension(extension)
|
21
|
-
|
17
|
+
if tracker.respond_to?(:supports_view_paths?)
|
18
|
+
@trackers[handler] = tracker
|
19
|
+
else
|
20
|
+
@trackers[handler] = lambda { |name, template, _|
|
21
|
+
tracker.call(name, template)
|
22
|
+
}
|
23
|
+
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def self.remove_tracker(handler)
|
@@ -151,11 +153,11 @@ module ActionView
|
|
151
153
|
def resolve_directories(wildcard_dependencies)
|
152
154
|
return [] unless @view_paths
|
153
155
|
|
154
|
-
wildcard_dependencies.
|
155
|
-
@view_paths.find_all_with_query(query).
|
156
|
-
|
156
|
+
wildcard_dependencies.flat_map { |query, templates|
|
157
|
+
@view_paths.find_all_with_query(query).map do |template|
|
158
|
+
"#{File.dirname(query)}/#{File.basename(template).split('.').first}"
|
157
159
|
end
|
158
|
-
|
160
|
+
}.sort
|
159
161
|
end
|
160
162
|
|
161
163
|
def explicit_dependencies
|
data/lib/action_view/digestor.rb
CHANGED
@@ -4,13 +4,11 @@ require 'monitor'
|
|
4
4
|
|
5
5
|
module ActionView
|
6
6
|
class Digestor
|
7
|
-
cattr_reader(:cache)
|
8
|
-
@@cache = Concurrent::Map.new
|
9
7
|
@@digest_monitor = Monitor.new
|
10
8
|
|
11
9
|
class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
|
12
10
|
def call(env)
|
13
|
-
ActionView::
|
11
|
+
ActionView::LookupContext::DetailsKey.clear
|
14
12
|
app.call(env)
|
15
13
|
end
|
16
14
|
end
|
@@ -22,72 +20,77 @@ module ActionView
|
|
22
20
|
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
|
23
21
|
# * <tt>dependencies</tt> - An array of dependent views
|
24
22
|
# * <tt>partial</tt> - Specifies whether the template is a partial
|
25
|
-
def digest(options)
|
26
|
-
options.assert_valid_keys(:
|
23
|
+
def digest(name:, finder:, **options)
|
24
|
+
options.assert_valid_keys(:dependencies, :partial)
|
27
25
|
|
28
|
-
cache_key = ([
|
26
|
+
cache_key = ([ name ].compact + Array.wrap(options[:dependencies])).join('.')
|
29
27
|
|
30
28
|
# this is a correctly done double-checked locking idiom
|
31
29
|
# (Concurrent::Map's lookups have volatile semantics)
|
32
|
-
|
33
|
-
|
34
|
-
compute_and_store_digest(cache_key, options)
|
30
|
+
finder.digest_cache[cache_key] || @@digest_monitor.synchronize do
|
31
|
+
finder.digest_cache.fetch(cache_key) do # re-check under lock
|
32
|
+
compute_and_store_digest(cache_key, name, finder, options)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
39
37
|
private
|
40
|
-
def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock
|
41
|
-
klass = if options[:partial] ||
|
38
|
+
def compute_and_store_digest(cache_key, name, finder, options) # called under @@digest_monitor lock
|
39
|
+
klass = if options[:partial] || name.include?("/_")
|
42
40
|
# Prevent re-entry or else recursive templates will blow the stack.
|
43
41
|
# There is no need to worry about other threads seeing the +false+ value,
|
44
42
|
# as they will then have to wait for this thread to let go of the @@digest_monitor lock.
|
45
|
-
pre_stored =
|
43
|
+
pre_stored = finder.digest_cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
|
46
44
|
PartialDigestor
|
47
45
|
else
|
48
46
|
Digestor
|
49
47
|
end
|
50
48
|
|
51
|
-
|
49
|
+
finder.digest_cache[cache_key] = stored_digest = klass.new(name, finder, options).digest
|
52
50
|
ensure
|
53
51
|
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
|
54
|
-
|
52
|
+
finder.digest_cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
56
|
attr_reader :name, :finder, :options
|
59
57
|
|
60
|
-
def initialize(options)
|
61
|
-
@name, @finder =
|
62
|
-
@options = options
|
58
|
+
def initialize(name, finder, options = {})
|
59
|
+
@name, @finder = name, finder
|
60
|
+
@options = options
|
63
61
|
end
|
64
62
|
|
65
63
|
def digest
|
66
64
|
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
|
67
|
-
logger.
|
65
|
+
logger.debug " Cache digest for #{template.inspect}: #{digest}"
|
68
66
|
end
|
69
67
|
rescue ActionView::MissingTemplate
|
70
|
-
logger.
|
68
|
+
logger.error " Couldn't find template for digesting: #{name}"
|
71
69
|
''
|
72
70
|
end
|
73
71
|
|
74
72
|
def dependencies
|
75
73
|
DependencyTracker.find_dependencies(name, template, finder.view_paths)
|
76
74
|
rescue ActionView::MissingTemplate
|
77
|
-
logger.
|
75
|
+
logger.error " '#{name}' file doesn't exist, so no dependencies"
|
78
76
|
[]
|
79
77
|
end
|
80
78
|
|
81
79
|
def nested_dependencies
|
82
80
|
dependencies.collect do |dependency|
|
83
|
-
dependencies = PartialDigestor.new(
|
81
|
+
dependencies = PartialDigestor.new(dependency, finder).nested_dependencies
|
84
82
|
dependencies.any? ? { dependency => dependencies } : dependency
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
88
86
|
private
|
87
|
+
class NullLogger
|
88
|
+
def self.debug(_); end
|
89
|
+
def self.error(_); end
|
90
|
+
end
|
91
|
+
|
89
92
|
def logger
|
90
|
-
ActionView::Base.logger
|
93
|
+
ActionView::Base.logger || NullLogger
|
91
94
|
end
|
92
95
|
|
93
96
|
def logical_name
|
@@ -126,44 +126,29 @@ module ActionView
|
|
126
126
|
#
|
127
127
|
# Now all you have to do is change that timestamp when the helper method changes.
|
128
128
|
#
|
129
|
-
# ===
|
129
|
+
# === Collection Caching
|
130
130
|
#
|
131
|
-
# When rendering
|
131
|
+
# When rendering a collection of objects that each use the same partial, a `cached`
|
132
|
+
# option can be passed.
|
133
|
+
# For collections rendered such:
|
132
134
|
#
|
133
|
-
# <%= render @notifications %>
|
134
|
-
# <%= render partial: 'notifications/notification', collection: @notifications %>
|
135
|
+
# <%= render partial: 'notifications/notification', collection: @notifications, cached: true %>
|
135
136
|
#
|
136
|
-
#
|
137
|
+
# The `cached: true` will make Action View's rendering read several templates
|
138
|
+
# from cache at once instead of one call per template.
|
137
139
|
#
|
138
|
-
#
|
139
|
-
# <%= notification.name %>
|
140
|
-
# <% end %>
|
141
|
-
#
|
142
|
-
# The collection can then automatically use any cached renders for that
|
143
|
-
# template by reading them at once instead of one by one.
|
144
|
-
#
|
145
|
-
# See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for
|
146
|
-
# more information on what cache calls make a template eligible for this
|
147
|
-
# collection caching.
|
148
|
-
#
|
149
|
-
# The automatic cache multi read can be turned off like so:
|
140
|
+
# Templates in the collection not already cached are written to cache.
|
150
141
|
#
|
151
|
-
#
|
142
|
+
# Works great alongside individual template fragment caching.
|
143
|
+
# For instance if the template the collection renders is cached like:
|
152
144
|
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
# mentioned above, you can still benefit from collection caching by
|
157
|
-
# adding a special comment format anywhere in the template, like:
|
158
|
-
#
|
159
|
-
# <%# Template Collection: notification %>
|
160
|
-
# <% my_helper_that_calls_cache(some_arg, notification) do %>
|
161
|
-
# <%= notification.name %>
|
145
|
+
# # notifications/_notification.html.erb
|
146
|
+
# <% cache notification do %>
|
147
|
+
# <%# ... %>
|
162
148
|
# <% end %>
|
163
149
|
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
# You can only declare one collection in a partial template file.
|
150
|
+
# Any collection renders will find those cached templates when attempting
|
151
|
+
# to read multiple templates at once.
|
167
152
|
def cache(name = {}, options = {}, &block)
|
168
153
|
if controller.respond_to?(:perform_caching) && controller.perform_caching
|
169
154
|
name_options = options.slice(:skip_digest, :virtual_path)
|
@@ -862,13 +862,13 @@ module ActionView
|
|
862
862
|
|
863
863
|
def extra_tags_for_form(html_options)
|
864
864
|
authenticity_token = html_options.delete("authenticity_token")
|
865
|
-
method = html_options.delete("method").to_s
|
865
|
+
method = html_options.delete("method").to_s.downcase
|
866
866
|
|
867
867
|
method_tag = case method
|
868
|
-
when
|
868
|
+
when 'get'
|
869
869
|
html_options["method"] = "get"
|
870
870
|
''
|
871
|
-
when
|
871
|
+
when 'post', ''
|
872
872
|
html_options["method"] = "post"
|
873
873
|
token_tag(authenticity_token, form_options: {
|
874
874
|
action: html_options["action"],
|
@@ -312,7 +312,8 @@ module ActionView
|
|
312
312
|
form_options[:'data-remote'] = true if remote
|
313
313
|
|
314
314
|
request_token_tag = if form_method == 'post'
|
315
|
-
|
315
|
+
request_method = method.empty? ? 'post' : method
|
316
|
+
token_tag(nil, form_options: { action: url, method: request_method })
|
316
317
|
else
|
317
318
|
''
|
318
319
|
end
|
@@ -329,8 +330,8 @@ module ActionView
|
|
329
330
|
|
330
331
|
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
|
331
332
|
if params
|
332
|
-
params.each do |
|
333
|
-
inner_tags.safe_concat tag(:input, type: "hidden", name:
|
333
|
+
to_form_params(params).each do |param|
|
334
|
+
inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
|
334
335
|
end
|
335
336
|
end
|
336
337
|
content_tag('form', inner_tags, form_options)
|
@@ -595,6 +596,42 @@ module ActionView
|
|
595
596
|
def method_tag(method)
|
596
597
|
tag('input', type: 'hidden', name: '_method', value: method.to_s)
|
597
598
|
end
|
599
|
+
|
600
|
+
# Returns an array of hashes each containing :name and :value keys
|
601
|
+
# suitable for use as the names and values of form input fields:
|
602
|
+
#
|
603
|
+
# to_form_params(name: 'David', nationality: 'Danish')
|
604
|
+
# # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
|
605
|
+
#
|
606
|
+
# to_form_params(country: {name: 'Denmark'})
|
607
|
+
# # => [{name: 'country[name]', value: 'Denmark'}]
|
608
|
+
#
|
609
|
+
# to_form_params(countries: ['Denmark', 'Sweden']})
|
610
|
+
# # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
|
611
|
+
#
|
612
|
+
# An optional namespace can be passed to enclose key names:
|
613
|
+
#
|
614
|
+
# to_form_params({ name: 'Denmark' }, 'country')
|
615
|
+
# # => [{name: 'country[name]', value: 'Denmark'}]
|
616
|
+
def to_form_params(attribute, namespace = nil) # :nodoc:
|
617
|
+
params = []
|
618
|
+
case attribute
|
619
|
+
when Hash
|
620
|
+
attribute.each do |key, value|
|
621
|
+
prefix = namespace ? "#{namespace}[#{key}]" : key
|
622
|
+
params.push(*to_form_params(value, prefix))
|
623
|
+
end
|
624
|
+
when Array
|
625
|
+
array_prefix = "#{namespace}[]"
|
626
|
+
attribute.each do |value|
|
627
|
+
params.push(*to_form_params(value, array_prefix))
|
628
|
+
end
|
629
|
+
else
|
630
|
+
params << { name: namespace, value: attribute.to_param }
|
631
|
+
end
|
632
|
+
|
633
|
+
params.sort_by { |pair| pair[:name] }
|
634
|
+
end
|
598
635
|
end
|
599
636
|
end
|
600
637
|
end
|
@@ -20,7 +20,15 @@ module ActionView
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
alias :render_partial :render_template
|
23
|
-
|
23
|
+
|
24
|
+
def render_collection(event)
|
25
|
+
identifier = event.payload[:identifier] || 'templates'
|
26
|
+
|
27
|
+
info do
|
28
|
+
" Rendered collection of #{from_rails_root(identifier)}" \
|
29
|
+
" #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
|
30
|
+
end
|
31
|
+
end
|
24
32
|
|
25
33
|
def logger
|
26
34
|
ActionView::Base.logger
|
@@ -38,6 +46,14 @@ module ActionView
|
|
38
46
|
def rails_root
|
39
47
|
@root ||= "#{Rails.root}/"
|
40
48
|
end
|
49
|
+
|
50
|
+
def render_count(payload)
|
51
|
+
if payload[:cache_hits]
|
52
|
+
"[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
|
53
|
+
else
|
54
|
+
"[#{payload[:count]} times]"
|
55
|
+
end
|
56
|
+
end
|
41
57
|
end
|
42
58
|
end
|
43
59
|
|
@@ -22,7 +22,7 @@ module ActionView
|
|
22
22
|
|
23
23
|
def self.register_detail(name, &block)
|
24
24
|
self.registered_details << name
|
25
|
-
|
25
|
+
Accessors::DEFAULT_PROCS[name] = block
|
26
26
|
|
27
27
|
Accessors.send :define_method, :"default_#{name}", &block
|
28
28
|
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
@@ -34,16 +34,12 @@ module ActionView
|
|
34
34
|
value = value.present? ? Array(value) : default_#{name}
|
35
35
|
_set_detail(:#{name}, value) if value != @details[:#{name}]
|
36
36
|
end
|
37
|
-
|
38
|
-
remove_possible_method :initialize_details
|
39
|
-
def initialize_details(details)
|
40
|
-
#{initialize.join("\n")}
|
41
|
-
end
|
42
37
|
METHOD
|
43
38
|
end
|
44
39
|
|
45
40
|
# Holds accessors for the registered details.
|
46
41
|
module Accessors #:nodoc:
|
42
|
+
DEFAULT_PROCS = {}
|
47
43
|
end
|
48
44
|
|
49
45
|
register_detail(:locale) do
|
@@ -59,9 +55,7 @@ module ActionView
|
|
59
55
|
|
60
56
|
class DetailsKey #:nodoc:
|
61
57
|
alias :eql? :equal?
|
62
|
-
alias :object_hash :hash
|
63
58
|
|
64
|
-
attr_reader :hash
|
65
59
|
@details_keys = Concurrent::Map.new
|
66
60
|
|
67
61
|
def self.get(details)
|
@@ -76,8 +70,16 @@ module ActionView
|
|
76
70
|
@details_keys.clear
|
77
71
|
end
|
78
72
|
|
73
|
+
def self.empty?; @details_keys.empty?; end
|
74
|
+
|
75
|
+
def self.digest_caches
|
76
|
+
@details_keys.values.map(&:digest_cache)
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :digest_cache
|
80
|
+
|
79
81
|
def initialize
|
80
|
-
@
|
82
|
+
@digest_cache = Concurrent::Map.new
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
@@ -195,15 +197,27 @@ module ActionView
|
|
195
197
|
include ViewPaths
|
196
198
|
|
197
199
|
def initialize(view_paths, details = {}, prefixes = [])
|
198
|
-
@
|
200
|
+
@details_key = nil
|
199
201
|
@cache = true
|
200
202
|
@prefixes = prefixes
|
201
203
|
@rendered_format = nil
|
202
204
|
|
205
|
+
@details = initialize_details({}, details)
|
203
206
|
self.view_paths = view_paths
|
204
|
-
initialize_details(details)
|
205
207
|
end
|
206
208
|
|
209
|
+
def digest_cache
|
210
|
+
details_key.digest_cache
|
211
|
+
end
|
212
|
+
|
213
|
+
def initialize_details(target, details)
|
214
|
+
registered_details.each do |k|
|
215
|
+
target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
|
216
|
+
end
|
217
|
+
target
|
218
|
+
end
|
219
|
+
private :initialize_details
|
220
|
+
|
207
221
|
# Override formats= to expand ["*/*"] values and automatically
|
208
222
|
# add :html as fallback to :js.
|
209
223
|
def formats=(values)
|
@@ -35,8 +35,12 @@ module ActionView
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def instrument(name, options
|
39
|
-
|
38
|
+
def instrument(name, **options)
|
39
|
+
options[:identifier] ||= (@template && @template.identifier) || @path
|
40
|
+
|
41
|
+
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
|
42
|
+
yield payload
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
def prepend_formats(formats)
|
@@ -294,7 +294,7 @@ module ActionView
|
|
294
294
|
|
295
295
|
def render(context, options, block)
|
296
296
|
setup(context, options, block)
|
297
|
-
|
297
|
+
@template = find_partial
|
298
298
|
|
299
299
|
@lookup_context.rendered_format ||= begin
|
300
300
|
if @template && @template.formats.present?
|
@@ -305,11 +305,9 @@ module ActionView
|
|
305
305
|
end
|
306
306
|
|
307
307
|
if @collection
|
308
|
-
|
309
|
-
render_collection
|
310
|
-
end
|
308
|
+
render_collection
|
311
309
|
else
|
312
|
-
instrument(:partial
|
310
|
+
instrument(:partial) do
|
313
311
|
render_partial
|
314
312
|
end
|
315
313
|
end
|
@@ -318,15 +316,17 @@ module ActionView
|
|
318
316
|
private
|
319
317
|
|
320
318
|
def render_collection
|
321
|
-
|
319
|
+
instrument(:collection, count: @collection.size) do |payload|
|
320
|
+
return nil if @collection.blank?
|
322
321
|
|
323
|
-
|
324
|
-
|
325
|
-
|
322
|
+
if @options.key?(:spacer_template)
|
323
|
+
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
|
324
|
+
end
|
326
325
|
|
327
|
-
|
328
|
-
|
329
|
-
|
326
|
+
cache_collection_render(payload) do
|
327
|
+
@template ? collection_with_template : collection_without_template
|
328
|
+
end.join(spacer).html_safe
|
329
|
+
end
|
330
330
|
end
|
331
331
|
|
332
332
|
def render_partial
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/object/try'
|
2
|
-
|
3
1
|
module ActionView
|
4
2
|
module CollectionCaching # :nodoc:
|
5
3
|
extend ActiveSupport::Concern
|
@@ -11,42 +9,25 @@ module ActionView
|
|
11
9
|
end
|
12
10
|
|
13
11
|
private
|
14
|
-
def cache_collection_render
|
15
|
-
return yield unless
|
12
|
+
def cache_collection_render(instrumentation_payload)
|
13
|
+
return yield unless @options[:cached]
|
16
14
|
|
17
15
|
keyed_collection = collection_by_cache_keys
|
18
|
-
|
16
|
+
cached_partials = collection_cache.read_multi(*keyed_collection.keys)
|
17
|
+
instrumentation_payload[:cache_hits] = cached_partials.size
|
19
18
|
|
20
|
-
@collection = keyed_collection.reject { |key, _|
|
21
|
-
rendered_partials = @collection.
|
19
|
+
@collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
|
20
|
+
rendered_partials = @collection.empty? ? [] : yield
|
22
21
|
|
23
|
-
|
24
|
-
|
22
|
+
index = 0
|
23
|
+
fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
|
24
|
+
rendered_partials[index].tap { index += 1 }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def cache_collection?
|
29
|
-
@options.fetch(:cache, automatic_cache_eligible?)
|
30
|
-
end
|
31
|
-
|
32
|
-
def automatic_cache_eligible?
|
33
|
-
single_template_render? && !callable_cache_key? &&
|
34
|
-
@template.eligible_for_collection_caching?(as: @options[:as])
|
35
|
-
end
|
36
|
-
|
37
|
-
def single_template_render?
|
38
|
-
@template # Template is only set when a collection renders one template.
|
39
|
-
end
|
40
|
-
|
41
|
-
def callable_cache_key?
|
42
|
-
@options[:cache].respond_to?(:call)
|
43
|
-
end
|
44
|
-
|
45
28
|
def collection_by_cache_keys
|
46
|
-
seed = callable_cache_key? ? @options[:cache] : ->(i) { i }
|
47
|
-
|
48
29
|
@collection.each_with_object({}) do |item, hash|
|
49
|
-
hash[expanded_cache_key(
|
30
|
+
hash[expanded_cache_key(item)] = item
|
50
31
|
end
|
51
32
|
end
|
52
33
|
|
@@ -56,12 +37,10 @@ module ActionView
|
|
56
37
|
end
|
57
38
|
|
58
39
|
def fetch_or_cache_partial(cached_partials, order_by:)
|
59
|
-
|
60
|
-
|
61
|
-
order_by.map do |key|
|
62
|
-
cached_partials.fetch(key) do
|
40
|
+
order_by.map do |cache_key|
|
41
|
+
cached_partials.fetch(cache_key) do
|
63
42
|
yield.tap do |rendered_partial|
|
64
|
-
collection_cache.write(
|
43
|
+
collection_cache.write(cache_key, rendered_partial)
|
65
44
|
end
|
66
45
|
end
|
67
46
|
end
|
@@ -2,13 +2,13 @@ namespace :cache_digests do
|
|
2
2
|
desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
|
3
3
|
task :nested_dependencies => :environment do
|
4
4
|
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
|
5
|
-
puts JSON.pretty_generate ActionView::Digestor.new(
|
5
|
+
puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).nested_dependencies
|
6
6
|
end
|
7
7
|
|
8
8
|
desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
|
9
9
|
task :dependencies => :environment do
|
10
10
|
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
|
11
|
-
puts JSON.pretty_generate ActionView::Digestor.new(
|
11
|
+
puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).dependencies
|
12
12
|
end
|
13
13
|
|
14
14
|
class CacheDigests
|
data/lib/action_view/template.rb
CHANGED
@@ -130,7 +130,6 @@ module ActionView
|
|
130
130
|
@source = source
|
131
131
|
@identifier = identifier
|
132
132
|
@handler = handler
|
133
|
-
@cache_name = extract_resource_cache_name
|
134
133
|
@compiled = false
|
135
134
|
@original_encoding = nil
|
136
135
|
@locals = details[:locals] || []
|
@@ -166,10 +165,6 @@ module ActionView
|
|
166
165
|
@type ||= Types[@formats.first] if @formats.first
|
167
166
|
end
|
168
167
|
|
169
|
-
def eligible_for_collection_caching?(as: nil)
|
170
|
-
@cache_name == (as || inferred_cache_name).to_s
|
171
|
-
end
|
172
|
-
|
173
168
|
# Receives a view object and return a template similar to self by using @virtual_path.
|
174
169
|
#
|
175
170
|
# This method is useful if you have a template object but it does not contain its source
|
@@ -355,23 +350,5 @@ module ActionView
|
|
355
350
|
ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
|
356
351
|
end
|
357
352
|
end
|
358
|
-
|
359
|
-
EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
|
360
|
-
|
361
|
-
def extract_resource_cache_name
|
362
|
-
if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
|
363
|
-
match[:resource_name]
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
def resource_cache_call_match
|
368
|
-
if @handler.respond_to?(:resource_cache_call_pattern)
|
369
|
-
@source.match(@handler.resource_cache_call_pattern)
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
def inferred_cache_name
|
374
|
-
@inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
|
375
|
-
end
|
376
353
|
end
|
377
354
|
end
|
@@ -59,6 +59,9 @@ module ActionView
|
|
59
59
|
class Error < ActionViewError #:nodoc:
|
60
60
|
SOURCE_CODE_RADIUS = 3
|
61
61
|
|
62
|
+
# Override to prevent #cause resetting during re-raise.
|
63
|
+
attr_reader :cause
|
64
|
+
|
62
65
|
def initialize(template, original_exception = nil)
|
63
66
|
if original_exception
|
64
67
|
ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
|
@@ -67,6 +70,7 @@ module ActionView
|
|
67
70
|
|
68
71
|
super($!.message)
|
69
72
|
set_backtrace($!.backtrace)
|
73
|
+
@cause = $!
|
70
74
|
@template, @sub_templates = template, nil
|
71
75
|
end
|
72
76
|
|
@@ -131,13 +135,13 @@ module ActionView
|
|
131
135
|
end
|
132
136
|
|
133
137
|
def formatted_code_for(source_code, line_counter, indent, output)
|
134
|
-
start_value = (output == :html) ? {} :
|
138
|
+
start_value = (output == :html) ? {} : []
|
135
139
|
source_code.inject(start_value) do |result, line|
|
136
140
|
line_counter += 1
|
137
141
|
if output == :html
|
138
142
|
result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
|
139
143
|
else
|
140
|
-
result << "%#{indent}s: %s
|
144
|
+
result << "%#{indent}s: %s" % [line_counter, line]
|
141
145
|
end
|
142
146
|
end
|
143
147
|
end
|
@@ -123,31 +123,6 @@ module ActionView
|
|
123
123
|
).src
|
124
124
|
end
|
125
125
|
|
126
|
-
# Returns Regexp to extract a cached resource's name from a cache call at the
|
127
|
-
# first line of a template.
|
128
|
-
# The extracted cache name is captured as :resource_name.
|
129
|
-
#
|
130
|
-
# <% cache notification do %> # => notification
|
131
|
-
#
|
132
|
-
# The pattern should support templates with a beginning comment:
|
133
|
-
#
|
134
|
-
# <%# Still extractable even though there's a comment %>
|
135
|
-
# <% cache notification do %> # => notification
|
136
|
-
#
|
137
|
-
# But fail to extract a name if a resource association is cached.
|
138
|
-
#
|
139
|
-
# <% cache notification.event do %> # => nil
|
140
|
-
def resource_cache_call_pattern
|
141
|
-
/\A
|
142
|
-
(?:<%\#.*%>)* # optional initial comment
|
143
|
-
\s* # followed by optional spaces or newlines
|
144
|
-
<%\s*cache[\(\s] # followed by an ERB call to cache
|
145
|
-
\s* # followed by optional spaces or newlines
|
146
|
-
(?<resource_name>\w+) # capture the cache call argument as :resource_name
|
147
|
-
[\s\)] # followed by a space or close paren
|
148
|
-
/xm
|
149
|
-
end
|
150
|
-
|
151
126
|
private
|
152
127
|
|
153
128
|
def valid_encoding(string, encoding)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionview
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.
|
4
|
+
version: 5.0.0.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.0.0.
|
19
|
+
version: 5.0.0.beta3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.0.0.
|
26
|
+
version: 5.0.0.beta3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: builder
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,28 +98,28 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - '='
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: 5.0.0.
|
101
|
+
version: 5.0.0.beta3
|
102
102
|
type: :development
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
105
|
requirements:
|
106
106
|
- - '='
|
107
107
|
- !ruby/object:Gem::Version
|
108
|
-
version: 5.0.0.
|
108
|
+
version: 5.0.0.beta3
|
109
109
|
- !ruby/object:Gem::Dependency
|
110
110
|
name: activemodel
|
111
111
|
requirement: !ruby/object:Gem::Requirement
|
112
112
|
requirements:
|
113
113
|
- - '='
|
114
114
|
- !ruby/object:Gem::Version
|
115
|
-
version: 5.0.0.
|
115
|
+
version: 5.0.0.beta3
|
116
116
|
type: :development
|
117
117
|
prerelease: false
|
118
118
|
version_requirements: !ruby/object:Gem::Requirement
|
119
119
|
requirements:
|
120
120
|
- - '='
|
121
121
|
- !ruby/object:Gem::Version
|
122
|
-
version: 5.0.0.
|
122
|
+
version: 5.0.0.beta3
|
123
123
|
description: Simple, battle-tested conventions and helpers for building web pages.
|
124
124
|
email: david@loudthinking.com
|
125
125
|
executables: []
|
@@ -250,9 +250,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
250
|
requirements:
|
251
251
|
- none
|
252
252
|
rubyforge_project:
|
253
|
-
rubygems_version: 2.5.
|
253
|
+
rubygems_version: 2.5.1
|
254
254
|
signing_key:
|
255
255
|
specification_version: 4
|
256
256
|
summary: Rendering framework putting the V in MVC (part of Rails).
|
257
257
|
test_files: []
|
258
|
-
has_rdoc:
|