actionview 4.2.11.1 → 6.0.4.8

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +242 -186
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +144 -37
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +54 -20
  10. data/lib/action_view/digestor.rb +88 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
  15. data/lib/action_view/helpers/asset_url_helper.rb +171 -67
  16. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  17. data/lib/action_view/helpers/cache_helper.rb +112 -42
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +230 -129
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +755 -129
  25. data/lib/action_view/helpers/form_options_helper.rb +130 -75
  26. data/lib/action_view/helpers/form_tag_helper.rb +116 -71
  27. data/lib/action_view/helpers/javascript_helper.rb +30 -14
  28. data/lib/action_view/helpers/number_helper.rb +84 -59
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +11 -8
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +232 -75
  33. data/lib/action_view/helpers/tags/base.rb +138 -98
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +37 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -1
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -1
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -1
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +91 -47
  71. data/lib/action_view/helpers/url_helper.rb +160 -105
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +65 -61
  74. data/lib/action_view/log_subscriber.rb +61 -10
  75. data/lib/action_view/lookup_context.rb +147 -89
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +28 -23
  78. data/lib/action_view/railtie.rb +62 -6
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +71 -13
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +239 -225
  83. data/lib/action_view/renderer/renderer.rb +22 -8
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
  85. data/lib/action_view/renderer/template_renderer.rb +79 -73
  86. data/lib/action_view/rendering.rb +68 -44
  87. data/lib/action_view/routing_url_for.rb +33 -22
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +44 -29
  90. data/lib/action_view/template/handlers/builder.rb +12 -13
  91. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  92. data/lib/action_view/template/handlers/erb.rb +24 -86
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +4 -4
  95. data/lib/action_view/template/handlers.rb +38 -8
  96. data/lib/action_view/template/html.rb +19 -10
  97. data/lib/action_view/template/inline.rb +22 -0
  98. data/lib/action_view/template/raw_file.rb +28 -0
  99. data/lib/action_view/template/resolver.rb +217 -193
  100. data/lib/action_view/template/sources/file.rb +17 -0
  101. data/lib/action_view/template/sources.rb +13 -0
  102. data/lib/action_view/template/text.rb +11 -10
  103. data/lib/action_view/template/types.rb +18 -18
  104. data/lib/action_view/template.rb +146 -90
  105. data/lib/action_view/test_case.rb +52 -32
  106. data/lib/action_view/testing/resolvers.rb +46 -34
  107. data/lib/action_view/unbound_template.rb +31 -0
  108. data/lib/action_view/version.rb +3 -1
  109. data/lib/action_view/view_paths.rb +48 -31
  110. data/lib/action_view.rb +11 -8
  111. data/lib/assets/compiled/rails-ujs.js +746 -0
  112. metadata +41 -32
  113. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  114. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pathname"
2
4
  require "active_support/core_ext/class"
3
5
  require "active_support/core_ext/module/attribute_accessors"
4
- require 'active_support/core_ext/string/filters'
5
6
  require "action_view/template"
6
7
  require "thread"
7
- require "thread_safe"
8
+ require "concurrent/map"
8
9
 
9
10
  module ActionView
10
11
  # = Action View Resolver
@@ -15,7 +16,7 @@ module ActionView
15
16
  alias_method :partial?, :partial
16
17
 
17
18
  def self.build(name, prefix, partial)
18
- virtual = ""
19
+ virtual = +""
19
20
  virtual << "#{prefix}/" unless prefix.empty?
20
21
  virtual << (partial ? "_#{name}" : name)
21
22
  new name, prefix, partial, virtual
@@ -36,67 +37,68 @@ module ActionView
36
37
 
37
38
  # Threadsafe template cache
38
39
  class Cache #:nodoc:
39
- class SmallCache < ThreadSafe::Cache
40
+ class SmallCache < Concurrent::Map
40
41
  def initialize(options = {})
41
- super(options.merge(:initial_capacity => 2))
42
+ super(options.merge(initial_capacity: 2))
42
43
  end
43
44
  end
44
45
 
45
46
  # preallocate all the default blocks for performance/memory consumption reasons
46
- PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
47
- PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
48
- NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
49
- KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
47
+ PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
48
+ PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
49
+ NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
50
+ KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
50
51
 
51
52
  # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
52
53
  NO_TEMPLATES = [].freeze
53
54
 
54
55
  def initialize
55
56
  @data = SmallCache.new(&KEY_BLOCK)
57
+ @query_cache = SmallCache.new
58
+ end
59
+
60
+ def inspect
61
+ "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
56
62
  end
57
63
 
58
64
  # Cache the templates returned by the block
59
65
  def cache(key, name, prefix, partial, locals)
60
- if Resolver.caching?
61
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
62
- else
63
- fresh_templates = yield
64
- cached_templates = @data[key][name][prefix][partial][locals]
66
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
67
+ end
65
68
 
66
- if templates_have_changed?(cached_templates, fresh_templates)
67
- @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
68
- else
69
- cached_templates || NO_TEMPLATES
70
- end
71
- end
69
+ def cache_query(query) # :nodoc:
70
+ @query_cache[query] ||= canonical_no_templates(yield)
72
71
  end
73
72
 
74
73
  def clear
75
74
  @data.clear
75
+ @query_cache.clear
76
76
  end
77
77
 
78
- private
78
+ # Get the cache size. Do not call this
79
+ # method. This method is not guaranteed to be here ever.
80
+ def size # :nodoc:
81
+ size = 0
82
+ @data.each_value do |v1|
83
+ v1.each_value do |v2|
84
+ v2.each_value do |v3|
85
+ v3.each_value do |v4|
86
+ size += v4.size
87
+ end
88
+ end
89
+ end
90
+ end
79
91
 
80
- def canonical_no_templates(templates)
81
- templates.empty? ? NO_TEMPLATES : templates
92
+ size + @query_cache.size
82
93
  end
83
94
 
84
- def templates_have_changed?(cached_templates, fresh_templates)
85
- # if either the old or new template list is empty, we don't need to (and can't)
86
- # compare modification times, and instead just check whether the lists are different
87
- if cached_templates.blank? || fresh_templates.blank?
88
- return fresh_templates.blank? != cached_templates.blank?
95
+ private
96
+ def canonical_no_templates(templates)
97
+ templates.empty? ? NO_TEMPLATES : templates
89
98
  end
90
-
91
- cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
92
-
93
- # if a template has changed, it will be now be newer than all the cached templates
94
- fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
95
- end
96
99
  end
97
100
 
98
- cattr_accessor :caching
99
- self.caching = true
101
+ cattr_accessor :caching, default: true
100
102
 
101
103
  class << self
102
104
  alias :caching? :caching
@@ -111,218 +113,190 @@ module ActionView
111
113
  end
112
114
 
113
115
  # Normalizes the arguments and passes it on to find_templates.
114
- def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
116
+ def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
117
+ locals = locals.map(&:to_s).sort!.freeze
118
+
115
119
  cached(key, [name, prefix, partial], details, locals) do
116
- find_templates(name, prefix, partial, details, false)
120
+ _find_all(name, prefix, partial, details, key, locals)
117
121
  end
118
122
  end
119
123
 
120
- def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[])
121
- cached(key, [name, prefix, partial], details, locals) do
122
- find_templates(name, prefix, partial, details, true)
123
- end
124
+ alias :find_all_anywhere :find_all
125
+ deprecate :find_all_anywhere
126
+
127
+ def find_all_with_query(query) # :nodoc:
128
+ @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
124
129
  end
125
130
 
126
131
  private
132
+ def _find_all(name, prefix, partial, details, key, locals)
133
+ find_templates(name, prefix, partial, details, locals)
134
+ end
127
135
 
128
136
  delegate :caching?, to: :class
129
137
 
130
138
  # This is what child classes implement. No defaults are needed
131
139
  # because Resolver guarantees that the arguments are present and
132
140
  # normalized.
133
- def find_templates(name, prefix, partial, details, outside_app_allowed)
134
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed) method"
135
- end
136
-
137
- # Helpers that builds a path. Useful for building virtual paths.
138
- def build_path(name, prefix, partial)
139
- 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"
140
143
  end
141
144
 
142
145
  # Handles templates caching. If a key is given and caching is on
143
146
  # always check the cache before hitting the resolver. Otherwise,
144
147
  # it always hits the resolver but if the key is present, check if the
145
148
  # resolver is fresher before returning it.
146
- def cached(key, path_info, details, locals) #:nodoc:
149
+ def cached(key, path_info, details, locals)
147
150
  name, prefix, partial = path_info
148
- locals = locals.map { |x| x.to_s }.sort!
149
151
 
150
152
  if key
151
153
  @cache.cache(key, name, prefix, partial, locals) do
152
- decorate(yield, path_info, details, locals)
154
+ yield
153
155
  end
154
156
  else
155
- decorate(yield, path_info, details, locals)
156
- end
157
- end
158
-
159
- # Ensures all the resolver information is set in the template.
160
- def decorate(templates, path_info, details, locals) #:nodoc:
161
- cached = nil
162
- templates.each do |t|
163
- t.locals = locals
164
- t.formats = details[:formats] || [:html] if t.formats.empty?
165
- t.variants = details[:variants] || [] if t.variants.empty?
166
- t.virtual_path ||= (cached ||= build_path(*path_info))
157
+ yield
167
158
  end
168
159
  end
169
160
  end
170
161
 
171
162
  # An abstract class that implements a Resolver with path semantics.
172
163
  class PathResolver < Resolver #:nodoc:
173
- EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
164
+ EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
174
165
  DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
175
166
 
176
- def initialize(pattern=nil)
177
- @pattern = pattern || DEFAULT_PATTERN
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
174
+ @unbound_templates = Concurrent::Map.new
178
175
  super()
179
176
  end
180
177
 
181
- private
182
-
183
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
184
- path = Path.build(name, prefix, partial)
185
- query(path, details, details[:formats], outside_app_allowed)
178
+ def clear_cache
179
+ @unbound_templates.clear
180
+ super()
186
181
  end
187
182
 
188
- def query(path, details, formats, outside_app_allowed)
189
- query = build_query(path, details)
190
-
191
- template_paths = find_template_paths query
192
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
183
+ private
184
+ def _find_all(name, prefix, partial, details, key, locals)
185
+ path = Path.build(name, prefix, partial)
186
+ query(path, details, details[:formats], locals, cache: !!key)
187
+ end
193
188
 
194
- template_paths.map { |template|
195
- handler, format, variant = extract_handler_and_format_and_variant(template, formats)
196
- contents = File.binread(template)
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)
192
+
193
+ template_paths.map do |template|
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)
204
+ end
205
+ end
197
206
 
198
- Template.new(contents, File.expand_path(template), handler,
199
- :virtual_path => path.virtual,
200
- :format => format,
201
- :variant => variant,
202
- :updated_at => mtime(template)
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,
203
218
  )
204
- }
205
- end
219
+ end
206
220
 
207
- def reject_files_external_to_app(files)
208
- files.reject { |filename| !inside_path?(@path, filename) }
209
- end
221
+ def reject_files_external_to_app(files)
222
+ files.reject { |filename| !inside_path?(@path, filename) }
223
+ end
224
+
225
+ def find_template_paths_from_details(path, details)
226
+ query = build_query(path, details)
227
+ find_template_paths(query)
228
+ end
210
229
 
211
- if RUBY_VERSION >= '2.2.0'
212
230
  def find_template_paths(query)
213
- Dir[query].reject { |filename|
231
+ Dir[query].uniq.reject do |filename|
214
232
  File.directory?(filename) ||
215
233
  # deals with case-insensitive file systems.
216
234
  !File.fnmatch(query, filename, File::FNM_EXTGLOB)
217
- }
235
+ end
218
236
  end
219
- else
220
- def find_template_paths(query)
221
- # deals with case-insensitive file systems.
222
- sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
223
237
 
224
- Dir[query].reject { |filename|
225
- File.directory?(filename) ||
226
- !sanitizer[File.dirname(filename)].include?(filename)
227
- }
238
+ def inside_path?(path, filename)
239
+ filename = File.expand_path(filename)
240
+ path = File.join(path, "")
241
+ filename.start_with?(path)
228
242
  end
229
- end
230
243
 
231
- def inside_path?(path, filename)
232
- filename = File.expand_path(filename)
233
- path = File.join(path, '')
234
- filename.start_with?(path)
235
- end
244
+ # Helper for building query glob string based on resolver's pattern.
245
+ def build_query(path, details)
246
+ query = @pattern.dup
236
247
 
237
- # Helper for building query glob string based on resolver's pattern.
238
- def build_query(path, details)
239
- query = @pattern.dup
248
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
249
+ query.gsub!(/:prefix(\/)?/, prefix)
240
250
 
241
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
242
- query.gsub!(/\:prefix(\/)?/, prefix)
251
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
252
+ query.gsub!(/:action/, partial)
243
253
 
244
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
245
- query.gsub!(/\:action/, partial)
254
+ details.each do |ext, candidates|
255
+ if ext == :variants && candidates == :any
256
+ query.gsub!(/:#{ext}/, "*")
257
+ else
258
+ query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
259
+ end
260
+ end
246
261
 
247
- details.each do |ext, variants|
248
- query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
262
+ File.expand_path(query, @path)
249
263
  end
250
264
 
251
- File.expand_path(query, @path)
252
- end
253
-
254
- def escape_entry(entry)
255
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
256
- end
265
+ def escape_entry(entry)
266
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
267
+ end
257
268
 
258
- # Returns the file mtime from the filesystem.
259
- def mtime(p)
260
- File.mtime(p)
261
- end
269
+ # Extract handler, formats and variant from path. If a format cannot be found neither
270
+ # from the path, or the handler, we should return the array of formats given
271
+ # to the resolver.
272
+ def extract_handler_and_format_and_variant(path)
273
+ pieces = File.basename(path).split(".")
274
+ pieces.shift
262
275
 
263
- # Extract handler, formats and variant from path. If a format cannot be found neither
264
- # from the path, or the handler, we should return the array of formats given
265
- # to the resolver.
266
- def extract_handler_and_format_and_variant(path, default_formats)
267
- pieces = File.basename(path).split(".")
268
- pieces.shift
269
-
270
- extension = pieces.pop
271
- unless extension
272
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
273
- The file #{path} did not specify a template handler. The default is
274
- currently ERB, but will change to RAW in the future.
275
- MSG
276
- end
276
+ extension = pieces.pop
277
277
 
278
- handler = Template.handler_for_extension(extension)
279
- format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
280
- format &&= Template::Types[format]
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
281
289
 
282
- [handler, format, variant]
283
- end
290
+ # Template::Types[format] and handler.default_format can return nil
291
+ [handler, format, variant]
292
+ end
284
293
  end
285
294
 
286
- # A resolver that loads files from the filesystem. It allows setting your own
287
- # resolving pattern. Such pattern can be a glob string supported by some variables.
288
- #
289
- # ==== Examples
290
- #
291
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
292
- # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
293
- #
294
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
295
- #
296
- # This one allows you to keep files with different formats in separate subdirectories,
297
- # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
298
- # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
299
- #
300
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
301
- #
302
- # If you don't specify a pattern then the default will be used.
303
- #
304
- # In order to use any of the customized resolvers above in a Rails application, you just need
305
- # to configure ActionController::Base.view_paths in an initializer, for example:
306
- #
307
- # ActionController::Base.view_paths = FileSystemResolver.new(
308
- # Rails.root.join("app/views"),
309
- # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}"
310
- # )
311
- #
312
- # ==== Pattern format and variables
313
- #
314
- # Pattern has to be a valid glob string, and it allows you to use the
315
- # following variables:
316
- #
317
- # * <tt>:prefix</tt> - usually the controller path
318
- # * <tt>:action</tt> - name of the action
319
- # * <tt>:locale</tt> - possible locale versions
320
- # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
321
- # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
322
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
323
- #
295
+ # A resolver that loads files from the filesystem.
324
296
  class FileSystemResolver < PathResolver
325
- def initialize(path, pattern=nil)
297
+ attr_reader :path
298
+
299
+ def initialize(path, pattern = nil)
326
300
  raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
327
301
  super(pattern)
328
302
  @path = File.expand_path(path)
@@ -341,26 +315,76 @@ module ActionView
341
315
 
342
316
  # An Optimized resolver for Rails' most common case.
343
317
  class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
344
- def build_query(path, details)
345
- query = escape_entry(File.join(@path, path))
318
+ def initialize(path)
319
+ super(path)
320
+ end
346
321
 
347
- exts = EXTENSIONS.map do |ext, prefix|
348
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
349
- end.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
353
+ end
354
+ end
350
355
 
351
- query + exts
352
- 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
353
371
  end
354
372
 
355
373
  # The same as FileSystemResolver but does not allow templates to store
356
374
  # a virtual path since it is invalid for such resolvers.
357
375
  class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
376
+ private_class_method :new
377
+
358
378
  def self.instances
359
379
  [new(""), new("/")]
360
380
  end
361
381
 
362
- def decorate(*)
363
- 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
364
388
  end
365
389
  end
366
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
@@ -1,22 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView #:nodoc:
2
4
  # = Action View Text Template
3
- class Template
5
+ class Template #:nodoc:
4
6
  class Text #:nodoc:
5
7
  attr_accessor :type
6
8
 
7
- def initialize(string, type = nil)
9
+ def initialize(string)
8
10
  @string = string.to_s
9
- @type = Types[type] || type if type
10
- @type ||= Types[:text]
11
11
  end
12
12
 
13
13
  def identifier
14
- 'text template'
14
+ "text template"
15
15
  end
16
16
 
17
- def inspect
18
- 'text template'
19
- end
17
+ alias_method :inspect, :identifier
20
18
 
21
19
  def to_str
22
20
  @string
@@ -26,9 +24,12 @@ module ActionView #:nodoc:
26
24
  to_str
27
25
  end
28
26
 
29
- def formats
30
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
27
+ def format
28
+ :text
31
29
  end
30
+
31
+ def formats; Array(format); end
32
+ deprecate :formats
32
33
  end
33
34
  end
34
35
  end