actionview 6.0.6.1 → 6.1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +238 -220
- data/MIT-LICENSE +1 -2
- data/lib/action_view/base.rb +18 -49
- data/lib/action_view/cache_expiry.rb +1 -2
- 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 +57 -17
- 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 +6 -5
- data/lib/action_view/helpers/form_helper.rb +66 -30
- data/lib/action_view/helpers/form_options_helper.rb +7 -16
- data/lib/action_view/helpers/form_tag_helper.rb +4 -3
- 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/tag_helper.rb +98 -22
- data/lib/action_view/helpers/tags/base.rb +10 -6
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- 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/hidden_field.rb +4 -0
- 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 +2 -2
- data/lib/action_view/helpers/translation_helper.rb +88 -50
- data/lib/action_view/helpers/url_helper.rb +136 -24
- 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 +39 -46
- data/lib/action_view/renderer/abstract_renderer.rb +93 -14
- 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 +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 +38 -7
- metadata +15 -12
data/lib/action_view/layouts.rb
CHANGED
@@ -306,7 +306,7 @@ module ActionView
|
|
306
306
|
RUBY
|
307
307
|
when Proc
|
308
308
|
define_method :_layout_from_proc, &_layout
|
309
|
-
|
309
|
+
private :_layout_from_proc
|
310
310
|
<<-RUBY
|
311
311
|
result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
|
312
312
|
return #{default_behavior} if result.nil?
|
@@ -321,6 +321,7 @@ module ActionView
|
|
321
321
|
end
|
322
322
|
|
323
323
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
324
|
+
# frozen_string_literal: true
|
324
325
|
def _layout(lookup_context, formats)
|
325
326
|
if _conditional_layout?
|
326
327
|
#{layout_definition}
|
@@ -395,7 +396,7 @@ module ActionView
|
|
395
396
|
end
|
396
397
|
|
397
398
|
def _normalize_layout(value)
|
398
|
-
value.is_a?(String) && value
|
399
|
+
value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value
|
399
400
|
end
|
400
401
|
|
401
402
|
# Returns the default layout for this controller.
|
@@ -23,7 +23,7 @@ module ActionView
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def render_partial(event)
|
26
|
-
|
26
|
+
debug do
|
27
27
|
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
|
28
28
|
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
29
29
|
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
|
@@ -32,19 +32,26 @@ module ActionView
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def render_layout(event)
|
36
|
+
info do
|
37
|
+
message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
|
38
|
+
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
35
42
|
def render_collection(event)
|
36
43
|
identifier = event.payload[:identifier] || "templates"
|
37
44
|
|
38
|
-
|
39
|
-
" Rendered collection of #{from_rails_root(identifier)}"
|
40
|
-
" #{
|
45
|
+
debug do
|
46
|
+
message = +" Rendered collection of #{from_rails_root(identifier)}"
|
47
|
+
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
48
|
+
message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
|
49
|
+
message
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
53
|
def start(name, id, payload)
|
45
|
-
|
46
|
-
log_rendering_start(payload)
|
47
|
-
end
|
54
|
+
log_rendering_start(payload, name)
|
48
55
|
|
49
56
|
super
|
50
57
|
end
|
@@ -82,9 +89,18 @@ module ActionView
|
|
82
89
|
end
|
83
90
|
end
|
84
91
|
|
85
|
-
def log_rendering_start(payload)
|
86
|
-
|
87
|
-
|
92
|
+
def log_rendering_start(payload, name)
|
93
|
+
debug do
|
94
|
+
qualifier =
|
95
|
+
if name == "render_template.action_view"
|
96
|
+
""
|
97
|
+
elsif name == "render_layout.action_view"
|
98
|
+
"layout "
|
99
|
+
end
|
100
|
+
|
101
|
+
return unless qualifier
|
102
|
+
|
103
|
+
message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
|
88
104
|
message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
|
89
105
|
message
|
90
106
|
end
|
@@ -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,70 +6,59 @@ 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
|
-
|
52
|
-
|
53
|
-
option = app.config.action_view.delete(:finalize_compiled_template_methods)
|
54
|
-
|
55
|
-
if option != NULL_OPTION
|
56
|
-
ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
|
57
|
-
end
|
58
|
-
end
|
40
|
+
config.after_initialize do |app|
|
41
|
+
ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
|
59
42
|
end
|
60
43
|
|
61
|
-
|
62
|
-
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
|
63
|
-
end
|
64
|
-
|
65
|
-
initializer "action_view.set_configs" do |app|
|
44
|
+
config.after_initialize do |app|
|
66
45
|
ActiveSupport.on_load(:action_view) do
|
67
46
|
app.config.action_view.each do |k, v|
|
47
|
+
if k == :raise_on_missing_translations
|
48
|
+
ActiveSupport::Deprecation.warn \
|
49
|
+
"action_view.raise_on_missing_translations is deprecated and will be removed in Rails 7.0. " \
|
50
|
+
"Set i18n.raise_on_missing_translations instead. " \
|
51
|
+
"Note that this new setting also affects how missing translations are handled in controllers."
|
52
|
+
end
|
68
53
|
send "#{k}=", v
|
69
54
|
end
|
70
55
|
end
|
71
56
|
end
|
72
57
|
|
58
|
+
initializer "action_view.logger" do
|
59
|
+
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
|
60
|
+
end
|
61
|
+
|
73
62
|
initializer "action_view.caching" do |app|
|
74
63
|
ActiveSupport.on_load(:action_view) do
|
75
64
|
if app.config.action_view.cache_template_loading.nil?
|
@@ -78,14 +67,6 @@ module ActionView
|
|
78
67
|
end
|
79
68
|
end
|
80
69
|
|
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
70
|
initializer "action_view.setup_action_pack" do |app|
|
90
71
|
ActiveSupport.on_load(:action_controller) do
|
91
72
|
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
|
@@ -96,6 +77,18 @@ module ActionView
|
|
96
77
|
PartialRenderer.collection_cache = app.config.action_controller.cache_store
|
97
78
|
end
|
98
79
|
|
80
|
+
config.after_initialize do |app|
|
81
|
+
enable_caching = if app.config.action_view.cache_template_loading.nil?
|
82
|
+
app.config.cache_classes
|
83
|
+
else
|
84
|
+
app.config.action_view.cache_template_loading
|
85
|
+
end
|
86
|
+
|
87
|
+
unless enable_caching
|
88
|
+
app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
99
92
|
rake_tasks do |app|
|
100
93
|
unless app.config.api_only
|
101
94
|
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,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/renderer/partial_renderer"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class PartialIteration
|
7
|
+
# The number of iterations that will be done by the partial.
|
8
|
+
attr_reader :size
|
9
|
+
|
10
|
+
# The current iteration of the partial.
|
11
|
+
attr_reader :index
|
12
|
+
|
13
|
+
def initialize(size)
|
14
|
+
@size = size
|
15
|
+
@index = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if this is the first iteration of the partial.
|
19
|
+
def first?
|
20
|
+
index == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if this is the last iteration of the partial.
|
24
|
+
def last?
|
25
|
+
index == size - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def iterate! # :nodoc:
|
29
|
+
@index += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CollectionRenderer < PartialRenderer # :nodoc:
|
34
|
+
include ObjectRendering
|
35
|
+
|
36
|
+
class CollectionIterator # :nodoc:
|
37
|
+
include Enumerable
|
38
|
+
|
39
|
+
def initialize(collection)
|
40
|
+
@collection = collection
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&blk)
|
44
|
+
@collection.each(&blk)
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@collection.size
|
49
|
+
end
|
50
|
+
|
51
|
+
def length
|
52
|
+
@collection.respond_to?(:length) ? @collection.length : size
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class SameCollectionIterator < CollectionIterator # :nodoc:
|
57
|
+
def initialize(collection, path, variables)
|
58
|
+
super(collection)
|
59
|
+
@path = path
|
60
|
+
@variables = variables
|
61
|
+
end
|
62
|
+
|
63
|
+
def from_collection(collection)
|
64
|
+
self.class.new(collection, @path, @variables)
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_with_info
|
68
|
+
return enum_for(:each_with_info) unless block_given?
|
69
|
+
variables = [@path] + @variables
|
70
|
+
@collection.each { |o| yield(o, variables) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
|
75
|
+
def initialize(collection, path, variables, relation)
|
76
|
+
super(collection, path, variables)
|
77
|
+
relation.skip_preloading! unless relation.loaded?
|
78
|
+
@relation = relation
|
79
|
+
end
|
80
|
+
|
81
|
+
def from_collection(collection)
|
82
|
+
self.class.new(collection, @path, @variables, @relation)
|
83
|
+
end
|
84
|
+
|
85
|
+
def each_with_info
|
86
|
+
return super unless block_given?
|
87
|
+
@relation.preload_associations(@collection)
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class MixedCollectionIterator < CollectionIterator # :nodoc:
|
93
|
+
def initialize(collection, paths)
|
94
|
+
super(collection)
|
95
|
+
@paths = paths
|
96
|
+
end
|
97
|
+
|
98
|
+
def each_with_info
|
99
|
+
return enum_for(:each_with_info) unless block_given?
|
100
|
+
@collection.each_with_index { |o, i| yield(o, @paths[i]) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def render_collection_with_partial(collection, partial, context, block)
|
105
|
+
iter_vars = retrieve_variable(partial)
|
106
|
+
|
107
|
+
collection = if collection.respond_to?(:preload_associations)
|
108
|
+
PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
|
109
|
+
else
|
110
|
+
SameCollectionIterator.new(collection, partial, iter_vars)
|
111
|
+
end
|
112
|
+
|
113
|
+
template = find_template(partial, @locals.keys + iter_vars)
|
114
|
+
|
115
|
+
layout = if !block && (layout = @options[:layout])
|
116
|
+
find_template(layout.to_s, @locals.keys + iter_vars)
|
117
|
+
end
|
118
|
+
|
119
|
+
render_collection(collection, context, partial, template, layout, block)
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_collection_derive_partial(collection, context, block)
|
123
|
+
paths = collection.map { |o| partial_path(o, context) }
|
124
|
+
|
125
|
+
if paths.uniq.length == 1
|
126
|
+
# Homogeneous
|
127
|
+
render_collection_with_partial(collection, paths.first, context, block)
|
128
|
+
else
|
129
|
+
if @options[:cached]
|
130
|
+
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
|
131
|
+
end
|
132
|
+
|
133
|
+
paths.map! { |path| retrieve_variable(path).unshift(path) }
|
134
|
+
collection = MixedCollectionIterator.new(collection, paths)
|
135
|
+
render_collection(collection, context, nil, nil, nil, block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def retrieve_variable(path)
|
141
|
+
variable = local_variable(path)
|
142
|
+
[variable, :"#{variable}_counter", :"#{variable}_iteration"]
|
143
|
+
end
|
144
|
+
|
145
|
+
def render_collection(collection, view, path, template, layout, block)
|
146
|
+
identifier = (template && template.identifier) || path
|
147
|
+
ActiveSupport::Notifications.instrument(
|
148
|
+
"render_collection.action_view",
|
149
|
+
identifier: identifier,
|
150
|
+
layout: layout && layout.virtual_path,
|
151
|
+
count: collection.length
|
152
|
+
) do |payload|
|
153
|
+
spacer = if @options.key?(:spacer_template)
|
154
|
+
spacer_template = find_template(@options[:spacer_template], @locals.keys)
|
155
|
+
build_rendered_template(spacer_template.render(view, @locals), spacer_template)
|
156
|
+
else
|
157
|
+
RenderedTemplate::EMPTY_SPACER
|
158
|
+
end
|
159
|
+
|
160
|
+
collection_body = if template
|
161
|
+
cache_collection_render(payload, view, template, collection) do |filtered_collection|
|
162
|
+
collection_with_template(view, template, layout, filtered_collection)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
collection_with_template(view, nil, layout, collection)
|
166
|
+
end
|
167
|
+
|
168
|
+
return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
|
169
|
+
|
170
|
+
build_rendered_collection(collection_body, spacer)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def collection_with_template(view, template, layout, collection)
|
175
|
+
locals = @locals
|
176
|
+
cache = {}
|
177
|
+
|
178
|
+
partial_iteration = PartialIteration.new(collection.size)
|
179
|
+
|
180
|
+
collection.each_with_info.map do |object, (path, as, counter, iteration)|
|
181
|
+
index = partial_iteration.index
|
182
|
+
|
183
|
+
locals[as] = object
|
184
|
+
locals[counter] = index
|
185
|
+
locals[iteration] = partial_iteration
|
186
|
+
|
187
|
+
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
|
188
|
+
|
189
|
+
content = _template.render(view, locals)
|
190
|
+
content = layout.render(view, locals) { content } if layout
|
191
|
+
partial_iteration.iterate!
|
192
|
+
build_rendered_template(content, _template)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class ObjectRenderer < PartialRenderer # :nodoc:
|
5
|
+
include ObjectRendering
|
6
|
+
|
7
|
+
def initialize(lookup_context, options)
|
8
|
+
super
|
9
|
+
@object = nil
|
10
|
+
@local_name = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_object_with_partial(object, partial, context, block)
|
14
|
+
@object = object
|
15
|
+
@local_name = local_variable(partial)
|
16
|
+
render(partial, context, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_object_derive_partial(object, context, block)
|
20
|
+
path = partial_path(object, context)
|
21
|
+
render_object_with_partial(object, path, context, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def template_keys(path)
|
26
|
+
super + [@local_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_partial_template(view, locals, template, layout, block)
|
30
|
+
locals[@local_name || template.variable] = @object
|
31
|
+
super(view, locals, template, layout, block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|