actionview 6.0.0.beta2 → 6.0.2.rc1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -3
  3. data/README.rdoc +3 -1
  4. data/lib/action_view.rb +2 -1
  5. data/lib/action_view/base.rb +5 -5
  6. data/lib/action_view/cache_expiry.rb +54 -0
  7. data/lib/action_view/digestor.rb +5 -10
  8. data/lib/action_view/gem_version.rb +2 -2
  9. data/lib/action_view/helpers/form_helper.rb +2 -2
  10. data/lib/action_view/helpers/form_options_helper.rb +4 -3
  11. data/lib/action_view/helpers/form_tag_helper.rb +5 -2
  12. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  13. data/lib/action_view/helpers/sanitize_helper.rb +10 -16
  14. data/lib/action_view/helpers/tag_helper.rb +1 -1
  15. data/lib/action_view/helpers/tags/base.rb +1 -1
  16. data/lib/action_view/helpers/translation_helper.rb +3 -3
  17. data/lib/action_view/helpers/url_helper.rb +2 -2
  18. data/lib/action_view/layouts.rb +5 -1
  19. data/lib/action_view/lookup_context.rb +11 -4
  20. data/lib/action_view/path_set.rb +5 -10
  21. data/lib/action_view/railtie.rb +1 -1
  22. data/lib/action_view/renderer/partial_renderer.rb +0 -3
  23. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +20 -13
  24. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  25. data/lib/action_view/renderer/template_renderer.rb +9 -3
  26. data/lib/action_view/rendering.rb +3 -2
  27. data/lib/action_view/template.rb +43 -50
  28. data/lib/action_view/template/error.rb +21 -1
  29. data/lib/action_view/template/handlers.rb +3 -3
  30. data/lib/action_view/template/handlers/erb/erubi.rb +2 -2
  31. data/lib/action_view/template/raw_file.rb +28 -0
  32. data/lib/action_view/template/resolver.rb +73 -117
  33. data/lib/action_view/template/sources.rb +13 -0
  34. data/lib/action_view/template/sources/file.rb +17 -0
  35. data/lib/action_view/testing/resolvers.rb +32 -21
  36. data/lib/action_view/unbound_template.rb +32 -0
  37. data/lib/assets/compiled/rails-ujs.js +21 -12
  38. metadata +24 -17
  39. data/lib/action_view/file_template.rb +0 -33
@@ -109,7 +109,7 @@ module ActionView
109
109
  end
110
110
  end
111
111
 
112
- def annoted_source_code
112
+ def annotated_source_code
113
113
  source_extract(4)
114
114
  end
115
115
 
@@ -138,4 +138,24 @@ module ActionView
138
138
  end
139
139
 
140
140
  TemplateError = Template::Error
141
+
142
+ class SyntaxErrorInTemplate < TemplateError #:nodoc
143
+ def initialize(template, offending_code_string)
144
+ @offending_code_string = offending_code_string
145
+ super(template)
146
+ end
147
+
148
+ def message
149
+ <<~MESSAGE
150
+ Encountered a syntax error while rendering template: check #{@offending_code_string}
151
+ MESSAGE
152
+ end
153
+
154
+ def annotated_source_code
155
+ @offending_code_string.split("\n").map.with_index(1) { |line, index|
156
+ indentation = " " * 4
157
+ "#{index}:#{indentation}#{line}"
158
+ }
159
+ end
160
+ end
141
161
  end
@@ -45,12 +45,12 @@ module ActionView #:nodoc:
45
45
 
46
46
  unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2
47
47
  ActiveSupport::Deprecation.warn <<~eowarn
48
- Single arity template handlers are deprecated. Template handlers must
48
+ Single arity template handlers are deprecated. Template handlers must
49
49
  now accept two parameters, the view object and the source for the view object.
50
50
  Change:
51
- >> #{handler.class}#call(#{params.map(&:last).join(", ")})
51
+ >> #{handler}.call(#{params.map(&:last).join(", ")})
52
52
  To:
53
- >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source)
53
+ >> #{handler}.call(#{params.map(&:last).join(", ")}, source)
54
54
  eowarn
55
55
  handler = LegacyHandlerWrapper.new(handler)
56
56
  end
@@ -25,9 +25,9 @@ module ActionView
25
25
  src = @src
26
26
  view = Class.new(ActionView::Base) {
27
27
  include action_view_erb_handler_context._routes.url_helpers
28
- class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0)
28
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
29
29
  }.empty
30
- view.run(:_template, nil, {}, ActionView::OutputBuffer.new)
30
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
31
31
  end
32
32
 
33
33
  private
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView #:nodoc:
4
+ # = Action View RawFile Template
5
+ class Template #:nodoc:
6
+ class RawFile #:nodoc:
7
+ attr_accessor :type, :format
8
+
9
+ def initialize(filename)
10
+ @filename = filename.to_s
11
+ extname = ::File.extname(filename).delete(".")
12
+ @type = Template::Types[extname] || Template::Types[:text]
13
+ @format = @type.symbol
14
+ end
15
+
16
+ def identifier
17
+ @filename
18
+ end
19
+
20
+ def render(*args)
21
+ ::File.read(@filename)
22
+ end
23
+
24
+ def formats; Array(format); end
25
+ deprecate :formats
26
+ end
27
+ end
28
+ end
@@ -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
@@ -112,19 +97,6 @@ module ActionView
112
97
  def canonical_no_templates(templates)
113
98
  templates.empty? ? NO_TEMPLATES : templates
114
99
  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
100
  end
129
101
 
130
102
  cattr_accessor :caching, default: true
@@ -143,35 +115,33 @@ module ActionView
143
115
 
144
116
  # Normalizes the arguments and passes it on to find_templates.
145
117
  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
118
+ locals = locals.map(&:to_s).sort!.freeze
150
119
 
151
- def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
152
120
  cached(key, [name, prefix, partial], details, locals) do
153
- find_templates(name, prefix, partial, details, true)
121
+ _find_all(name, prefix, partial, details, key, locals)
154
122
  end
155
123
  end
156
124
 
125
+ alias :find_all_anywhere :find_all
126
+ deprecate :find_all_anywhere
127
+
157
128
  def find_all_with_query(query) # :nodoc:
158
129
  @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
159
130
  end
160
131
 
161
132
  private
162
133
 
134
+ def _find_all(name, prefix, partial, details, key, locals)
135
+ find_templates(name, prefix, partial, details, locals)
136
+ end
137
+
163
138
  delegate :caching?, to: :class
164
139
 
165
140
  # This is what child classes implement. No defaults are needed
166
141
  # because Resolver guarantees that the arguments are present and
167
142
  # 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)
143
+ def find_templates(name, prefix, partial, details, locals = [])
144
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
175
145
  end
176
146
 
177
147
  # Handles templates caching. If a key is given and caching is on
@@ -180,24 +150,13 @@ module ActionView
180
150
  # resolver is fresher before returning it.
181
151
  def cached(key, path_info, details, locals)
182
152
  name, prefix, partial = path_info
183
- locals = locals.map(&:to_s).sort!
184
153
 
185
154
  if key
186
155
  @cache.cache(key, name, prefix, partial, locals) do
187
- decorate(yield, path_info, details, locals)
156
+ yield
188
157
  end
189
158
  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.variants = details[:variants] || [] if t.variants.empty?
200
- t.virtual_path ||= (cached ||= build_path(*path_info))
159
+ yield
201
160
  end
202
161
  end
203
162
  end
@@ -208,33 +167,60 @@ module ActionView
208
167
  DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
209
168
 
210
169
  def initialize(pattern = nil)
211
- @pattern = pattern || DEFAULT_PATTERN
170
+ if pattern
171
+ ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
172
+ @pattern = pattern
173
+ else
174
+ @pattern = DEFAULT_PATTERN
175
+ end
176
+ @unbound_templates = Concurrent::Map.new
177
+ super()
178
+ end
179
+
180
+ def clear_cache
181
+ @unbound_templates.clear
212
182
  super()
213
183
  end
214
184
 
215
185
  private
216
186
 
217
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
187
+ def _find_all(name, prefix, partial, details, key, locals)
218
188
  path = Path.build(name, prefix, partial)
219
- query(path, details, details[:formats], outside_app_allowed)
189
+ query(path, details, details[:formats], locals, cache: !!key)
220
190
  end
221
191
 
222
- def query(path, details, formats, outside_app_allowed)
192
+ def query(path, details, formats, locals, cache:)
223
193
  template_paths = find_template_paths_from_details(path, details)
224
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
194
+ template_paths = reject_files_external_to_app(template_paths)
225
195
 
226
196
  template_paths.map do |template|
227
- handler, format, variant = extract_handler_and_format_and_variant(template, formats.first)
228
-
229
- FileTemplate.new(File.expand_path(template), handler,
230
- virtual_path: path.virtual,
231
- format: format,
232
- variant: variant,
233
- updated_at: mtime(template)
234
- )
197
+ unbound_template =
198
+ if cache
199
+ @unbound_templates.compute_if_absent([template, path.virtual]) do
200
+ build_unbound_template(template, path.virtual)
201
+ end
202
+ else
203
+ build_unbound_template(template, path.virtual)
204
+ end
205
+
206
+ unbound_template.bind_locals(locals)
235
207
  end
236
208
  end
237
209
 
210
+ def build_unbound_template(template, virtual_path)
211
+ handler, format, variant = extract_handler_and_format_and_variant(template)
212
+ source = Template::Sources::File.new(template)
213
+
214
+ UnboundTemplate.new(
215
+ source,
216
+ template,
217
+ handler,
218
+ virtual_path: virtual_path,
219
+ format: format,
220
+ variant: variant,
221
+ )
222
+ end
223
+
238
224
  def reject_files_external_to_app(files)
239
225
  files.reject { |filename| !inside_path?(@path, filename) }
240
226
  end
@@ -283,15 +269,10 @@ module ActionView
283
269
  entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
284
270
  end
285
271
 
286
- # Returns the file mtime from the filesystem.
287
- def mtime(p)
288
- File.mtime(p)
289
- end
290
-
291
272
  # Extract handler, formats and variant from path. If a format cannot be found neither
292
273
  # from the path, or the handler, we should return the array of formats given
293
274
  # to the resolver.
294
- def extract_handler_and_format_and_variant(path, query_format)
275
+ def extract_handler_and_format_and_variant(path)
295
276
  pieces = File.basename(path).split(".")
296
277
  pieces.shift
297
278
 
@@ -305,54 +286,19 @@ module ActionView
305
286
  if handler.respond_to?(:default_format) # default_format can return nil
306
287
  handler.default_format
307
288
  else
308
- query_format
289
+ nil
309
290
  end
310
291
  end
311
292
 
312
293
  # Template::Types[format] and handler.default_format can return nil
313
- [handler, format || query_format, variant]
294
+ [handler, format, variant]
314
295
  end
315
296
  end
316
297
 
317
- # A resolver that loads files from the filesystem. It allows setting your own
318
- # resolving pattern. Such pattern can be a glob string supported by some variables.
319
- #
320
- # ==== Examples
321
- #
322
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
323
- # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
324
- #
325
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
326
- #
327
- # This one allows you to keep files with different formats in separate subdirectories,
328
- # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
329
- # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
330
- #
331
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
332
- #
333
- # If you don't specify a pattern then the default will be used.
334
- #
335
- # In order to use any of the customized resolvers above in a Rails application, you just need
336
- # to configure ActionController::Base.view_paths in an initializer, for example:
337
- #
338
- # ActionController::Base.view_paths = FileSystemResolver.new(
339
- # Rails.root.join("app/views"),
340
- # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
341
- # )
342
- #
343
- # ==== Pattern format and variables
344
- #
345
- # Pattern has to be a valid glob string, and it allows you to use the
346
- # following variables:
347
- #
348
- # * <tt>:prefix</tt> - usually the controller path
349
- # * <tt>:action</tt> - name of the action
350
- # * <tt>:locale</tt> - possible locale versions
351
- # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
352
- # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
353
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
354
- #
298
+ # A resolver that loads files from the filesystem.
355
299
  class FileSystemResolver < PathResolver
300
+ attr_reader :path
301
+
356
302
  def initialize(path, pattern = nil)
357
303
  raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
358
304
  super(pattern)
@@ -372,6 +318,10 @@ module ActionView
372
318
 
373
319
  # An Optimized resolver for Rails' most common case.
374
320
  class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
321
+ def initialize(path)
322
+ super(path)
323
+ end
324
+
375
325
  private
376
326
 
377
327
  def find_template_paths_from_details(path, details)
@@ -427,12 +377,18 @@ module ActionView
427
377
  # The same as FileSystemResolver but does not allow templates to store
428
378
  # a virtual path since it is invalid for such resolvers.
429
379
  class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
380
+ private_class_method :new
381
+
430
382
  def self.instances
431
383
  [new(""), new("/")]
432
384
  end
433
385
 
434
- def decorate(*)
435
- super.each { |t| t.virtual_path = nil }
386
+ def build_unbound_template(template, _)
387
+ super(template, nil)
388
+ end
389
+
390
+ def reject_files_external_to_app(files)
391
+ files
436
392
  end
437
393
  end
438
394
  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
@@ -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
@@ -7,10 +7,15 @@ module ActionView #:nodoc:
7
7
  # file system. This is used internally by Rails' own test suite, and is
8
8
  # useful for testing extensions that have no way of knowing what the file
9
9
  # system will look like at runtime.
10
- class FixtureResolver < PathResolver
10
+ class FixtureResolver < OptimizedFileSystemResolver
11
11
  def initialize(hash = {}, pattern = nil)
12
- super(pattern)
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
13
17
  @hash = hash
18
+ @path = ""
14
19
  end
15
20
 
16
21
  def data
@@ -23,34 +28,40 @@ module ActionView #:nodoc:
23
28
 
24
29
  private
25
30
 
26
- def query(path, exts, _, _)
27
- query = +""
28
- EXTENSIONS.each_key do |ext|
29
- query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
30
- end
31
- query = /^(#{Regexp.escape(path)})#{query}$/
32
-
33
- templates = []
34
- @hash.each do |_path, array|
35
- source, updated_at = array
36
- next unless query.match?(_path)
37
- handler, format, variant = extract_handler_and_format_and_variant(_path, :html)
38
- templates << Template.new(source, _path, handler,
31
+ def query(path, exts, _, locals, cache:)
32
+ regex = build_regex(path, exts)
33
+
34
+ @hash.select do |_path, _|
35
+ ("/" + _path).match?(regex)
36
+ end.map do |_path, source|
37
+ handler, format, variant = extract_handler_and_format_and_variant(_path)
38
+
39
+ Template.new(source, _path, handler,
39
40
  virtual_path: path.virtual,
40
41
  format: format,
41
42
  variant: variant,
42
- updated_at: updated_at
43
+ locals: locals
43
44
  )
45
+ end.sort_by do |t|
46
+ match = ("/" + t.identifier).match(regex)
47
+ EXTENSIONS.keys.reverse.map do |ext|
48
+ if ext == :variants && exts[ext] == :any
49
+ match[ext].nil? ? 0 : 1
50
+ elsif match[ext].nil?
51
+ exts[ext].length
52
+ else
53
+ found = match[ext].to_sym
54
+ exts[ext].index(found)
55
+ end
56
+ end
44
57
  end
45
-
46
- templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
47
58
  end
48
59
  end
49
60
 
50
61
  class NullResolver < PathResolver
51
- def query(path, exts, _, _)
52
- handler, format, variant = extract_handler_and_format_and_variant(path, :html)
53
- [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)]
62
+ def query(path, exts, _, locals, cache:)
63
+ handler, format, variant = extract_handler_and_format_and_variant(path)
64
+ [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)]
54
65
  end
55
66
  end
56
67
  end