actionview 6.1.7.2 → 7.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -277
- data/MIT-LICENSE +2 -1
- data/README.rdoc +3 -3
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +37 -19
- data/lib/action_view/buffers.rb +107 -9
- data/lib/action_view/cache_expiry.rb +48 -37
- data/lib/action_view/context.rb +1 -1
- 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 +6 -147
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +8 -5
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
- data/lib/action_view/helpers/asset_url_helper.rb +22 -21
- data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
- data/lib/action_view/helpers/cache_helper.rb +55 -12
- data/lib/action_view/helpers/capture_helper.rb +34 -14
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +8 -2
- data/lib/action_view/helpers/csp_helper.rb +3 -3
- data/lib/action_view/helpers/csrf_helper.rb +4 -4
- data/lib/action_view/helpers/date_helper.rb +123 -57
- data/lib/action_view/helpers/debug_helper.rb +6 -4
- data/lib/action_view/helpers/form_helper.rb +253 -97
- data/lib/action_view/helpers/form_options_helper.rb +72 -34
- data/lib/action_view/helpers/form_tag_helper.rb +189 -58
- data/lib/action_view/helpers/javascript_helper.rb +4 -5
- data/lib/action_view/helpers/number_helper.rb +43 -335
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +6 -7
- data/lib/action_view/helpers/sanitize_helper.rb +54 -24
- data/lib/action_view/helpers/tag_helper.rb +42 -35
- data/lib/action_view/helpers/tags/base.rb +16 -77
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +4 -1
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +4 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +11 -2
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +180 -97
- data/lib/action_view/helpers/translation_helper.rb +14 -45
- data/lib/action_view/helpers/url_helper.rb +230 -132
- data/lib/action_view/helpers.rb +27 -25
- data/lib/action_view/layouts.rb +15 -10
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +58 -61
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +28 -35
- data/lib/action_view/railtie.rb +44 -9
- data/lib/action_view/record_identifier.rb +16 -9
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +3 -3
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
- data/lib/action_view/renderer/partial_renderer.rb +3 -36
- data/lib/action_view/renderer/renderer.rb +6 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
- data/lib/action_view/renderer/template_renderer.rb +9 -4
- data/lib/action_view/rendering.rb +25 -7
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +8 -5
- data/lib/action_view/template/error.rb +122 -14
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +79 -1
- data/lib/action_view/template/handlers.rb +4 -4
- data/lib/action_view/template/html.rb +4 -4
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +4 -4
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +96 -313
- data/lib/action_view/template/text.rb +4 -4
- data/lib/action_view/template/types.rb +25 -32
- data/lib/action_view/template.rb +245 -41
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +182 -23
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +43 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +19 -28
- data/lib/action_view.rb +6 -4
- data/lib/assets/compiled/rails-ujs.js +36 -5
- metadata +32 -25
data/lib/action_view/path_set.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionView
|
3
|
+
module ActionView # :nodoc:
|
4
4
|
# = Action View PathSet
|
5
5
|
#
|
6
6
|
# This class is used to store and access paths in Action View. A number of
|
@@ -8,19 +8,19 @@ module ActionView #:nodoc:
|
|
8
8
|
# set and also perform operations on other +PathSet+ objects.
|
9
9
|
#
|
10
10
|
# A +LookupContext+ will use a +PathSet+ to store the paths in its context.
|
11
|
-
class PathSet
|
11
|
+
class PathSet # :nodoc:
|
12
12
|
include Enumerable
|
13
13
|
|
14
14
|
attr_reader :paths
|
15
15
|
|
16
|
-
delegate :[], :include?, :
|
16
|
+
delegate :[], :include?, :size, :each, to: :paths
|
17
17
|
|
18
18
|
def initialize(paths = [])
|
19
|
-
@paths = typecast
|
19
|
+
@paths = typecast(paths).freeze
|
20
20
|
end
|
21
21
|
|
22
22
|
def initialize_copy(other)
|
23
|
-
@paths = other.paths.dup
|
23
|
+
@paths = other.paths.dup.freeze
|
24
24
|
self
|
25
25
|
end
|
26
26
|
|
@@ -32,58 +32,51 @@ module ActionView #:nodoc:
|
|
32
32
|
PathSet.new paths.compact
|
33
33
|
end
|
34
34
|
|
35
|
-
def +(
|
35
|
+
def +(other)
|
36
|
+
array = Array === other ? other : other.paths
|
36
37
|
PathSet.new(paths + array)
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
paths.#{method}(*typecast(args))
|
43
|
-
end
|
44
|
-
METHOD
|
45
|
-
end
|
46
|
-
|
47
|
-
def find(*args)
|
48
|
-
find_all(*args).first || raise(MissingTemplate.new(self, *args))
|
49
|
-
end
|
50
|
-
|
51
|
-
def find_all(path, prefixes = [], *args)
|
52
|
-
_find_all path, prefixes, args
|
53
|
-
end
|
54
|
-
|
55
|
-
def exists?(path, prefixes, *args)
|
56
|
-
find_all(path, prefixes, *args).any?
|
40
|
+
def find(path, prefixes, partial, details, details_key, locals)
|
41
|
+
find_all(path, prefixes, partial, details, details_key, locals).first ||
|
42
|
+
raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
|
57
43
|
end
|
58
44
|
|
59
|
-
def
|
60
|
-
|
61
|
-
templates = resolver.
|
45
|
+
def find_all(path, prefixes, partial, details, details_key, locals)
|
46
|
+
search_combinations(prefixes) do |resolver, prefix|
|
47
|
+
templates = resolver.find_all(path, prefix, partial, details, details_key, locals)
|
62
48
|
return templates unless templates.empty?
|
63
49
|
end
|
64
|
-
|
65
50
|
[]
|
66
51
|
end
|
67
52
|
|
53
|
+
def exists?(path, prefixes, partial, details, details_key, locals)
|
54
|
+
find_all(path, prefixes, partial, details, details_key, locals).any?
|
55
|
+
end
|
56
|
+
|
68
57
|
private
|
69
|
-
def
|
70
|
-
prefixes =
|
58
|
+
def search_combinations(prefixes)
|
59
|
+
prefixes = Array(prefixes)
|
71
60
|
prefixes.each do |prefix|
|
72
61
|
paths.each do |resolver|
|
73
|
-
|
74
|
-
return templates unless templates.empty?
|
62
|
+
yield resolver, prefix
|
75
63
|
end
|
76
64
|
end
|
77
|
-
[]
|
78
65
|
end
|
79
66
|
|
80
67
|
def typecast(paths)
|
81
68
|
paths.map do |path|
|
82
69
|
case path
|
83
70
|
when Pathname, String
|
84
|
-
|
85
|
-
|
71
|
+
# This path should only be reached by "direct" users of
|
72
|
+
# ActionView::Base (not using the ViewPaths or Renderer modules).
|
73
|
+
# We can't cache/de-dup the file system resolver in this case as we
|
74
|
+
# don't know which compiled_method_container we'll be rendering to.
|
75
|
+
FileSystemResolver.new(path)
|
76
|
+
when Resolver
|
86
77
|
path
|
78
|
+
else
|
79
|
+
raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver"
|
87
80
|
end
|
88
81
|
end
|
89
82
|
end
|
data/lib/action_view/railtie.rb
CHANGED
@@ -10,6 +10,10 @@ module ActionView
|
|
10
10
|
config.action_view.embed_authenticity_token_in_remote_forms = nil
|
11
11
|
config.action_view.debug_missing_translation = true
|
12
12
|
config.action_view.default_enforce_utf8 = nil
|
13
|
+
config.action_view.image_loading = nil
|
14
|
+
config.action_view.image_decoding = nil
|
15
|
+
config.action_view.apply_stylesheet_media_default = true
|
16
|
+
config.action_view.prepend_content_exfiltration_prevention = false
|
13
17
|
|
14
18
|
config.eager_load_namespaces << ActionView
|
15
19
|
|
@@ -38,23 +42,47 @@ module ActionView
|
|
38
42
|
end
|
39
43
|
|
40
44
|
config.after_initialize do |app|
|
45
|
+
prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
|
46
|
+
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
|
47
|
+
end
|
48
|
+
|
49
|
+
config.after_initialize do |app|
|
50
|
+
if klass = app.config.action_view.delete(:sanitizer_vendor)
|
51
|
+
ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
config.after_initialize do |app|
|
56
|
+
button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
|
57
|
+
unless button_to_generates_button_tag.nil?
|
58
|
+
ActionView::Helpers::UrlHelper.button_to_generates_button_tag = button_to_generates_button_tag
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
config.after_initialize do |app|
|
63
|
+
frozen_string_literal = app.config.action_view.delete(:frozen_string_literal)
|
64
|
+
ActionView::Template.frozen_string_literal = frozen_string_literal
|
65
|
+
end
|
66
|
+
|
67
|
+
config.after_initialize do |app|
|
68
|
+
ActionView::Helpers::AssetTagHelper.image_loading = app.config.action_view.delete(:image_loading)
|
69
|
+
ActionView::Helpers::AssetTagHelper.image_decoding = app.config.action_view.delete(:image_decoding)
|
41
70
|
ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
|
71
|
+
ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default)
|
42
72
|
end
|
43
73
|
|
44
74
|
config.after_initialize do |app|
|
45
75
|
ActiveSupport.on_load(:action_view) do
|
46
76
|
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
|
53
77
|
send "#{k}=", v
|
54
78
|
end
|
55
79
|
end
|
56
80
|
end
|
57
81
|
|
82
|
+
initializer "action_view.deprecator", before: :load_environment_config do |app|
|
83
|
+
app.deprecators[:action_view] = ActionView.deprecator
|
84
|
+
end
|
85
|
+
|
58
86
|
initializer "action_view.logger" do
|
59
87
|
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
|
60
88
|
end
|
@@ -62,7 +90,7 @@ module ActionView
|
|
62
90
|
initializer "action_view.caching" do |app|
|
63
91
|
ActiveSupport.on_load(:action_view) do
|
64
92
|
if app.config.action_view.cache_template_loading.nil?
|
65
|
-
ActionView::Resolver.caching = app.config.
|
93
|
+
ActionView::Resolver.caching = !app.config.reloading_enabled?
|
66
94
|
end
|
67
95
|
end
|
68
96
|
end
|
@@ -79,13 +107,20 @@ module ActionView
|
|
79
107
|
|
80
108
|
config.after_initialize do |app|
|
81
109
|
enable_caching = if app.config.action_view.cache_template_loading.nil?
|
82
|
-
app.config.
|
110
|
+
!app.config.reloading_enabled?
|
83
111
|
else
|
84
112
|
app.config.action_view.cache_template_loading
|
85
113
|
end
|
86
114
|
|
87
115
|
unless enable_caching
|
88
|
-
|
116
|
+
view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
|
117
|
+
|
118
|
+
app.reloaders << view_reloader
|
119
|
+
view_reloader.execute
|
120
|
+
app.reloader.to_run do
|
121
|
+
require_unload_lock!
|
122
|
+
view_reloader.execute
|
123
|
+
end
|
89
124
|
end
|
90
125
|
end
|
91
126
|
|
@@ -4,6 +4,8 @@ require "active_support/core_ext/module"
|
|
4
4
|
require "action_view/model_naming"
|
5
5
|
|
6
6
|
module ActionView
|
7
|
+
# = Action View \Record \Identifier
|
8
|
+
#
|
7
9
|
# RecordIdentifier encapsulates methods used by various ActionView helpers
|
8
10
|
# to associate records with DOM elements.
|
9
11
|
#
|
@@ -31,6 +33,8 @@ module ActionView
|
|
31
33
|
# automatically generated, following naming conventions encapsulated by the
|
32
34
|
# RecordIdentifier methods #dom_id and #dom_class:
|
33
35
|
#
|
36
|
+
# dom_id(Post) # => "new_post"
|
37
|
+
# dom_class(Post) # => "post"
|
34
38
|
# dom_id(Post.new) # => "new_post"
|
35
39
|
# dom_class(Post.new) # => "post"
|
36
40
|
# dom_id(Post.find 42) # => "post_42"
|
@@ -79,18 +83,21 @@ module ActionView
|
|
79
83
|
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
80
84
|
# If no id is found, prefix with "new_" instead.
|
81
85
|
#
|
82
|
-
# dom_id(Post.find(45))
|
83
|
-
# dom_id(Post
|
86
|
+
# dom_id(Post.find(45)) # => "post_45"
|
87
|
+
# dom_id(Post) # => "new_post"
|
84
88
|
#
|
85
89
|
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
86
90
|
#
|
87
91
|
# dom_id(Post.find(45), :edit) # => "edit_post_45"
|
88
|
-
# dom_id(Post
|
89
|
-
def dom_id(
|
90
|
-
|
91
|
-
|
92
|
+
# dom_id(Post, :custom) # => "custom_post"
|
93
|
+
def dom_id(record_or_class, prefix = nil)
|
94
|
+
raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
|
95
|
+
|
96
|
+
record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
|
97
|
+
if record_id
|
98
|
+
"#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
|
92
99
|
else
|
93
|
-
dom_class(
|
100
|
+
dom_class(record_or_class, prefix || NEW)
|
94
101
|
end
|
95
102
|
end
|
96
103
|
|
@@ -102,10 +109,10 @@ module ActionView
|
|
102
109
|
# on the default implementation (which just joins all key attributes with '_') or on your own
|
103
110
|
# overwritten version of the method. By default, this implementation passes the key string through a
|
104
111
|
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
|
105
|
-
# make sure yourself that your dom ids are valid, in case you
|
112
|
+
# make sure yourself that your dom ids are valid, in case you override this method.
|
106
113
|
def record_key_for_dom_id(record) # :doc:
|
107
114
|
key = convert_to_model(record).to_key
|
108
|
-
key ? key.join(JOIN) :
|
115
|
+
key && key.all? ? key.join(JOIN) : nil
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/ripper_ast_parser"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
class RenderParser # :nodoc:
|
7
|
+
def initialize(name, code)
|
8
|
+
@name = name
|
9
|
+
@code = code
|
10
|
+
@parser = RipperASTParser
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_calls
|
14
|
+
render_nodes = @parser.parse_render_nodes(@code)
|
15
|
+
|
16
|
+
render_nodes.map do |method, nodes|
|
17
|
+
nodes.map { |n| send(:parse_render, n) }
|
18
|
+
end.flatten.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def directory
|
23
|
+
File.dirname(@name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve_path_directory(path)
|
27
|
+
if path.include?("/")
|
28
|
+
path
|
29
|
+
else
|
30
|
+
"#{directory}/#{path}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert
|
35
|
+
# render("foo", ...)
|
36
|
+
# into either
|
37
|
+
# render(template: "foo", ...)
|
38
|
+
# or
|
39
|
+
# render(partial: "foo", ...)
|
40
|
+
def normalize_args(string, options_hash)
|
41
|
+
if options_hash
|
42
|
+
{ partial: string, locals: options_hash }
|
43
|
+
else
|
44
|
+
{ partial: string }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_render(node)
|
49
|
+
node = node.argument_nodes
|
50
|
+
|
51
|
+
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
52
|
+
if node.length == 1
|
53
|
+
options = normalize_args(node[0], nil)
|
54
|
+
elsif node.length == 2
|
55
|
+
options = normalize_args(node[0], node[1])
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil unless options
|
59
|
+
|
60
|
+
parse_render_from_options(options)
|
61
|
+
elsif node.length == 1 && node[0].hash?
|
62
|
+
options = parse_hash_to_symbols(node[0])
|
63
|
+
|
64
|
+
return nil unless options
|
65
|
+
|
66
|
+
parse_render_from_options(options)
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_hash(node)
|
73
|
+
node.hash? && node.to_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_hash_to_symbols(node)
|
77
|
+
hash = parse_hash(node)
|
78
|
+
|
79
|
+
return unless hash
|
80
|
+
|
81
|
+
hash.transform_keys do |key_node|
|
82
|
+
key = parse_sym(key_node)
|
83
|
+
|
84
|
+
return unless key
|
85
|
+
|
86
|
+
key
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
|
91
|
+
|
92
|
+
RENDER_TYPE_KEYS =
|
93
|
+
[:partial, :template, :layout]
|
94
|
+
|
95
|
+
def parse_render_from_options(options_hash)
|
96
|
+
renders = []
|
97
|
+
keys = options_hash.keys
|
98
|
+
|
99
|
+
if (keys & RENDER_TYPE_KEYS).size < 1
|
100
|
+
# Must have at least one of render keys
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
if (keys - ALL_KNOWN_KEYS).any?
|
105
|
+
# de-opt in case of unknown option
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
110
|
+
|
111
|
+
node = options_hash[render_type]
|
112
|
+
|
113
|
+
if node.string?
|
114
|
+
template = resolve_path_directory(node.to_string)
|
115
|
+
else
|
116
|
+
if node.variable_reference?
|
117
|
+
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
118
|
+
elsif node.vcall?
|
119
|
+
dependency = node.variable_name
|
120
|
+
elsif node.call?
|
121
|
+
dependency = node.call_method_name
|
122
|
+
else
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
object_template = true
|
127
|
+
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
128
|
+
end
|
129
|
+
|
130
|
+
return unless template
|
131
|
+
|
132
|
+
if spacer_template = render_template_with_spacer?(options_hash)
|
133
|
+
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
134
|
+
renders << virtual_path
|
135
|
+
end
|
136
|
+
|
137
|
+
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
138
|
+
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
139
|
+
return nil unless options_hash.key?(:partial)
|
140
|
+
end
|
141
|
+
|
142
|
+
virtual_path = partial_to_virtual_path(render_type, template)
|
143
|
+
renders << virtual_path
|
144
|
+
|
145
|
+
# Support for rendering multiple templates (i.e. a partial with a layout)
|
146
|
+
if layout_template = render_template_with_layout?(render_type, options_hash)
|
147
|
+
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
148
|
+
|
149
|
+
renders << virtual_path
|
150
|
+
end
|
151
|
+
|
152
|
+
renders
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse_str(node)
|
156
|
+
node.string? && node.to_string
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_sym(node)
|
160
|
+
node.symbol? && node.to_symbol
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def render_template_with_layout?(render_type, options_hash)
|
165
|
+
if render_type != :layout && options_hash.key?(:layout)
|
166
|
+
parse_str(options_hash[:layout])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def render_template_with_spacer?(options_hash)
|
171
|
+
if options_hash.key?(:spacer_template)
|
172
|
+
parse_str(options_hash[:spacer_template])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def partial_to_virtual_path(render_type, partial_path)
|
177
|
+
if render_type == :partial || render_type == :layout
|
178
|
+
partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
|
179
|
+
else
|
180
|
+
partial_path
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def layout_to_virtual_path(layout_path)
|
185
|
+
"layouts/#{layout_path}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -18,7 +18,7 @@ module ActionView
|
|
18
18
|
# renderer object of the correct type is created, and the +render+ method on
|
19
19
|
# that new object is called in turn. This abstracts the set up and rendering
|
20
20
|
# into a separate classes for partials and templates.
|
21
|
-
class AbstractRenderer
|
21
|
+
class AbstractRenderer # :nodoc:
|
22
22
|
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
|
23
23
|
|
24
24
|
def initialize(lookup_context)
|
@@ -31,7 +31,7 @@ module ActionView
|
|
31
31
|
|
32
32
|
module ObjectRendering # :nodoc:
|
33
33
|
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
|
34
|
-
h
|
34
|
+
h.compute_if_absent(k) { Concurrent::Map.new }
|
35
35
|
end
|
36
36
|
|
37
37
|
def initialize(lookup_context, options)
|
@@ -158,7 +158,7 @@ module ActionView
|
|
158
158
|
|
159
159
|
def extract_details(options) # :doc:
|
160
160
|
details = nil
|
161
|
-
|
161
|
+
LookupContext.registered_details.each do |key|
|
162
162
|
value = options[key]
|
163
163
|
|
164
164
|
if value
|
@@ -51,6 +51,10 @@ module ActionView
|
|
51
51
|
def length
|
52
52
|
@collection.respond_to?(:length) ? @collection.length : size
|
53
53
|
end
|
54
|
+
|
55
|
+
def preload!
|
56
|
+
# no-op
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
class SameCollectionIterator < CollectionIterator # :nodoc:
|
@@ -84,9 +88,13 @@ module ActionView
|
|
84
88
|
|
85
89
|
def each_with_info
|
86
90
|
return super unless block_given?
|
87
|
-
|
91
|
+
preload!
|
88
92
|
super
|
89
93
|
end
|
94
|
+
|
95
|
+
def preload!
|
96
|
+
@relation.preload_associations(@collection)
|
97
|
+
end
|
90
98
|
end
|
91
99
|
|
92
100
|
class MixedCollectionIterator < CollectionIterator # :nodoc:
|
@@ -186,7 +194,7 @@ module ActionView
|
|
186
194
|
|
187
195
|
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
|
188
196
|
|
189
|
-
content = _template.render(view, locals)
|
197
|
+
content = _template.render(view, locals, implicit_locals: [counter, iteration])
|
190
198
|
content = layout.render(view, locals) { content } if layout
|
191
199
|
partial_iteration.iterate!
|
192
200
|
build_rendered_template(content, _template)
|
@@ -59,6 +59,7 @@ module ActionView
|
|
59
59
|
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
|
60
60
|
|
61
61
|
digest_path = view.digest_path_from_template(template)
|
62
|
+
collection.preload! if callable_cache_key?
|
62
63
|
|
63
64
|
collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
|
64
65
|
key = expanded_cache_key(seed.call(item), view, template, digest_path)
|
@@ -88,15 +89,32 @@ module ActionView
|
|
88
89
|
# If the partial is not already cached it will also be
|
89
90
|
# written back to the underlying cache store.
|
90
91
|
def fetch_or_cache_partial(cached_partials, template, order_by:)
|
91
|
-
|
92
|
+
entries_to_write = {}
|
93
|
+
|
94
|
+
keyed_partials = order_by.index_with do |cache_key|
|
92
95
|
if content = cached_partials[cache_key]
|
93
96
|
build_rendered_template(content, template)
|
94
97
|
else
|
95
|
-
|
96
|
-
|
98
|
+
rendered_partial = yield
|
99
|
+
body = rendered_partial.body
|
100
|
+
|
101
|
+
# We want to cache buffers as raw strings. This both improve performance and
|
102
|
+
# avoid creating forward compatibility issues with the internal representation
|
103
|
+
# of these two types.
|
104
|
+
if body.is_a?(ActionView::OutputBuffer) || body.is_a?(ActiveSupport::SafeBuffer)
|
105
|
+
body = body.to_str
|
97
106
|
end
|
107
|
+
|
108
|
+
entries_to_write[cache_key] = body
|
109
|
+
rendered_partial
|
98
110
|
end
|
99
111
|
end
|
112
|
+
|
113
|
+
unless entries_to_write.empty?
|
114
|
+
collection_cache.write_multi(entries_to_write)
|
115
|
+
end
|
116
|
+
|
117
|
+
keyed_partials
|
100
118
|
end
|
101
119
|
end
|
102
120
|
end
|
@@ -27,7 +27,7 @@ module ActionView
|
|
27
27
|
# This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
|
28
28
|
# render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
|
29
29
|
#
|
30
|
-
# == The
|
30
|
+
# == The +:as+ and +:object+ options
|
31
31
|
#
|
32
32
|
# By default ActionView::PartialRenderer doesn't have any local variables.
|
33
33
|
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
|
@@ -217,40 +217,6 @@ module ActionView
|
|
217
217
|
# </div>
|
218
218
|
#
|
219
219
|
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
|
220
|
-
#
|
221
|
-
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
|
222
|
-
# an array to layout and treat it as an enumerable.
|
223
|
-
#
|
224
|
-
# <%# app/views/users/_user.html.erb %>
|
225
|
-
# <div class="user">
|
226
|
-
# Budget: $<%= user.budget %>
|
227
|
-
# <%= yield user %>
|
228
|
-
# </div>
|
229
|
-
#
|
230
|
-
# <%# app/views/users/index.html.erb %>
|
231
|
-
# <%= render layout: @users do |user| %>
|
232
|
-
# Title: <%= user.title %>
|
233
|
-
# <% end %>
|
234
|
-
#
|
235
|
-
# This will render the layout for each user and yield to the block, passing the user, each time.
|
236
|
-
#
|
237
|
-
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
|
238
|
-
#
|
239
|
-
# <%# app/views/users/_user.html.erb %>
|
240
|
-
# <div class="user">
|
241
|
-
# <%= yield user, :header %>
|
242
|
-
# Budget: $<%= user.budget %>
|
243
|
-
# <%= yield user, :footer %>
|
244
|
-
# </div>
|
245
|
-
#
|
246
|
-
# <%# app/views/users/index.html.erb %>
|
247
|
-
# <%= render layout: @users do |user, section| %>
|
248
|
-
# <%- case section when :header -%>
|
249
|
-
# Title: <%= user.title %>
|
250
|
-
# <%- when :footer -%>
|
251
|
-
# Deadline: <%= user.deadline %>
|
252
|
-
# <%- end -%>
|
253
|
-
# <% end %>
|
254
220
|
class PartialRenderer < AbstractRenderer
|
255
221
|
include CollectionCaching
|
256
222
|
|
@@ -280,7 +246,8 @@ module ActionView
|
|
280
246
|
ActiveSupport::Notifications.instrument(
|
281
247
|
"render_partial.action_view",
|
282
248
|
identifier: template.identifier,
|
283
|
-
layout: layout && layout.virtual_path
|
249
|
+
layout: layout && layout.virtual_path,
|
250
|
+
locals: locals
|
284
251
|
) do |payload|
|
285
252
|
content = template.render(view, locals, add_to_stack: !block) do |*name|
|
286
253
|
view._layout_for(*name, &block)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
+
# = Action View \Renderer
|
5
|
+
#
|
4
6
|
# This is the main entry point for rendering. It basically delegates
|
5
7
|
# to other objects like TemplateRenderer and PartialRenderer which
|
6
8
|
# actually renders the template.
|
@@ -44,12 +46,12 @@ module ActionView
|
|
44
46
|
end
|
45
47
|
|
46
48
|
# Direct access to template rendering.
|
47
|
-
def render_template(context, options)
|
49
|
+
def render_template(context, options) # :nodoc:
|
48
50
|
render_template_to_object(context, options).body
|
49
51
|
end
|
50
52
|
|
51
53
|
# Direct access to partial rendering.
|
52
|
-
def render_partial(context, options, &block)
|
54
|
+
def render_partial(context, options, &block) # :nodoc:
|
53
55
|
render_partial_to_object(context, options, &block).body
|
54
56
|
end
|
55
57
|
|
@@ -57,11 +59,11 @@ module ActionView
|
|
57
59
|
@cache_hits ||= {}
|
58
60
|
end
|
59
61
|
|
60
|
-
def render_template_to_object(context, options)
|
62
|
+
def render_template_to_object(context, options) # :nodoc:
|
61
63
|
TemplateRenderer.new(@lookup_context).render(context, options)
|
62
64
|
end
|
63
65
|
|
64
|
-
def render_partial_to_object(context, options, &block)
|
66
|
+
def render_partial_to_object(context, options, &block) # :nodoc:
|
65
67
|
partial = options[:partial]
|
66
68
|
if String === partial
|
67
69
|
collection = collection_from_options(options)
|