actionview 6.0.3.1 → 6.1.0.rc2

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +160 -209
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view.rb +4 -1
  5. data/lib/action_view/base.rb +21 -52
  6. data/lib/action_view/cache_expiry.rb +1 -2
  7. data/lib/action_view/context.rb +0 -1
  8. data/lib/action_view/dependency_tracker.rb +10 -4
  9. data/lib/action_view/digestor.rb +3 -2
  10. data/lib/action_view/gem_version.rb +3 -3
  11. data/lib/action_view/helpers/asset_tag_helper.rb +40 -15
  12. data/lib/action_view/helpers/asset_url_helper.rb +6 -4
  13. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  14. data/lib/action_view/helpers/cache_helper.rb +10 -16
  15. data/lib/action_view/helpers/date_helper.rb +4 -4
  16. data/lib/action_view/helpers/form_helper.rb +59 -17
  17. data/lib/action_view/helpers/form_options_helper.rb +7 -16
  18. data/lib/action_view/helpers/form_tag_helper.rb +2 -1
  19. data/lib/action_view/helpers/javascript_helper.rb +3 -3
  20. data/lib/action_view/helpers/number_helper.rb +6 -6
  21. data/lib/action_view/helpers/rendering_helper.rb +11 -3
  22. data/lib/action_view/helpers/tag_helper.rb +92 -17
  23. data/lib/action_view/helpers/tags/base.rb +9 -5
  24. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  25. data/lib/action_view/helpers/tags/date_select.rb +2 -2
  26. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -1
  27. data/lib/action_view/helpers/tags/label.rb +4 -0
  28. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  29. data/lib/action_view/helpers/tags/select.rb +1 -1
  30. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  32. data/lib/action_view/helpers/text_helper.rb +1 -1
  33. data/lib/action_view/helpers/translation_helper.rb +94 -49
  34. data/lib/action_view/helpers/url_helper.rb +107 -13
  35. data/lib/action_view/layouts.rb +3 -2
  36. data/lib/action_view/log_subscriber.rb +26 -10
  37. data/lib/action_view/lookup_context.rb +3 -18
  38. data/lib/action_view/path_set.rb +0 -3
  39. data/lib/action_view/railtie.rb +35 -46
  40. data/lib/action_view/renderer/abstract_renderer.rb +93 -14
  41. data/lib/action_view/renderer/collection_renderer.rb +192 -0
  42. data/lib/action_view/renderer/object_renderer.rb +34 -0
  43. data/lib/action_view/renderer/partial_renderer.rb +20 -282
  44. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
  45. data/lib/action_view/renderer/renderer.rb +44 -1
  46. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -1
  47. data/lib/action_view/renderer/template_renderer.rb +15 -12
  48. data/lib/action_view/rendering.rb +3 -1
  49. data/lib/action_view/routing_url_for.rb +1 -1
  50. data/lib/action_view/template.rb +9 -49
  51. data/lib/action_view/template/handlers.rb +0 -26
  52. data/lib/action_view/template/handlers/erb.rb +10 -14
  53. data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
  54. data/lib/action_view/template/html.rb +1 -11
  55. data/lib/action_view/template/raw_file.rb +0 -3
  56. data/lib/action_view/template/renderable.rb +24 -0
  57. data/lib/action_view/template/resolver.rb +82 -40
  58. data/lib/action_view/template/text.rb +0 -3
  59. data/lib/action_view/test_case.rb +18 -25
  60. data/lib/action_view/testing/resolvers.rb +10 -31
  61. data/lib/action_view/unbound_template.rb +3 -3
  62. data/lib/action_view/view_paths.rb +34 -36
  63. metadata +17 -14
@@ -20,9 +20,6 @@ module ActionView #:nodoc:
20
20
  def render(*args)
21
21
  ::File.read(@filename)
22
22
  end
23
-
24
- def formats; Array(format); end
25
- deprecate :formats
26
23
  end
27
24
  end
28
25
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View Renderable Template for objects that respond to #render_in
5
+ class Template
6
+ class Renderable # :nodoc:
7
+ def initialize(renderable)
8
+ @renderable = renderable
9
+ end
10
+
11
+ def identifier
12
+ @renderable.class.name
13
+ end
14
+
15
+ def render(context, *args)
16
+ @renderable.render_in(context)
17
+ end
18
+
19
+ def format
20
+ @renderable.format
21
+ end
22
+ end
23
+ end
24
+ end
@@ -35,6 +35,41 @@ module ActionView
35
35
  alias :to_s :to_str
36
36
  end
37
37
 
38
+ class PathParser # :nodoc:
39
+ def build_path_regex
40
+ handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
41
+ formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
42
+ locales = "[a-z]{2}(?:-[A-Z]{2})?"
43
+ variants = "[^.]*"
44
+
45
+ %r{
46
+ \A
47
+ (?:(?<prefix>.*)/)?
48
+ (?<partial>_)?
49
+ (?<action>.*?)
50
+ (?:\.(?<locale>#{locales}))??
51
+ (?:\.(?<format>#{formats}))??
52
+ (?:\+(?<variant>#{variants}))??
53
+ (?:\.(?<handler>#{handlers}))?
54
+ \z
55
+ }x
56
+ end
57
+
58
+ def parse(path)
59
+ @regex ||= build_path_regex
60
+ match = @regex.match(path)
61
+ {
62
+ prefix: match[:prefix] || "",
63
+ action: match[:action],
64
+ partial: !!match[:partial],
65
+ locale: match[:locale]&.to_sym,
66
+ handler: match[:handler]&.to_sym,
67
+ format: match[:format]&.to_sym,
68
+ variant: match[:variant]
69
+ }
70
+ end
71
+ end
72
+
38
73
  # Threadsafe template cache
39
74
  class Cache #:nodoc:
40
75
  class SmallCache < Concurrent::Map
@@ -43,13 +78,13 @@ module ActionView
43
78
  end
44
79
  end
45
80
 
46
- # preallocate all the default blocks for performance/memory consumption reasons
81
+ # Preallocate all the default blocks for performance/memory consumption reasons
47
82
  PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
48
83
  PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
49
84
  NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
50
85
  KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
51
86
 
52
- # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
87
+ # Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
53
88
  NO_TEMPLATES = [].freeze
54
89
 
55
90
  def initialize
@@ -58,7 +93,7 @@ module ActionView
58
93
  end
59
94
 
60
95
  def inspect
61
- "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
96
+ "#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
62
97
  end
63
98
 
64
99
  # Cache the templates returned by the block
@@ -75,7 +110,7 @@ module ActionView
75
110
  @query_cache.clear
76
111
  end
77
112
 
78
- # Get the cache size. Do not call this
113
+ # Get the cache size. Do not call this
79
114
  # method. This method is not guaranteed to be here ever.
80
115
  def size # :nodoc:
81
116
  size = 0
@@ -121,9 +156,6 @@ module ActionView
121
156
  end
122
157
  end
123
158
 
124
- alias :find_all_anywhere :find_all
125
- deprecate :find_all_anywhere
126
-
127
159
  def find_all_with_query(query) # :nodoc:
128
160
  @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
129
161
  end
@@ -164,20 +196,17 @@ module ActionView
164
196
  EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
165
197
  DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
166
198
 
167
- def initialize(pattern = nil)
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
199
+ def initialize
200
+ @pattern = DEFAULT_PATTERN
174
201
  @unbound_templates = Concurrent::Map.new
175
- super()
202
+ @path_parser = PathParser.new
203
+ super
176
204
  end
177
205
 
178
206
  def clear_cache
179
207
  @unbound_templates.clear
180
- super()
208
+ @path_parser = PathParser.new
209
+ super
181
210
  end
182
211
 
183
212
  private
@@ -204,9 +233,13 @@ module ActionView
204
233
  end
205
234
  end
206
235
 
236
+ def source_for_template(template)
237
+ Template::Sources::File.new(template)
238
+ end
239
+
207
240
  def build_unbound_template(template, virtual_path)
208
241
  handler, format, variant = extract_handler_and_format_and_variant(template)
209
- source = Template::Sources::File.new(template)
242
+ source = source_for_template(template)
210
243
 
211
244
  UnboundTemplate.new(
212
245
  source,
@@ -223,6 +256,10 @@ module ActionView
223
256
  end
224
257
 
225
258
  def find_template_paths_from_details(path, details)
259
+ if path.name.include?(".")
260
+ ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
261
+ end
262
+
226
263
  query = build_query(path, details)
227
264
  find_template_paths(query)
228
265
  end
@@ -249,7 +286,7 @@ module ActionView
249
286
  query.gsub!(/:prefix(\/)?/, prefix)
250
287
 
251
288
  partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
252
- query.gsub!(/:action/, partial)
289
+ query.gsub!(":action", partial)
253
290
 
254
291
  details.each do |ext, candidates|
255
292
  if ext == :variants && candidates == :any
@@ -270,22 +307,11 @@ module ActionView
270
307
  # from the path, or the handler, we should return the array of formats given
271
308
  # to the resolver.
272
309
  def extract_handler_and_format_and_variant(path)
273
- pieces = File.basename(path).split(".")
274
- pieces.shift
275
-
276
- extension = pieces.pop
277
-
278
- handler = Template.handler_for_extension(extension)
279
- format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
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
310
+ details = @path_parser.parse(path)
311
+
312
+ handler = Template.handler_for_extension(details[:handler])
313
+ format = details[:format] || handler.try(:default_format)
314
+ variant = details[:variant]
289
315
 
290
316
  # Template::Types[format] and handler.default_format can return nil
291
317
  [handler, format, variant]
@@ -296,9 +322,9 @@ module ActionView
296
322
  class FileSystemResolver < PathResolver
297
323
  attr_reader :path
298
324
 
299
- def initialize(path, pattern = nil)
325
+ def initialize(path)
300
326
  raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
301
- super(pattern)
327
+ super()
302
328
  @path = File.expand_path(path)
303
329
  end
304
330
 
@@ -320,14 +346,27 @@ module ActionView
320
346
  end
321
347
 
322
348
  private
323
- def find_template_paths_from_details(path, details)
349
+ def find_candidate_template_paths(path)
324
350
  # Instead of checking for every possible path, as our other globs would
325
351
  # do, scan the directory for files with the right prefix.
326
352
  query = "#{escape_entry(File.join(@path, path))}*"
327
353
 
354
+ Dir[query].reject do |filename|
355
+ File.directory?(filename)
356
+ end
357
+ end
358
+
359
+ def find_template_paths_from_details(path, details)
360
+ if path.name.include?(".")
361
+ # Fall back to the unoptimized resolver, which will warn
362
+ return super
363
+ end
364
+
365
+ candidates = find_candidate_template_paths(path)
366
+
328
367
  regex = build_regex(path, details)
329
368
 
330
- Dir[query].uniq.reject do |filename|
369
+ candidates.uniq.reject do |filename|
331
370
  # This regex match does double duty of finding only files which match
332
371
  # details (instead of just matching the prefix) and also filtering for
333
372
  # case-insensitive file systems.
@@ -339,7 +378,7 @@ module ActionView
339
378
  # We can use the matches found by the regex and sort by their index in
340
379
  # details.
341
380
  match = filename.match(regex)
342
- EXTENSIONS.keys.reverse.map do |ext|
381
+ EXTENSIONS.keys.map do |ext|
343
382
  if ext == :variants && details[ext] == :any
344
383
  match[ext].nil? ? 0 : 1
345
384
  elsif match[ext].nil?
@@ -360,7 +399,10 @@ module ActionView
360
399
  if ext == :variants && details[ext] == :any
361
400
  ".*?"
362
401
  else
363
- details[ext].compact.uniq.map { |e| Regexp.escape(e) }.join("|")
402
+ arr = details[ext].compact
403
+ arr.uniq!
404
+ arr.map! { |e| Regexp.escape(e) }
405
+ arr.join("|")
364
406
  end
365
407
  prefix = Regexp.escape(prefix)
366
408
  "(#{prefix}(?<#{ext}>#{match}))?"
@@ -27,9 +27,6 @@ module ActionView #:nodoc:
27
27
  def format
28
28
  :text
29
29
  end
30
-
31
- def formats; Array(format); end
32
- deprecate :formats
33
30
  end
34
31
  end
35
32
  end
@@ -16,11 +16,12 @@ module ActionView
16
16
  attr_accessor :request, :response, :params
17
17
 
18
18
  class << self
19
- attr_writer :controller_path
19
+ # Overrides AbstractController::Base#controller_path
20
+ attr_accessor :controller_path
20
21
  end
21
22
 
22
23
  def controller_path=(path)
23
- self.class.controller_path = (path)
24
+ self.class.controller_path = path
24
25
  end
25
26
 
26
27
  def initialize
@@ -73,7 +74,7 @@ module ActionView
73
74
  def helper_method(*methods)
74
75
  # Almost a duplicate from ActionController::Helpers
75
76
  methods.flatten.each do |method|
76
- _helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1
77
+ _helpers_for_modification.module_eval <<-end_eval, __FILE__, __LINE__ + 1
77
78
  def #{method}(*args, &block) # def current_user(*args, &block)
78
79
  _test_case.send(:'#{method}', *args, &block) # _test_case.send(:'current_user', *args, &block)
79
80
  end # end
@@ -101,7 +102,8 @@ module ActionView
101
102
  end
102
103
 
103
104
  def setup_with_controller
104
- @controller = ActionView::TestCase::TestController.new
105
+ controller_class = Class.new(ActionView::TestCase::TestController)
106
+ @controller = controller_class.new
105
107
  @request = @controller.request
106
108
  @view_flow = ActionView::OutputFlow.new
107
109
  # empty string ensures buffer has UTF-8 encoding as
@@ -109,8 +111,8 @@ module ActionView
109
111
  @output_buffer = ActiveSupport::SafeBuffer.new ""
110
112
  @rendered = +""
111
113
 
112
- make_test_case_available_to_view!
113
- say_no_to_protect_against_forgery!
114
+ test_case_instance = self
115
+ controller_class.define_method(:_test_case) { test_case_instance }
114
116
  end
115
117
 
116
118
  def config
@@ -160,33 +162,24 @@ module ActionView
160
162
  included do
161
163
  setup :setup_with_controller
162
164
  ActiveSupport.run_load_hooks(:action_view_test_case, self)
163
- end
164
165
 
165
- private
166
- # Need to experiment if this priority is the best one: rendered => output_buffer
167
- def document_root_element
168
- Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
169
- end
170
-
171
- def say_no_to_protect_against_forgery!
172
- _helpers.module_eval do
173
- silence_redefinition_of_method :protect_against_forgery?
166
+ helper do
174
167
  def protect_against_forgery?
175
168
  false
176
169
  end
177
- end
178
- end
179
170
 
180
- def make_test_case_available_to_view!
181
- test_case_instance = self
182
- _helpers.module_eval do
183
- unless private_method_defined?(:_test_case)
184
- define_method(:_test_case) { test_case_instance }
185
- private :_test_case
171
+ def _test_case
172
+ controller._test_case
186
173
  end
187
174
  end
188
175
  end
189
176
 
177
+ private
178
+ # Need to experiment if this priority is the best one: rendered => output_buffer
179
+ def document_root_element
180
+ Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
181
+ end
182
+
190
183
  module Locals
191
184
  attr_accessor :rendered_views
192
185
 
@@ -283,7 +276,7 @@ module ActionView
283
276
 
284
277
  def respond_to_missing?(name, include_private = false)
285
278
  begin
286
- routes = @controller.respond_to?(:_routes) && @controller._routes
279
+ routes = defined?(@controller) && @controller.respond_to?(:_routes) && @controller._routes
287
280
  rescue
288
281
  # Don't call routes, if there is an error on _routes call
289
282
  end
@@ -8,12 +8,8 @@ module ActionView #:nodoc:
8
8
  # useful for testing extensions that have no way of knowing what the file
9
9
  # system will look like at runtime.
10
10
  class FixtureResolver < OptimizedFileSystemResolver
11
- def initialize(hash = {}, pattern = nil)
11
+ def initialize(hash = {})
12
12
  super("")
13
- if pattern
14
- ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
15
- @pattern = pattern
16
- end
17
13
  @hash = hash
18
14
  @path = ""
19
15
  end
@@ -27,34 +23,17 @@ module ActionView #:nodoc:
27
23
  end
28
24
 
29
25
  private
30
- def query(path, exts, _, locals, cache:)
31
- regex = build_regex(path, exts)
32
-
33
- @hash.select do |_path, _|
34
- ("/" + _path).match?(regex)
35
- end.map do |_path, source|
36
- handler, format, variant = extract_handler_and_format_and_variant(_path)
37
-
38
- Template.new(source, _path, handler,
39
- virtual_path: path.virtual,
40
- format: format,
41
- variant: variant,
42
- locals: locals
43
- )
44
- end.sort_by do |t|
45
- match = ("/" + t.identifier).match(regex)
46
- EXTENSIONS.keys.reverse.map do |ext|
47
- if ext == :variants && exts[ext] == :any
48
- match[ext].nil? ? 0 : 1
49
- elsif match[ext].nil?
50
- exts[ext].length
51
- else
52
- found = match[ext].to_sym
53
- exts[ext].index(found)
54
- end
55
- end
26
+ def find_candidate_template_paths(path)
27
+ @hash.keys.select do |fixture|
28
+ fixture.start_with?(path.virtual)
29
+ end.map do |fixture|
30
+ "/#{fixture}"
56
31
  end
57
32
  end
33
+
34
+ def source_for_template(template)
35
+ @hash[template[1..template.size]]
36
+ end
58
37
  end
59
38
 
60
39
  class NullResolver < PathResolver
@@ -4,9 +4,9 @@ require "concurrent/map"
4
4
 
5
5
  module ActionView
6
6
  class UnboundTemplate
7
- def initialize(source, identifer, handler, options)
7
+ def initialize(source, identifier, handler, options)
8
8
  @source = source
9
- @identifer = identifer
9
+ @identifier = identifier
10
10
  @handler = handler
11
11
  @options = options
12
12
 
@@ -22,7 +22,7 @@ module ActionView
22
22
  options = @options.merge(locals: locals)
23
23
  Template.new(
24
24
  @source,
25
- @identifer,
25
+ @identifier,
26
26
  @handler,
27
27
  **options
28
28
  )