actionview 6.0.6.1 → 6.1.0.rc1
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 +152 -325
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +20 -51
- data/lib/action_view/cache_expiry.rb +1 -2
- data/lib/action_view/context.rb +0 -1
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +3 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +40 -15
- data/lib/action_view/helpers/asset_url_helper.rb +6 -4
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +10 -16
- data/lib/action_view/helpers/date_helper.rb +4 -4
- data/lib/action_view/helpers/form_helper.rb +59 -17
- data/lib/action_view/helpers/form_options_helper.rb +7 -16
- data/lib/action_view/helpers/form_tag_helper.rb +8 -6
- data/lib/action_view/helpers/javascript_helper.rb +3 -3
- data/lib/action_view/helpers/number_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +11 -3
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +96 -52
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +4 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +1 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +1 -1
- data/lib/action_view/helpers/translation_helper.rb +88 -53
- data/lib/action_view/helpers/url_helper.rb +107 -13
- data/lib/action_view/layouts.rb +3 -2
- data/lib/action_view/log_subscriber.rb +26 -10
- data/lib/action_view/lookup_context.rb +3 -18
- data/lib/action_view/path_set.rb +0 -3
- data/lib/action_view/railtie.rb +35 -46
- data/lib/action_view/renderer/abstract_renderer.rb +93 -14
- data/lib/action_view/renderer/collection_renderer.rb +192 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
- data/lib/action_view/renderer/partial_renderer.rb +20 -282
- data/lib/action_view/renderer/renderer.rb +44 -1
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -1
- data/lib/action_view/renderer/template_renderer.rb +15 -12
- data/lib/action_view/rendering.rb +3 -1
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
- data/lib/action_view/template/handlers/erb.rb +10 -14
- data/lib/action_view/template/handlers.rb +0 -26
- data/lib/action_view/template/html.rb +1 -11
- data/lib/action_view/template/raw_file.rb +0 -3
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +82 -40
- data/lib/action_view/template/text.rb +0 -3
- data/lib/action_view/template.rb +9 -49
- data/lib/action_view/test_case.rb +18 -25
- data/lib/action_view/testing/resolvers.rb +10 -31
- data/lib/action_view/unbound_template.rb +3 -3
- data/lib/action_view/view_paths.rb +34 -36
- data/lib/action_view.rb +4 -1
- data/lib/assets/compiled/rails-ujs.js +1 -1
- metadata +20 -18
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "concurrent/map"
|
4
|
-
require "active_support/core_ext/module/remove_method"
|
5
4
|
require "active_support/core_ext/module/attribute_accessors"
|
6
|
-
require "active_support/deprecation"
|
7
5
|
require "action_view/template/resolver"
|
8
6
|
|
9
7
|
module ActionView
|
@@ -16,8 +14,6 @@ module ActionView
|
|
16
14
|
# only once during the request, it speeds up all cache accesses.
|
17
15
|
class LookupContext #:nodoc:
|
18
16
|
attr_accessor :prefixes, :rendered_format
|
19
|
-
deprecate :rendered_format
|
20
|
-
deprecate :rendered_format=
|
21
17
|
|
22
18
|
mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
|
23
19
|
|
@@ -30,7 +26,7 @@ module ActionView
|
|
30
26
|
Accessors.define_method(:"default_#{name}", &block)
|
31
27
|
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
32
28
|
def #{name}
|
33
|
-
@details
|
29
|
+
@details[:#{name}] || []
|
34
30
|
end
|
35
31
|
|
36
32
|
def #{name}=(value)
|
@@ -132,9 +128,6 @@ module ActionView
|
|
132
128
|
end
|
133
129
|
alias :find_template :find
|
134
130
|
|
135
|
-
alias :find_file :find
|
136
|
-
deprecate :find_file
|
137
|
-
|
138
131
|
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
|
139
132
|
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
|
140
133
|
end
|
@@ -155,18 +148,10 @@ module ActionView
|
|
155
148
|
view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
|
156
149
|
|
157
150
|
if block_given?
|
158
|
-
|
159
|
-
Calling `with_fallbacks` with a block is
|
151
|
+
raise ArgumentError, <<~eowarn.squish
|
152
|
+
Calling `with_fallbacks` with a block is not supported. Call methods on
|
160
153
|
the lookup context returned by `with_fallbacks` instead.
|
161
154
|
eowarn
|
162
|
-
|
163
|
-
begin
|
164
|
-
_view_paths = @view_paths
|
165
|
-
@view_paths = view_paths
|
166
|
-
yield
|
167
|
-
ensure
|
168
|
-
@view_paths = _view_paths
|
169
|
-
end
|
170
155
|
else
|
171
156
|
ActionView::LookupContext.new(view_paths, @details, @prefixes)
|
172
157
|
end
|
data/lib/action_view/path_set.rb
CHANGED
data/lib/action_view/railtie.rb
CHANGED
@@ -6,54 +6,47 @@ require "rails"
|
|
6
6
|
module ActionView
|
7
7
|
# = Action View Railtie
|
8
8
|
class Railtie < Rails::Engine # :nodoc:
|
9
|
-
NULL_OPTION = Object.new
|
10
|
-
|
11
9
|
config.action_view = ActiveSupport::OrderedOptions.new
|
12
10
|
config.action_view.embed_authenticity_token_in_remote_forms = nil
|
13
11
|
config.action_view.debug_missing_translation = true
|
14
12
|
config.action_view.default_enforce_utf8 = nil
|
15
|
-
config.action_view.finalize_compiled_template_methods = NULL_OPTION
|
16
13
|
|
17
14
|
config.eager_load_namespaces << ActionView
|
18
15
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
|
23
|
-
end
|
16
|
+
config.after_initialize do |app|
|
17
|
+
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
|
18
|
+
app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
|
24
19
|
end
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
|
30
|
-
end
|
21
|
+
config.after_initialize do |app|
|
22
|
+
form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
|
23
|
+
ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
|
31
24
|
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
|
38
|
-
end
|
26
|
+
config.after_initialize do |app|
|
27
|
+
form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
|
28
|
+
unless form_with_generates_ids.nil?
|
29
|
+
ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
|
47
|
-
end
|
33
|
+
config.after_initialize do |app|
|
34
|
+
default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
|
35
|
+
unless default_enforce_utf8.nil?
|
36
|
+
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
|
48
37
|
end
|
49
38
|
end
|
50
39
|
|
51
|
-
|
40
|
+
config.after_initialize do |app|
|
52
41
|
ActiveSupport.on_load(:action_view) do
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
42
|
+
app.config.action_view.each do |k, v|
|
43
|
+
if k == :raise_on_missing_translations
|
44
|
+
ActiveSupport::Deprecation.warn \
|
45
|
+
"action_view.raise_on_missing_translations is deprecated and will be removed in Rails 6.2. " \
|
46
|
+
"Set i18n.raise_on_missing_translations instead. " \
|
47
|
+
"Note that this new setting also affects how missing translations are handled in controllers."
|
48
|
+
end
|
49
|
+
send "#{k}=", v
|
57
50
|
end
|
58
51
|
end
|
59
52
|
end
|
@@ -62,14 +55,6 @@ module ActionView
|
|
62
55
|
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
|
63
56
|
end
|
64
57
|
|
65
|
-
initializer "action_view.set_configs" do |app|
|
66
|
-
ActiveSupport.on_load(:action_view) do
|
67
|
-
app.config.action_view.each do |k, v|
|
68
|
-
send "#{k}=", v
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
58
|
initializer "action_view.caching" do |app|
|
74
59
|
ActiveSupport.on_load(:action_view) do
|
75
60
|
if app.config.action_view.cache_template_loading.nil?
|
@@ -78,14 +63,6 @@ module ActionView
|
|
78
63
|
end
|
79
64
|
end
|
80
65
|
|
81
|
-
initializer "action_view.per_request_digest_cache" do |app|
|
82
|
-
ActiveSupport.on_load(:action_view) do
|
83
|
-
unless ActionView::Resolver.caching?
|
84
|
-
app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
66
|
initializer "action_view.setup_action_pack" do |app|
|
90
67
|
ActiveSupport.on_load(:action_controller) do
|
91
68
|
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
|
@@ -96,6 +73,18 @@ module ActionView
|
|
96
73
|
PartialRenderer.collection_cache = app.config.action_controller.cache_store
|
97
74
|
end
|
98
75
|
|
76
|
+
config.after_initialize do |app|
|
77
|
+
enable_caching = if app.config.action_view.cache_template_loading.nil?
|
78
|
+
app.config.cache_classes
|
79
|
+
else
|
80
|
+
app.config.action_view.cache_template_loading
|
81
|
+
end
|
82
|
+
|
83
|
+
unless enable_caching
|
84
|
+
app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
99
88
|
rake_tasks do |app|
|
100
89
|
unless app.config.api_only
|
101
90
|
load "action_view/tasks/cache_digests.rake"
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent/map"
|
4
|
+
|
3
5
|
module ActionView
|
4
6
|
# This class defines the interface for a renderer. Each class that
|
5
7
|
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
|
@@ -14,7 +16,7 @@ module ActionView
|
|
14
16
|
#
|
15
17
|
# Whenever the +render+ method is called on the base +Renderer+ class, a new
|
16
18
|
# renderer object of the correct type is created, and the +render+ method on
|
17
|
-
# that new object is called in turn. This abstracts the
|
19
|
+
# that new object is called in turn. This abstracts the set up and rendering
|
18
20
|
# into a separate classes for partials and templates.
|
19
21
|
class AbstractRenderer #:nodoc:
|
20
22
|
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
|
@@ -27,6 +29,84 @@ module ActionView
|
|
27
29
|
raise NotImplementedError
|
28
30
|
end
|
29
31
|
|
32
|
+
module ObjectRendering # :nodoc:
|
33
|
+
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
|
34
|
+
h[k] = Concurrent::Map.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(lookup_context, options)
|
38
|
+
super
|
39
|
+
@context_prefix = lookup_context.prefixes.first
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def local_variable(path)
|
44
|
+
if as = @options[:as]
|
45
|
+
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
|
46
|
+
as.to_sym
|
47
|
+
else
|
48
|
+
base = path.end_with?("/") ? "" : File.basename(path)
|
49
|
+
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
|
50
|
+
$1.to_sym
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
|
55
|
+
"make sure your partial name starts with underscore."
|
56
|
+
|
57
|
+
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
|
58
|
+
"make sure it starts with lowercase letter, " \
|
59
|
+
"and is followed by any combination of letters, numbers and underscores."
|
60
|
+
|
61
|
+
def raise_invalid_identifier(path)
|
62
|
+
raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path
|
63
|
+
end
|
64
|
+
|
65
|
+
def raise_invalid_option_as(as)
|
66
|
+
raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as
|
67
|
+
end
|
68
|
+
|
69
|
+
# Obtains the path to where the object's partial is located. If the object
|
70
|
+
# responds to +to_partial_path+, then +to_partial_path+ will be called and
|
71
|
+
# will provide the path. If the object does not respond to +to_partial_path+,
|
72
|
+
# then an +ArgumentError+ is raised.
|
73
|
+
#
|
74
|
+
# If +prefix_partial_path_with_controller_namespace+ is true, then this
|
75
|
+
# method will prefix the partial paths with a namespace.
|
76
|
+
def partial_path(object, view)
|
77
|
+
object = object.to_model if object.respond_to?(:to_model)
|
78
|
+
|
79
|
+
path = if object.respond_to?(:to_partial_path)
|
80
|
+
object.to_partial_path
|
81
|
+
else
|
82
|
+
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
|
83
|
+
end
|
84
|
+
|
85
|
+
if view.prefix_partial_path_with_controller_namespace
|
86
|
+
PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
|
87
|
+
else
|
88
|
+
path
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def merge_prefix_into_object_path(prefix, object_path)
|
93
|
+
if prefix.include?(?/) && object_path.include?(?/)
|
94
|
+
prefixes = []
|
95
|
+
prefix_array = File.dirname(prefix).split("/")
|
96
|
+
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
|
97
|
+
|
98
|
+
prefix_array.each_with_index do |dir, index|
|
99
|
+
break if dir == object_path_array[index]
|
100
|
+
prefixes << dir
|
101
|
+
end
|
102
|
+
|
103
|
+
(prefixes << object_path).join("/")
|
104
|
+
else
|
105
|
+
object_path
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
30
110
|
class RenderedCollection # :nodoc:
|
31
111
|
def self.empty(format)
|
32
112
|
EmptyCollection.new format
|
@@ -59,11 +139,10 @@ module ActionView
|
|
59
139
|
end
|
60
140
|
|
61
141
|
class RenderedTemplate # :nodoc:
|
62
|
-
attr_reader :body, :
|
142
|
+
attr_reader :body, :template
|
63
143
|
|
64
|
-
def initialize(body,
|
144
|
+
def initialize(body, template)
|
65
145
|
@body = body
|
66
|
-
@layout = layout
|
67
146
|
@template = template
|
68
147
|
end
|
69
148
|
|
@@ -75,18 +154,18 @@ module ActionView
|
|
75
154
|
end
|
76
155
|
|
77
156
|
private
|
157
|
+
NO_DETAILS = {}.freeze
|
158
|
+
|
78
159
|
def extract_details(options) # :doc:
|
79
|
-
|
160
|
+
details = nil
|
161
|
+
@lookup_context.registered_details.each do |key|
|
80
162
|
value = options[key]
|
81
163
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def instrument(name, **options) # :doc:
|
87
|
-
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
|
88
|
-
yield payload
|
164
|
+
if value
|
165
|
+
(details ||= {})[key] = Array(value)
|
166
|
+
end
|
89
167
|
end
|
168
|
+
details || NO_DETAILS
|
90
169
|
end
|
91
170
|
|
92
171
|
def prepend_formats(formats) # :doc:
|
@@ -96,8 +175,8 @@ module ActionView
|
|
96
175
|
@lookup_context.formats = formats | @lookup_context.formats
|
97
176
|
end
|
98
177
|
|
99
|
-
def build_rendered_template(content, template
|
100
|
-
RenderedTemplate.new content,
|
178
|
+
def build_rendered_template(content, template)
|
179
|
+
RenderedTemplate.new content, template
|
101
180
|
end
|
102
181
|
|
103
182
|
def build_rendered_collection(templates, spacer)
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/renderer/partial_renderer"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class PartialIteration
|
7
|
+
# The number of iterations that will be done by the partial.
|
8
|
+
attr_reader :size
|
9
|
+
|
10
|
+
# The current iteration of the partial.
|
11
|
+
attr_reader :index
|
12
|
+
|
13
|
+
def initialize(size)
|
14
|
+
@size = size
|
15
|
+
@index = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if this is the first iteration of the partial.
|
19
|
+
def first?
|
20
|
+
index == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if this is the last iteration of the partial.
|
24
|
+
def last?
|
25
|
+
index == size - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def iterate! # :nodoc:
|
29
|
+
@index += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CollectionRenderer < PartialRenderer # :nodoc:
|
34
|
+
include ObjectRendering
|
35
|
+
|
36
|
+
class CollectionIterator # :nodoc:
|
37
|
+
include Enumerable
|
38
|
+
|
39
|
+
def initialize(collection)
|
40
|
+
@collection = collection
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&blk)
|
44
|
+
@collection.each(&blk)
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@collection.size
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class SameCollectionIterator < CollectionIterator # :nodoc:
|
53
|
+
def initialize(collection, path, variables)
|
54
|
+
super(collection)
|
55
|
+
@path = path
|
56
|
+
@variables = variables
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_collection(collection)
|
60
|
+
self.class.new(collection, @path, @variables)
|
61
|
+
end
|
62
|
+
|
63
|
+
def each_with_info
|
64
|
+
return enum_for(:each_with_info) unless block_given?
|
65
|
+
variables = [@path] + @variables
|
66
|
+
@collection.each { |o| yield(o, variables) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
|
71
|
+
def initialize(collection, path, variables, relation)
|
72
|
+
super(collection, path, variables)
|
73
|
+
relation.skip_preloading! unless relation.loaded?
|
74
|
+
@relation = relation
|
75
|
+
end
|
76
|
+
|
77
|
+
def from_collection(collection)
|
78
|
+
self.class.new(collection, @path, @variables, @relation)
|
79
|
+
end
|
80
|
+
|
81
|
+
def each_with_info
|
82
|
+
return super unless block_given?
|
83
|
+
@relation.preload_associations(@collection)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class MixedCollectionIterator < CollectionIterator # :nodoc:
|
89
|
+
def initialize(collection, paths)
|
90
|
+
super(collection)
|
91
|
+
@paths = paths
|
92
|
+
end
|
93
|
+
|
94
|
+
def each_with_info
|
95
|
+
return enum_for(:each_with_info) unless block_given?
|
96
|
+
@collection.each_with_index { |o, i| yield(o, @paths[i]) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def render_collection_with_partial(collection, partial, context, block)
|
101
|
+
iter_vars = retrieve_variable(partial)
|
102
|
+
|
103
|
+
collection = if collection.respond_to?(:preload_associations)
|
104
|
+
PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
|
105
|
+
else
|
106
|
+
SameCollectionIterator.new(collection, partial, iter_vars)
|
107
|
+
end
|
108
|
+
|
109
|
+
template = find_template(partial, @locals.keys + iter_vars)
|
110
|
+
|
111
|
+
layout = if !block && (layout = @options[:layout])
|
112
|
+
find_template(layout.to_s, @locals.keys + iter_vars)
|
113
|
+
end
|
114
|
+
|
115
|
+
render_collection(collection, context, partial, template, layout, block)
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_collection_derive_partial(collection, context, block)
|
119
|
+
paths = collection.map { |o| partial_path(o, context) }
|
120
|
+
|
121
|
+
if paths.uniq.length == 1
|
122
|
+
# Homogeneous
|
123
|
+
render_collection_with_partial(collection, paths.first, context, block)
|
124
|
+
else
|
125
|
+
if @options[:cached]
|
126
|
+
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
|
127
|
+
end
|
128
|
+
|
129
|
+
paths.map! { |path| retrieve_variable(path).unshift(path) }
|
130
|
+
collection = MixedCollectionIterator.new(collection, paths)
|
131
|
+
render_collection(collection, context, nil, nil, nil, block)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
def retrieve_variable(path)
|
137
|
+
variable = local_variable(path)
|
138
|
+
[variable, :"#{variable}_counter", :"#{variable}_iteration"]
|
139
|
+
end
|
140
|
+
|
141
|
+
def render_collection(collection, view, path, template, layout, block)
|
142
|
+
identifier = (template && template.identifier) || path
|
143
|
+
ActiveSupport::Notifications.instrument(
|
144
|
+
"render_collection.action_view",
|
145
|
+
identifier: identifier,
|
146
|
+
layout: layout && layout.virtual_path,
|
147
|
+
count: collection.size
|
148
|
+
) do |payload|
|
149
|
+
spacer = if @options.key?(:spacer_template)
|
150
|
+
spacer_template = find_template(@options[:spacer_template], @locals.keys)
|
151
|
+
build_rendered_template(spacer_template.render(view, @locals), spacer_template)
|
152
|
+
else
|
153
|
+
RenderedTemplate::EMPTY_SPACER
|
154
|
+
end
|
155
|
+
|
156
|
+
collection_body = if template
|
157
|
+
cache_collection_render(payload, view, template, collection) do |filtered_collection|
|
158
|
+
collection_with_template(view, template, layout, filtered_collection)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
collection_with_template(view, nil, layout, collection)
|
162
|
+
end
|
163
|
+
|
164
|
+
return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
|
165
|
+
|
166
|
+
build_rendered_collection(collection_body, spacer)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def collection_with_template(view, template, layout, collection)
|
171
|
+
locals = @locals
|
172
|
+
cache = {}
|
173
|
+
|
174
|
+
partial_iteration = PartialIteration.new(collection.size)
|
175
|
+
|
176
|
+
collection.each_with_info.map do |object, (path, as, counter, iteration)|
|
177
|
+
index = partial_iteration.index
|
178
|
+
|
179
|
+
locals[as] = object
|
180
|
+
locals[counter] = index
|
181
|
+
locals[iteration] = partial_iteration
|
182
|
+
|
183
|
+
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
|
184
|
+
|
185
|
+
content = _template.render(view, locals)
|
186
|
+
content = layout.render(view, locals) { content } if layout
|
187
|
+
partial_iteration.iterate!
|
188
|
+
build_rendered_template(content, _template)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class ObjectRenderer < PartialRenderer # :nodoc:
|
5
|
+
include ObjectRendering
|
6
|
+
|
7
|
+
def initialize(lookup_context, options)
|
8
|
+
super
|
9
|
+
@object = nil
|
10
|
+
@local_name = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_object_with_partial(object, partial, context, block)
|
14
|
+
@object = object
|
15
|
+
@local_name = local_variable(partial)
|
16
|
+
render(partial, context, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_object_derive_partial(object, context, block)
|
20
|
+
path = partial_path(object, context)
|
21
|
+
render_object_with_partial(object, path, context, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def template_keys(path)
|
26
|
+
super + [@local_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_partial_template(view, locals, template, layout, block)
|
30
|
+
locals[@local_name || template.variable] = @object
|
31
|
+
super(view, locals, template, layout, block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
|
3
5
|
module ActionView
|
4
6
|
module CollectionCaching # :nodoc:
|
5
7
|
extend ActiveSupport::Concern
|
@@ -11,13 +13,19 @@ module ActionView
|
|
11
13
|
end
|
12
14
|
|
13
15
|
private
|
14
|
-
def
|
15
|
-
|
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
|
16
24
|
|
17
25
|
# Result is a hash with the key represents the
|
18
26
|
# key used for cache lookup and the value is the item
|
19
27
|
# on which the partial is being rendered
|
20
|
-
keyed_collection, ordered_keys = collection_by_cache_keys(view, template)
|
28
|
+
keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection)
|
21
29
|
|
22
30
|
# Pull all partials from cache
|
23
31
|
# Result is a hash, key matches the entry in
|
@@ -27,17 +35,9 @@ module ActionView
|
|
27
35
|
instrumentation_payload[:cache_hits] = cached_partials.size
|
28
36
|
|
29
37
|
# Extract the items for the keys that are not found
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# If all elements are already in cache then
|
35
|
-
# rendered partials will be an empty array
|
36
|
-
#
|
37
|
-
# If the cache is missing elements then
|
38
|
-
# the block will be called against the remaining items
|
39
|
-
# in the @collection.
|
40
|
-
rendered_partials = @collection.empty? ? [] : yield
|
38
|
+
collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
|
39
|
+
|
40
|
+
rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection))
|
41
41
|
|
42
42
|
index = 0
|
43
43
|
keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
|
@@ -55,12 +55,12 @@ module ActionView
|
|
55
55
|
@options[:cached].respond_to?(:call)
|
56
56
|
end
|
57
57
|
|
58
|
-
def collection_by_cache_keys(view, template)
|
58
|
+
def collection_by_cache_keys(view, template, collection)
|
59
59
|
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
|
60
60
|
|
61
61
|
digest_path = view.digest_path_from_template(template)
|
62
62
|
|
63
|
-
|
63
|
+
collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
|
64
64
|
key = expanded_cache_key(seed.call(item), view, template, digest_path)
|
65
65
|
ordered_keys << key
|
66
66
|
hash[key] = item
|
@@ -68,7 +68,7 @@ module ActionView
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def expanded_cache_key(key, view, template, digest_path)
|
71
|
-
key = view.combined_fragment_cache_key(view.cache_fragment_name(key,
|
71
|
+
key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
|
72
72
|
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
|
73
73
|
end
|
74
74
|
|
@@ -88,16 +88,15 @@ module ActionView
|
|
88
88
|
# If the partial is not already cached it will also be
|
89
89
|
# written back to the underlying cache store.
|
90
90
|
def fetch_or_cache_partial(cached_partials, template, order_by:)
|
91
|
-
order_by.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
end
|
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
|
100
98
|
end
|
99
|
+
end
|
101
100
|
end
|
102
101
|
end
|
103
102
|
end
|