actionview 5.2.7.1 → 6.1.4.6

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -112
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/dependency_tracker.rb +10 -4
  10. data/lib/action_view/digestor.rb +15 -22
  11. data/lib/action_view/flows.rb +0 -1
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  16. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  17. data/lib/action_view/helpers/cache_helper.rb +23 -22
  18. data/lib/action_view/helpers/capture_helper.rb +4 -0
  19. data/lib/action_view/helpers/csp_helper.rb +4 -2
  20. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  21. data/lib/action_view/helpers/date_helper.rb +73 -30
  22. data/lib/action_view/helpers/form_helper.rb +305 -37
  23. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  24. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  25. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  26. data/lib/action_view/helpers/number_helper.rb +14 -8
  27. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  28. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  29. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  30. data/lib/action_view/helpers/tag_helper.rb +100 -55
  31. data/lib/action_view/helpers/tags/base.rb +18 -11
  32. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  34. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  36. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  37. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  39. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  40. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -1
  42. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  44. data/lib/action_view/helpers/tags/select.rb +1 -2
  45. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  46. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  47. data/lib/action_view/helpers/tags/translator.rb +1 -6
  48. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  49. data/lib/action_view/helpers/text_helper.rb +4 -5
  50. data/lib/action_view/helpers/translation_helper.rb +94 -54
  51. data/lib/action_view/helpers/url_helper.rb +136 -28
  52. data/lib/action_view/helpers.rb +0 -2
  53. data/lib/action_view/layouts.rb +8 -10
  54. data/lib/action_view/log_subscriber.rb +30 -15
  55. data/lib/action_view/lookup_context.rb +63 -35
  56. data/lib/action_view/path_set.rb +3 -12
  57. data/lib/action_view/railtie.rb +42 -26
  58. data/lib/action_view/record_identifier.rb +2 -3
  59. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  60. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  61. data/lib/action_view/renderer/object_renderer.rb +34 -0
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/renderer.rb +59 -4
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  66. data/lib/action_view/renderer/template_renderer.rb +35 -27
  67. data/lib/action_view/rendering.rb +54 -33
  68. data/lib/action_view/routing_url_for.rb +13 -12
  69. data/lib/action_view/template/error.rb +30 -15
  70. data/lib/action_view/template/handlers/builder.rb +2 -2
  71. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  72. data/lib/action_view/template/handlers/erb.rb +16 -11
  73. data/lib/action_view/template/handlers/html.rb +1 -1
  74. data/lib/action_view/template/handlers/raw.rb +2 -2
  75. data/lib/action_view/template/handlers.rb +1 -1
  76. data/lib/action_view/template/html.rb +5 -6
  77. data/lib/action_view/template/inline.rb +22 -0
  78. data/lib/action_view/template/raw_file.rb +25 -0
  79. data/lib/action_view/template/renderable.rb +24 -0
  80. data/lib/action_view/template/resolver.rb +191 -150
  81. data/lib/action_view/template/sources/file.rb +17 -0
  82. data/lib/action_view/template/sources.rb +13 -0
  83. data/lib/action_view/template/text.rb +2 -3
  84. data/lib/action_view/template.rb +66 -75
  85. data/lib/action_view/test_case.rb +21 -29
  86. data/lib/action_view/testing/resolvers.rb +18 -27
  87. data/lib/action_view/unbound_template.rb +31 -0
  88. data/lib/action_view/view_paths.rb +59 -38
  89. data/lib/action_view.rb +7 -2
  90. data/lib/assets/compiled/rails-ujs.js +32 -6
  91. metadata +29 -18
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -3,7 +3,7 @@
3
3
  module ActionView
4
4
  module Template::Handlers
5
5
  class Html < Raw
6
- def call(template)
6
+ def call(template, source)
7
7
  "ActionView::OutputBuffer.new #{super}"
8
8
  end
9
9
  end
@@ -3,8 +3,8 @@
3
3
  module ActionView
4
4
  module Template::Handlers
5
5
  class Raw
6
- def call(template)
7
- "#{template.source.inspect}.html_safe;"
6
+ def call(template, source)
7
+ "#{source.inspect}.html_safe;"
8
8
  end
9
9
  end
10
10
  end
@@ -14,7 +14,7 @@ module ActionView #:nodoc:
14
14
  base.register_template_handler :erb, ERB.new
15
15
  base.register_template_handler :html, Html.new
16
16
  base.register_template_handler :builder, Builder.new
17
- base.register_template_handler :ruby, :source.to_proc
17
+ base.register_template_handler :ruby, lambda { |_, source| source }
18
18
  end
19
19
 
20
20
  @@template_handlers = {}
@@ -4,12 +4,11 @@ module ActionView #:nodoc:
4
4
  # = Action View HTML Template
5
5
  class Template #:nodoc:
6
6
  class HTML #:nodoc:
7
- attr_accessor :type
7
+ attr_reader :type
8
8
 
9
- def initialize(string, type = nil)
9
+ def initialize(string, type)
10
10
  @string = string.to_s
11
- @type = Types[type] || type if type
12
- @type ||= Types[:html]
11
+ @type = type
13
12
  end
14
13
 
15
14
  def identifier
@@ -26,8 +25,8 @@ module ActionView #:nodoc:
26
25
  to_str
27
26
  end
28
27
 
29
- def formats
30
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
28
+ def format
29
+ @type
31
30
  end
32
31
  end
33
32
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView #:nodoc:
4
+ class Template #:nodoc:
5
+ class Inline < Template #:nodoc:
6
+ # This finalizer is needed (and exactly with a proc inside another proc)
7
+ # otherwise templates leak in development.
8
+ Finalizer = proc do |method_name, mod| # :nodoc:
9
+ proc do
10
+ mod.module_eval do
11
+ remove_possible_method method_name
12
+ end
13
+ end
14
+ end
15
+
16
+ def compile(mod)
17
+ super
18
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
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
+ end
24
+ end
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
@@ -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
@@ -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,31 +93,16 @@ 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
65
100
  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
101
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
78
102
  end
79
103
 
80
104
  def cache_query(query) # :nodoc:
81
- if Resolver.caching?
82
- @query_cache[query] ||= canonical_no_templates(yield)
83
- else
84
- yield
85
- end
105
+ @query_cache[query] ||= canonical_no_templates(yield)
86
106
  end
87
107
 
88
108
  def clear
@@ -90,7 +110,7 @@ module ActionView
90
110
  @query_cache.clear
91
111
  end
92
112
 
93
- # Get the cache size. Do not call this
113
+ # Get the cache size. Do not call this
94
114
  # method. This method is not guaranteed to be here ever.
95
115
  def size # :nodoc:
96
116
  size = 0
@@ -108,23 +128,9 @@ module ActionView
108
128
  end
109
129
 
110
130
  private
111
-
112
131
  def canonical_no_templates(templates)
113
132
  templates.empty? ? NO_TEMPLATES : templates
114
133
  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
134
  end
129
135
 
130
136
  cattr_accessor :caching, default: true
@@ -143,14 +149,10 @@ module ActionView
143
149
 
144
150
  # Normalizes the arguments and passes it on to find_templates.
145
151
  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
152
+ locals = locals.map(&:to_s).sort!.freeze
150
153
 
151
- def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
152
154
  cached(key, [name, prefix, partial], details, locals) do
153
- find_templates(name, prefix, partial, details, true)
155
+ _find_all(name, prefix, partial, details, key, locals)
154
156
  end
155
157
  end
156
158
 
@@ -159,19 +161,17 @@ module ActionView
159
161
  end
160
162
 
161
163
  private
164
+ def _find_all(name, prefix, partial, details, key, locals)
165
+ find_templates(name, prefix, partial, details, locals)
166
+ end
162
167
 
163
168
  delegate :caching?, to: :class
164
169
 
165
170
  # This is what child classes implement. No defaults are needed
166
171
  # because Resolver guarantees that the arguments are present and
167
172
  # 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)
173
+ def find_templates(name, prefix, partial, details, locals = [])
174
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
175
175
  end
176
176
 
177
177
  # Handles templates caching. If a key is given and caching is on
@@ -180,25 +180,13 @@ module ActionView
180
180
  # resolver is fresher before returning it.
181
181
  def cached(key, path_info, details, locals)
182
182
  name, prefix, partial = path_info
183
- locals = locals.map(&:to_s).sort!
184
183
 
185
184
  if key
186
185
  @cache.cache(key, name, prefix, partial, locals) do
187
- decorate(yield, path_info, details, locals)
186
+ yield
188
187
  end
189
188
  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))
189
+ yield
202
190
  end
203
191
  end
204
192
  end
@@ -208,41 +196,74 @@ module ActionView
208
196
  EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
209
197
  DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
210
198
 
211
- def initialize(pattern = nil)
212
- @pattern = pattern || DEFAULT_PATTERN
213
- super()
199
+ def initialize
200
+ @pattern = DEFAULT_PATTERN
201
+ @unbound_templates = Concurrent::Map.new
202
+ @path_parser = PathParser.new
203
+ super
214
204
  end
215
205
 
216
- private
206
+ def clear_cache
207
+ @unbound_templates.clear
208
+ @path_parser = PathParser.new
209
+ super
210
+ end
217
211
 
218
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
212
+ private
213
+ def _find_all(name, prefix, partial, details, key, locals)
219
214
  path = Path.build(name, prefix, partial)
220
- query(path, details, details[:formats], outside_app_allowed)
215
+ query(path, details, details[:formats], locals, cache: !!key)
221
216
  end
222
217
 
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
218
+ def query(path, details, formats, locals, cache:)
219
+ template_paths = find_template_paths_from_details(path, details)
220
+ template_paths = reject_files_external_to_app(template_paths)
228
221
 
229
222
  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
- )
223
+ unbound_template =
224
+ if cache
225
+ @unbound_templates.compute_if_absent([template, path.virtual]) do
226
+ build_unbound_template(template, path.virtual)
227
+ end
228
+ else
229
+ build_unbound_template(template, path.virtual)
230
+ end
231
+
232
+ unbound_template.bind_locals(locals)
239
233
  end
240
234
  end
241
235
 
236
+ def source_for_template(template)
237
+ Template::Sources::File.new(template)
238
+ end
239
+
240
+ def build_unbound_template(template, virtual_path)
241
+ handler, format, variant = extract_handler_and_format_and_variant(template)
242
+ source = source_for_template(template)
243
+
244
+ UnboundTemplate.new(
245
+ source,
246
+ template,
247
+ handler,
248
+ virtual_path: virtual_path,
249
+ format: format,
250
+ variant: variant,
251
+ )
252
+ end
253
+
242
254
  def reject_files_external_to_app(files)
243
255
  files.reject { |filename| !inside_path?(@path, filename) }
244
256
  end
245
257
 
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
+
263
+ query = build_query(path, details)
264
+ find_template_paths(query)
265
+ end
266
+
246
267
  def find_template_paths(query)
247
268
  Dir[query].uniq.reject do |filename|
248
269
  File.directory?(filename) ||
@@ -265,7 +286,7 @@ module ActionView
265
286
  query.gsub!(/:prefix(\/)?/, prefix)
266
287
 
267
288
  partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
268
- query.gsub!(/:action/, partial)
289
+ query.gsub!(":action", partial)
269
290
 
270
291
  details.each do |ext, candidates|
271
292
  if ext == :variants && candidates == :any
@@ -279,73 +300,31 @@ module ActionView
279
300
  end
280
301
 
281
302
  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)
303
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
288
304
  end
289
305
 
290
306
  # Extract handler, formats and variant from path. If a format cannot be found neither
291
307
  # from the path, or the handler, we should return the array of formats given
292
308
  # to the resolver.
293
309
  def extract_handler_and_format_and_variant(path)
294
- pieces = File.basename(path).split(".".freeze)
295
- pieces.shift
296
-
297
- extension = pieces.pop
310
+ details = @path_parser.parse(path)
298
311
 
299
- handler = Template.handler_for_extension(extension)
300
- format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
301
- format &&= Template::Types[format]
312
+ handler = Template.handler_for_extension(details[:handler])
313
+ format = details[:format] || handler.try(:default_format)
314
+ variant = details[:variant]
302
315
 
316
+ # Template::Types[format] and handler.default_format can return nil
303
317
  [handler, format, variant]
304
318
  end
305
319
  end
306
320
 
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
- #
321
+ # A resolver that loads files from the filesystem.
345
322
  class FileSystemResolver < PathResolver
346
- def initialize(path, pattern = nil)
323
+ attr_reader :path
324
+
325
+ def initialize(path)
347
326
  raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
348
- super(pattern)
327
+ super()
349
328
  @path = File.expand_path(path)
350
329
  end
351
330
 
@@ -362,30 +341,92 @@ module ActionView
362
341
 
363
342
  # An Optimized resolver for Rails' most common case.
364
343
  class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
365
- def build_query(path, details)
366
- query = escape_entry(File.join(@path, path))
367
-
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}}"
344
+ def initialize(path)
345
+ super(path)
346
+ end
347
+
348
+ private
349
+ def find_candidate_template_paths(path)
350
+ # Instead of checking for every possible path, as our other globs would
351
+ # do, scan the directory for files with the right prefix.
352
+ query = "#{escape_entry(File.join(@path, path))}*"
353
+
354
+ Dir[query].reject do |filename|
355
+ File.directory?(filename)
373
356
  end
374
- end.join
357
+ end
375
358
 
376
- query + exts
377
- end
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
+
367
+ regex = build_regex(path, details)
368
+
369
+ candidates.uniq.reject do |filename|
370
+ # This regex match does double duty of finding only files which match
371
+ # details (instead of just matching the prefix) and also filtering for
372
+ # case-insensitive file systems.
373
+ !regex.match?(filename) ||
374
+ File.directory?(filename)
375
+ end.sort_by do |filename|
376
+ # Because we scanned the directory, instead of checking for files
377
+ # one-by-one, they will be returned in an arbitrary order.
378
+ # We can use the matches found by the regex and sort by their index in
379
+ # details.
380
+ match = filename.match(regex)
381
+ EXTENSIONS.keys.map do |ext|
382
+ if ext == :variants && details[ext] == :any
383
+ match[ext].nil? ? 0 : 1
384
+ elsif match[ext].nil?
385
+ # No match should be last
386
+ details[ext].length
387
+ else
388
+ found = match[ext].to_sym
389
+ details[ext].index(found)
390
+ end
391
+ end
392
+ end
393
+ end
394
+
395
+ def build_regex(path, details)
396
+ query = Regexp.escape(File.join(@path, path))
397
+ exts = EXTENSIONS.map do |ext, prefix|
398
+ match =
399
+ if ext == :variants && details[ext] == :any
400
+ ".*?"
401
+ else
402
+ arr = details[ext].compact
403
+ arr.uniq!
404
+ arr.map! { |e| Regexp.escape(e) }
405
+ arr.join("|")
406
+ end
407
+ prefix = Regexp.escape(prefix)
408
+ "(#{prefix}(?<#{ext}>#{match}))?"
409
+ end.join
410
+
411
+ %r{\A#{query}#{exts}\z}
412
+ end
378
413
  end
379
414
 
380
415
  # The same as FileSystemResolver but does not allow templates to store
381
416
  # a virtual path since it is invalid for such resolvers.
382
417
  class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
418
+ private_class_method :new
419
+
383
420
  def self.instances
384
421
  [new(""), new("/")]
385
422
  end
386
423
 
387
- def decorate(*)
388
- super.each { |t| t.virtual_path = nil }
424
+ def build_unbound_template(template, _)
425
+ super(template, nil)
426
+ end
427
+
428
+ def reject_files_external_to_app(files)
429
+ files
389
430
  end
390
431
  end
391
432
  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,8 +24,8 @@ 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
31
30
  end
32
31
  end