actionview 5.2.8.1 → 6.0.6.1

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.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +280 -94
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +108 -11
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/digestor.rb +12 -20
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +8 -31
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +19 -12
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +70 -27
  20. data/lib/action_view/helpers/form_helper.rb +240 -8
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +17 -15
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +8 -2
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  28. data/lib/action_view/helpers/tag_helper.rb +8 -7
  29. data/lib/action_view/helpers/tags/base.rb +9 -6
  30. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  34. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  35. data/lib/action_view/helpers/tags/date_field.rb +0 -1
  36. data/lib/action_view/helpers/tags/date_select.rb +0 -1
  37. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -1
  39. data/lib/action_view/helpers/tags/label.rb +0 -1
  40. data/lib/action_view/helpers/tags/month_field.rb +0 -1
  41. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  42. data/lib/action_view/helpers/tags/select.rb +0 -1
  43. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  44. data/lib/action_view/helpers/tags/time_field.rb +0 -1
  45. data/lib/action_view/helpers/tags/translator.rb +1 -6
  46. data/lib/action_view/helpers/tags/week_field.rb +0 -1
  47. data/lib/action_view/helpers/text_helper.rb +3 -4
  48. data/lib/action_view/helpers/translation_helper.rb +19 -17
  49. data/lib/action_view/helpers/url_helper.rb +14 -14
  50. data/lib/action_view/helpers.rb +0 -2
  51. data/lib/action_view/layouts.rb +5 -8
  52. data/lib/action_view/log_subscriber.rb +6 -7
  53. data/lib/action_view/lookup_context.rb +75 -32
  54. data/lib/action_view/path_set.rb +5 -11
  55. data/lib/action_view/railtie.rb +24 -1
  56. data/lib/action_view/record_identifier.rb +2 -3
  57. data/lib/action_view/renderer/abstract_renderer.rb +56 -4
  58. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +63 -17
  59. data/lib/action_view/renderer/partial_renderer.rb +67 -57
  60. data/lib/action_view/renderer/renderer.rb +16 -4
  61. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -7
  62. data/lib/action_view/renderer/template_renderer.rb +25 -20
  63. data/lib/action_view/rendering.rb +51 -32
  64. data/lib/action_view/routing_url_for.rb +12 -11
  65. data/lib/action_view/template/error.rb +30 -15
  66. data/lib/action_view/template/handlers/builder.rb +2 -2
  67. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  68. data/lib/action_view/template/handlers/erb.rb +17 -8
  69. data/lib/action_view/template/handlers/html.rb +1 -1
  70. data/lib/action_view/template/handlers/raw.rb +2 -2
  71. data/lib/action_view/template/handlers.rb +27 -1
  72. data/lib/action_view/template/html.rb +14 -5
  73. data/lib/action_view/template/inline.rb +22 -0
  74. data/lib/action_view/template/raw_file.rb +28 -0
  75. data/lib/action_view/template/resolver.rb +134 -135
  76. data/lib/action_view/template/sources/file.rb +17 -0
  77. data/lib/action_view/template/sources.rb +13 -0
  78. data/lib/action_view/template/text.rb +5 -3
  79. data/lib/action_view/template.rb +102 -71
  80. data/lib/action_view/test_case.rb +3 -4
  81. data/lib/action_view/testing/resolvers.rb +33 -21
  82. data/lib/action_view/unbound_template.rb +31 -0
  83. data/lib/action_view/view_paths.rb +25 -2
  84. data/lib/action_view.rb +4 -2
  85. data/lib/assets/compiled/rails-ujs.js +30 -4
  86. metadata +27 -18
  87. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -16,7 +16,7 @@ module ActionView
16
16
  alias_method :partial?, :partial
17
17
 
18
18
  def self.build(name, prefix, partial)
19
- virtual = "".dup
19
+ virtual = +""
20
20
  virtual << "#{prefix}/" unless prefix.empty?
21
21
  virtual << (partial ? "_#{name}" : name)
22
22
  new name, prefix, partial, virtual
@@ -63,26 +63,11 @@ module ActionView
63
63
 
64
64
  # Cache the templates returned by the block
65
65
  def cache(key, name, prefix, partial, locals)
66
- if Resolver.caching?
67
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
68
- else
69
- fresh_templates = yield
70
- cached_templates = @data[key][name][prefix][partial][locals]
71
-
72
- if templates_have_changed?(cached_templates, fresh_templates)
73
- @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
74
- else
75
- cached_templates || NO_TEMPLATES
76
- end
77
- end
66
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
78
67
  end
79
68
 
80
69
  def cache_query(query) # :nodoc:
81
- if Resolver.caching?
82
- @query_cache[query] ||= canonical_no_templates(yield)
83
- else
84
- yield
85
- end
70
+ @query_cache[query] ||= canonical_no_templates(yield)
86
71
  end
87
72
 
88
73
  def clear
@@ -108,23 +93,9 @@ module ActionView
108
93
  end
109
94
 
110
95
  private
111
-
112
96
  def canonical_no_templates(templates)
113
97
  templates.empty? ? NO_TEMPLATES : templates
114
98
  end
115
-
116
- def templates_have_changed?(cached_templates, fresh_templates)
117
- # if either the old or new template list is empty, we don't need to (and can't)
118
- # compare modification times, and instead just check whether the lists are different
119
- if cached_templates.blank? || fresh_templates.blank?
120
- return fresh_templates.blank? != cached_templates.blank?
121
- end
122
-
123
- cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
124
-
125
- # if a template has changed, it will be now be newer than all the cached templates
126
- fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
127
- end
128
99
  end
129
100
 
130
101
  cattr_accessor :caching, default: true
@@ -143,35 +114,32 @@ module ActionView
143
114
 
144
115
  # Normalizes the arguments and passes it on to find_templates.
145
116
  def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
146
- cached(key, [name, prefix, partial], details, locals) do
147
- find_templates(name, prefix, partial, details)
148
- end
149
- end
117
+ locals = locals.map(&:to_s).sort!.freeze
150
118
 
151
- def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
152
119
  cached(key, [name, prefix, partial], details, locals) do
153
- find_templates(name, prefix, partial, details, true)
120
+ _find_all(name, prefix, partial, details, key, locals)
154
121
  end
155
122
  end
156
123
 
124
+ alias :find_all_anywhere :find_all
125
+ deprecate :find_all_anywhere
126
+
157
127
  def find_all_with_query(query) # :nodoc:
158
128
  @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
159
129
  end
160
130
 
161
131
  private
132
+ def _find_all(name, prefix, partial, details, key, locals)
133
+ find_templates(name, prefix, partial, details, locals)
134
+ end
162
135
 
163
136
  delegate :caching?, to: :class
164
137
 
165
138
  # This is what child classes implement. No defaults are needed
166
139
  # because Resolver guarantees that the arguments are present and
167
140
  # normalized.
168
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
169
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"
170
- end
171
-
172
- # Helpers that builds a path. Useful for building virtual paths.
173
- def build_path(name, prefix, partial)
174
- Path.build(name, prefix, partial)
141
+ def find_templates(name, prefix, partial, details, locals = [])
142
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
175
143
  end
176
144
 
177
145
  # Handles templates caching. If a key is given and caching is on
@@ -180,25 +148,13 @@ module ActionView
180
148
  # resolver is fresher before returning it.
181
149
  def cached(key, path_info, details, locals)
182
150
  name, prefix, partial = path_info
183
- locals = locals.map(&:to_s).sort!
184
151
 
185
152
  if key
186
153
  @cache.cache(key, name, prefix, partial, locals) do
187
- decorate(yield, path_info, details, locals)
154
+ yield
188
155
  end
189
156
  else
190
- decorate(yield, path_info, details, locals)
191
- end
192
- end
193
-
194
- # Ensures all the resolver information is set in the template.
195
- def decorate(templates, path_info, details, locals)
196
- cached = nil
197
- templates.each do |t|
198
- t.locals = locals
199
- t.formats = details[:formats] || [:html] if t.formats.empty?
200
- t.variants = details[:variants] || [] if t.variants.empty?
201
- t.virtual_path ||= (cached ||= build_path(*path_info))
157
+ yield
202
158
  end
203
159
  end
204
160
  end
@@ -209,40 +165,68 @@ module ActionView
209
165
  DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
210
166
 
211
167
  def initialize(pattern = nil)
212
- @pattern = pattern || DEFAULT_PATTERN
168
+ if pattern
169
+ ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
170
+ @pattern = pattern
171
+ else
172
+ @pattern = DEFAULT_PATTERN
173
+ end
174
+ @unbound_templates = Concurrent::Map.new
213
175
  super()
214
176
  end
215
177
 
216
- private
178
+ def clear_cache
179
+ @unbound_templates.clear
180
+ super()
181
+ end
217
182
 
218
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
183
+ private
184
+ def _find_all(name, prefix, partial, details, key, locals)
219
185
  path = Path.build(name, prefix, partial)
220
- query(path, details, details[:formats], outside_app_allowed)
186
+ query(path, details, details[:formats], locals, cache: !!key)
221
187
  end
222
188
 
223
- def query(path, details, formats, outside_app_allowed)
224
- query = build_query(path, details)
225
-
226
- template_paths = find_template_paths(query)
227
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
189
+ def query(path, details, formats, locals, cache:)
190
+ template_paths = find_template_paths_from_details(path, details)
191
+ template_paths = reject_files_external_to_app(template_paths)
228
192
 
229
193
  template_paths.map do |template|
230
- handler, format, variant = extract_handler_and_format_and_variant(template)
231
- contents = File.binread(template)
232
-
233
- Template.new(contents, File.expand_path(template), handler,
234
- virtual_path: path.virtual,
235
- format: format,
236
- variant: variant,
237
- updated_at: mtime(template)
238
- )
194
+ unbound_template =
195
+ if cache
196
+ @unbound_templates.compute_if_absent([template, path.virtual]) do
197
+ build_unbound_template(template, path.virtual)
198
+ end
199
+ else
200
+ build_unbound_template(template, path.virtual)
201
+ end
202
+
203
+ unbound_template.bind_locals(locals)
239
204
  end
240
205
  end
241
206
 
207
+ def build_unbound_template(template, virtual_path)
208
+ handler, format, variant = extract_handler_and_format_and_variant(template)
209
+ source = Template::Sources::File.new(template)
210
+
211
+ UnboundTemplate.new(
212
+ source,
213
+ template,
214
+ handler,
215
+ virtual_path: virtual_path,
216
+ format: format,
217
+ variant: variant,
218
+ )
219
+ end
220
+
242
221
  def reject_files_external_to_app(files)
243
222
  files.reject { |filename| !inside_path?(@path, filename) }
244
223
  end
245
224
 
225
+ def find_template_paths_from_details(path, details)
226
+ query = build_query(path, details)
227
+ find_template_paths(query)
228
+ end
229
+
246
230
  def find_template_paths(query)
247
231
  Dir[query].uniq.reject do |filename|
248
232
  File.directory?(filename) ||
@@ -279,70 +263,39 @@ module ActionView
279
263
  end
280
264
 
281
265
  def escape_entry(entry)
282
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
283
- end
284
-
285
- # Returns the file mtime from the filesystem.
286
- def mtime(p)
287
- File.mtime(p)
266
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
288
267
  end
289
268
 
290
269
  # Extract handler, formats and variant from path. If a format cannot be found neither
291
270
  # from the path, or the handler, we should return the array of formats given
292
271
  # to the resolver.
293
272
  def extract_handler_and_format_and_variant(path)
294
- pieces = File.basename(path).split(".".freeze)
273
+ pieces = File.basename(path).split(".")
295
274
  pieces.shift
296
275
 
297
276
  extension = pieces.pop
298
277
 
299
278
  handler = Template.handler_for_extension(extension)
300
279
  format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
301
- format &&= Template::Types[format]
280
+ format = if format
281
+ Template::Types[format]&.ref
282
+ else
283
+ if handler.respond_to?(:default_format) # default_format can return nil
284
+ handler.default_format
285
+ else
286
+ nil
287
+ end
288
+ end
302
289
 
290
+ # Template::Types[format] and handler.default_format can return nil
303
291
  [handler, format, variant]
304
292
  end
305
293
  end
306
294
 
307
- # A resolver that loads files from the filesystem. It allows setting your own
308
- # resolving pattern. Such pattern can be a glob string supported by some variables.
309
- #
310
- # ==== Examples
311
- #
312
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
313
- # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
314
- #
315
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
316
- #
317
- # This one allows you to keep files with different formats in separate subdirectories,
318
- # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
319
- # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
320
- #
321
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
322
- #
323
- # If you don't specify a pattern then the default will be used.
324
- #
325
- # In order to use any of the customized resolvers above in a Rails application, you just need
326
- # to configure ActionController::Base.view_paths in an initializer, for example:
327
- #
328
- # ActionController::Base.view_paths = FileSystemResolver.new(
329
- # Rails.root.join("app/views"),
330
- # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
331
- # )
332
- #
333
- # ==== Pattern format and variables
334
- #
335
- # Pattern has to be a valid glob string, and it allows you to use the
336
- # following variables:
337
- #
338
- # * <tt>:prefix</tt> - usually the controller path
339
- # * <tt>:action</tt> - name of the action
340
- # * <tt>:locale</tt> - possible locale versions
341
- # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
342
- # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
343
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
344
- #
295
+ # A resolver that loads files from the filesystem.
345
296
  class FileSystemResolver < PathResolver
297
+ attr_reader :path
298
+
346
299
  def initialize(path, pattern = nil)
347
300
  raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
348
301
  super(pattern)
@@ -362,30 +315,76 @@ module ActionView
362
315
 
363
316
  # An Optimized resolver for Rails' most common case.
364
317
  class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
365
- def build_query(path, details)
366
- query = escape_entry(File.join(@path, path))
318
+ def initialize(path)
319
+ super(path)
320
+ end
367
321
 
368
- exts = EXTENSIONS.map do |ext, prefix|
369
- if ext == :variants && details[ext] == :any
370
- "{#{prefix}*,}"
371
- else
372
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
322
+ private
323
+ def find_template_paths_from_details(path, details)
324
+ # Instead of checking for every possible path, as our other globs would
325
+ # do, scan the directory for files with the right prefix.
326
+ query = "#{escape_entry(File.join(@path, path))}*"
327
+
328
+ regex = build_regex(path, details)
329
+
330
+ Dir[query].uniq.reject do |filename|
331
+ # This regex match does double duty of finding only files which match
332
+ # details (instead of just matching the prefix) and also filtering for
333
+ # case-insensitive file systems.
334
+ !regex.match?(filename) ||
335
+ File.directory?(filename)
336
+ end.sort_by do |filename|
337
+ # Because we scanned the directory, instead of checking for files
338
+ # one-by-one, they will be returned in an arbitrary order.
339
+ # We can use the matches found by the regex and sort by their index in
340
+ # details.
341
+ match = filename.match(regex)
342
+ EXTENSIONS.keys.reverse.map do |ext|
343
+ if ext == :variants && details[ext] == :any
344
+ match[ext].nil? ? 0 : 1
345
+ elsif match[ext].nil?
346
+ # No match should be last
347
+ details[ext].length
348
+ else
349
+ found = match[ext].to_sym
350
+ details[ext].index(found)
351
+ end
352
+ end
373
353
  end
374
- end.join
354
+ end
375
355
 
376
- query + exts
377
- end
356
+ def build_regex(path, details)
357
+ query = Regexp.escape(File.join(@path, path))
358
+ exts = EXTENSIONS.map do |ext, prefix|
359
+ match =
360
+ if ext == :variants && details[ext] == :any
361
+ ".*?"
362
+ else
363
+ details[ext].compact.uniq.map { |e| Regexp.escape(e) }.join("|")
364
+ end
365
+ prefix = Regexp.escape(prefix)
366
+ "(#{prefix}(?<#{ext}>#{match}))?"
367
+ end.join
368
+
369
+ %r{\A#{query}#{exts}\z}
370
+ end
378
371
  end
379
372
 
380
373
  # The same as FileSystemResolver but does not allow templates to store
381
374
  # a virtual path since it is invalid for such resolvers.
382
375
  class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
376
+ private_class_method :new
377
+
383
378
  def self.instances
384
379
  [new(""), new("/")]
385
380
  end
386
381
 
387
- def decorate(*)
388
- super.each { |t| t.virtual_path = nil }
382
+ def build_unbound_template(template, _)
383
+ super(template, nil)
384
+ end
385
+
386
+ def reject_files_external_to_app(files)
387
+ files
389
388
  end
390
389
  end
391
390
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class Template
5
+ module Sources
6
+ class File
7
+ def initialize(filename)
8
+ @filename = filename
9
+ end
10
+
11
+ def to_s
12
+ ::File.binread @filename
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class Template
5
+ module Sources
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :File
10
+ end
11
+ end
12
+ end
13
+ end
@@ -8,7 +8,6 @@ module ActionView #:nodoc:
8
8
 
9
9
  def initialize(string)
10
10
  @string = string.to_s
11
- @type = Types[:text]
12
11
  end
13
12
 
14
13
  def identifier
@@ -25,9 +24,12 @@ module ActionView #:nodoc:
25
24
  to_str
26
25
  end
27
26
 
28
- def formats
29
- [@type.ref]
27
+ def format
28
+ :text
30
29
  end
30
+
31
+ def formats; Array(format); end
32
+ deprecate :formats
31
33
  end
32
34
  end
33
35
  end