actionview 6.1.3.2 → 7.0.0.alpha2

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.

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -262
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view/base.rb +3 -3
  5. data/lib/action_view/buffers.rb +2 -2
  6. data/lib/action_view/cache_expiry.rb +46 -32
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  8. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  9. data/lib/action_view/dependency_tracker.rb +6 -147
  10. data/lib/action_view/digestor.rb +7 -4
  11. data/lib/action_view/flows.rb +4 -4
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +85 -30
  15. data/lib/action_view/helpers/asset_url_helper.rb +7 -7
  16. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  17. data/lib/action_view/helpers/cache_helper.rb +51 -3
  18. data/lib/action_view/helpers/capture_helper.rb +2 -2
  19. data/lib/action_view/helpers/controller_helper.rb +2 -2
  20. data/lib/action_view/helpers/csp_helper.rb +1 -1
  21. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  22. data/lib/action_view/helpers/date_helper.rb +5 -5
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +72 -12
  25. data/lib/action_view/helpers/form_options_helper.rb +65 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +73 -30
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +3 -4
  29. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  30. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  31. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  32. data/lib/action_view/helpers/tag_helper.rb +17 -4
  33. data/lib/action_view/helpers/tags/base.rb +2 -14
  34. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  37. data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
  38. data/lib/action_view/helpers/tags.rb +3 -2
  39. data/lib/action_view/helpers/text_helper.rb +24 -13
  40. data/lib/action_view/helpers/translation_helper.rb +4 -3
  41. data/lib/action_view/helpers/url_helper.rb +122 -80
  42. data/lib/action_view/helpers.rb +25 -25
  43. data/lib/action_view/lookup_context.rb +33 -52
  44. data/lib/action_view/model_naming.rb +1 -1
  45. data/lib/action_view/path_set.rb +16 -22
  46. data/lib/action_view/railtie.rb +15 -2
  47. data/lib/action_view/render_parser.rb +188 -0
  48. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  49. data/lib/action_view/renderer/partial_renderer.rb +0 -34
  50. data/lib/action_view/renderer/renderer.rb +4 -4
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  52. data/lib/action_view/renderer/template_renderer.rb +6 -2
  53. data/lib/action_view/rendering.rb +2 -2
  54. data/lib/action_view/ripper_ast_parser.rb +198 -0
  55. data/lib/action_view/routing_url_for.rb +1 -1
  56. data/lib/action_view/template/error.rb +108 -13
  57. data/lib/action_view/template/handlers/erb.rb +6 -0
  58. data/lib/action_view/template/handlers.rb +3 -3
  59. data/lib/action_view/template/html.rb +3 -3
  60. data/lib/action_view/template/inline.rb +3 -3
  61. data/lib/action_view/template/raw_file.rb +3 -3
  62. data/lib/action_view/template/resolver.rb +84 -311
  63. data/lib/action_view/template/text.rb +3 -3
  64. data/lib/action_view/template/types.rb +14 -12
  65. data/lib/action_view/template.rb +10 -1
  66. data/lib/action_view/template_details.rb +66 -0
  67. data/lib/action_view/template_path.rb +64 -0
  68. data/lib/action_view/test_case.rb +6 -2
  69. data/lib/action_view/testing/resolvers.rb +11 -12
  70. data/lib/action_view/unbound_template.rb +33 -7
  71. data/lib/action_view.rb +3 -4
  72. data/lib/assets/compiled/rails-ujs.js +2 -2
  73. metadata +25 -18
@@ -12,12 +12,11 @@ module ActionView
12
12
  # <tt>LookupContext</tt> is also responsible for generating a key, given to
13
13
  # view paths, used in the resolver cache lookup. Since this key is generated
14
14
  # only once during the request, it speeds up all cache accesses.
15
- class LookupContext #:nodoc:
15
+ class LookupContext # :nodoc:
16
16
  attr_accessor :prefixes, :rendered_format
17
17
 
18
- mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
19
-
20
- mattr_accessor :registered_details, default: []
18
+ singleton_class.attr_accessor :registered_details
19
+ self.registered_details = []
21
20
 
22
21
  def self.register_detail(name, &block)
23
22
  registered_details << name
@@ -37,7 +36,7 @@ module ActionView
37
36
  end
38
37
 
39
38
  # Holds accessors for the registered details.
40
- module Accessors #:nodoc:
39
+ module Accessors # :nodoc:
41
40
  DEFAULT_PROCS = {}
42
41
  end
43
42
 
@@ -52,7 +51,7 @@ module ActionView
52
51
  register_detail(:variants) { [] }
53
52
  register_detail(:handlers) { Template::Handlers.extensions }
54
53
 
55
- class DetailsKey #:nodoc:
54
+ class DetailsKey # :nodoc:
56
55
  alias :eql? :equal?
57
56
 
58
57
  @details_keys = Concurrent::Map.new
@@ -68,14 +67,13 @@ module ActionView
68
67
  details = details.dup
69
68
  details[:formats] &= Template::Types.symbols
70
69
  end
71
- @details_keys[details] ||= Object.new
70
+ @details_keys[details] ||= TemplateDetails::Requested.new(**details)
72
71
  end
73
72
 
74
73
  def self.clear
75
74
  ActionView::ViewPaths.all_view_paths.each do |path_set|
76
75
  path_set.each(&:clear_cache)
77
76
  end
78
- ActionView::LookupContext.fallbacks.each(&:clear_cache)
79
77
  @view_context_class = nil
80
78
  @details_keys.clear
81
79
  @digest_cache.clear
@@ -98,7 +96,7 @@ module ActionView
98
96
 
99
97
  # Calculate the details key. Remove the handlers from calculation to improve performance
100
98
  # since the user cannot modify it explicitly.
101
- def details_key #:nodoc:
99
+ def details_key # :nodoc:
102
100
  @details_key ||= DetailsKey.details_cache_key(@details) if @cache
103
101
  end
104
102
 
@@ -124,39 +122,32 @@ module ActionView
124
122
  attr_reader :view_paths, :html_fallback_for_js
125
123
 
126
124
  def find(name, prefixes = [], partial = false, keys = [], options = {})
127
- @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
125
+ name, prefixes = normalize_name(name, prefixes)
126
+ details, details_key = detail_args_for(options)
127
+ @view_paths.find(name, prefixes, partial, details, details_key, keys)
128
128
  end
129
129
  alias :find_template :find
130
130
 
131
131
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
132
- @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
132
+ name, prefixes = normalize_name(name, prefixes)
133
+ details, details_key = detail_args_for(options)
134
+ @view_paths.find_all(name, prefixes, partial, details, details_key, keys)
133
135
  end
134
136
 
135
137
  def exists?(name, prefixes = [], partial = false, keys = [], **options)
136
- @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
138
+ name, prefixes = normalize_name(name, prefixes)
139
+ details, details_key = detail_args_for(options)
140
+ @view_paths.exists?(name, prefixes, partial, details, details_key, keys)
137
141
  end
138
142
  alias :template_exists? :exists?
139
143
 
140
144
  def any?(name, prefixes = [], partial = false)
141
- @view_paths.exists?(*args_for_any(name, prefixes, partial))
145
+ name, prefixes = normalize_name(name, prefixes)
146
+ details, details_key = detail_args_for_any
147
+ @view_paths.exists?(name, prefixes, partial, details, details_key, [])
142
148
  end
143
149
  alias :any_templates? :any?
144
150
 
145
- # Adds fallbacks to the view paths. Useful in cases when you are rendering
146
- # a :file.
147
- def with_fallbacks
148
- view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
149
-
150
- if block_given?
151
- raise ArgumentError, <<~eowarn.squish
152
- Calling `with_fallbacks` with a block is not supported. Call methods on
153
- the lookup context returned by `with_fallbacks` instead.
154
- eowarn
155
- else
156
- ActionView::LookupContext.new(view_paths, @details, @prefixes)
157
- end
158
- end
159
-
160
151
  private
161
152
  # Whenever setting view paths, makes a copy so that we can manipulate them in
162
153
  # instance objects as we wish.
@@ -164,12 +155,6 @@ module ActionView
164
155
  ActionView::PathSet.new(Array(paths))
165
156
  end
166
157
 
167
- def args_for_lookup(name, prefixes, partial, keys, details_options)
168
- name, prefixes = normalize_name(name, prefixes)
169
- details, details_key = detail_args_for(details_options)
170
- [name, prefixes, partial || false, details, details_key, keys]
171
- end
172
-
173
158
  # Compute details hash and key according to user options (e.g. passed from #render).
174
159
  def detail_args_for(options) # :doc:
175
160
  return @details, details_key if options.empty? # most common path.
@@ -184,17 +169,11 @@ module ActionView
184
169
  [user_details, details_key]
185
170
  end
186
171
 
187
- def args_for_any(name, prefixes, partial)
188
- name, prefixes = normalize_name(name, prefixes)
189
- details, details_key = detail_args_for_any
190
- [name, prefixes, partial || false, details, details_key]
191
- end
192
-
193
172
  def detail_args_for_any
194
173
  @detail_args_for_any ||= begin
195
174
  details = {}
196
175
 
197
- registered_details.each do |k|
176
+ LookupContext.registered_details.each do |k|
198
177
  if k == :variants
199
178
  details[k] = :any
200
179
  else
@@ -210,19 +189,21 @@ module ActionView
210
189
  end
211
190
  end
212
191
 
213
- # Support legacy foo.erb names even though we now ignore .erb
214
- # as well as incorrectly putting part of the path in the template
215
- # name instead of the prefix.
192
+ # Fix when prefix is specified as part of the template name
216
193
  def normalize_name(name, prefixes)
217
- prefixes = prefixes.presence
218
- parts = name.to_s.split("/")
219
- parts.shift if parts.first.empty?
220
- name = parts.pop
194
+ name = name.to_s
195
+ idx = name.rindex("/")
196
+ return name, prefixes.presence || [""] unless idx
221
197
 
222
- return name, prefixes || [""] if parts.empty?
198
+ path_prefix = name[0, idx]
199
+ path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
200
+ name = name.from(idx + 1)
223
201
 
224
- parts = parts.join("/")
225
- prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
202
+ if !prefixes || prefixes.empty?
203
+ prefixes = [path_prefix]
204
+ else
205
+ prefixes = prefixes.map { |p| "#{p}/#{path_prefix}" }
206
+ end
226
207
 
227
208
  return name, prefixes
228
209
  end
@@ -254,7 +235,7 @@ module ActionView
254
235
  end
255
236
 
256
237
  def initialize_details(target, details)
257
- registered_details.each do |k|
238
+ LookupContext.registered_details.each do |k|
258
239
  target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
259
240
  end
260
241
  target
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
- module ModelNaming #:nodoc:
4
+ module ModelNaming # :nodoc:
5
5
  # Converts the given object to an ActiveModel compliant one.
6
6
  def convert_to_model(object)
7
7
  object.respond_to?(:to_model) ? object.to_model : object
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
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,7 +8,7 @@ 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 #:nodoc:
11
+ class PathSet # :nodoc:
12
12
  include Enumerable
13
13
 
14
14
  attr_reader :paths
@@ -44,44 +44,38 @@ module ActionView #:nodoc:
44
44
  METHOD
45
45
  end
46
46
 
47
- def find(*args)
48
- find_all(*args).first || raise(MissingTemplate.new(self, *args))
47
+ def find(path, prefixes, partial, details, details_key, locals)
48
+ find_all(path, prefixes, partial, details, details_key, locals).first ||
49
+ raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
49
50
  end
50
51
 
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?
57
- end
58
-
59
- def find_all_with_query(query) # :nodoc:
60
- paths.each do |resolver|
61
- templates = resolver.find_all_with_query(query)
52
+ def find_all(path, prefixes, partial, details, details_key, locals)
53
+ search_combinations(prefixes) do |resolver, prefix|
54
+ templates = resolver.find_all(path, prefix, partial, details, details_key, locals)
62
55
  return templates unless templates.empty?
63
56
  end
64
-
65
57
  []
66
58
  end
67
59
 
60
+ def exists?(path, prefixes, partial, details, details_key, locals)
61
+ find_all(path, prefixes, partial, details, details_key, locals).any?
62
+ end
63
+
68
64
  private
69
- def _find_all(path, prefixes, args)
70
- prefixes = [prefixes] if String === prefixes
65
+ def search_combinations(prefixes)
66
+ prefixes = Array(prefixes)
71
67
  prefixes.each do |prefix|
72
68
  paths.each do |resolver|
73
- templates = resolver.find_all(path, prefix, *args)
74
- return templates unless templates.empty?
69
+ yield resolver, prefix
75
70
  end
76
71
  end
77
- []
78
72
  end
79
73
 
80
74
  def typecast(paths)
81
75
  paths.map do |path|
82
76
  case path
83
77
  when Pathname, String
84
- OptimizedFileSystemResolver.new path.to_s
78
+ FileSystemResolver.new path.to_s
85
79
  else
86
80
  path
87
81
  end
@@ -10,6 +10,9 @@ 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
13
16
 
14
17
  config.eager_load_namespaces << ActionView
15
18
 
@@ -38,7 +41,17 @@ module ActionView
38
41
  end
39
42
 
40
43
  config.after_initialize do |app|
44
+ button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
45
+ unless button_to_generates_button_tag.nil?
46
+ ActionView::Helpers::UrlHelper.button_to_generates_button_tag = button_to_generates_button_tag
47
+ end
48
+ end
49
+
50
+ config.after_initialize do |app|
51
+ ActionView::Helpers::AssetTagHelper.image_loading = app.config.action_view.delete(:image_loading)
52
+ ActionView::Helpers::AssetTagHelper.image_decoding = app.config.action_view.delete(:image_decoding)
41
53
  ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
54
+ ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default)
42
55
  end
43
56
 
44
57
  config.after_initialize do |app|
@@ -46,7 +59,7 @@ module ActionView
46
59
  app.config.action_view.each do |k, v|
47
60
  if k == :raise_on_missing_translations
48
61
  ActiveSupport::Deprecation.warn \
49
- "action_view.raise_on_missing_translations is deprecated and will be removed in Rails 6.2. " \
62
+ "action_view.raise_on_missing_translations is deprecated and will be removed in Rails 7.0. " \
50
63
  "Set i18n.raise_on_missing_translations instead. " \
51
64
  "Note that this new setting also affects how missing translations are handled in controllers."
52
65
  end
@@ -85,7 +98,7 @@ module ActionView
85
98
  end
86
99
 
87
100
  unless enable_caching
88
- app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
101
+ app.executor.register_hook ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
89
102
  end
90
103
  end
91
104
 
@@ -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