actionview 5.2.4 → 6.0.0

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -77
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -2
  5. data/lib/action_view.rb +3 -2
  6. data/lib/action_view/base.rb +107 -10
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/digestor.rb +12 -20
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers.rb +0 -2
  13. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +18 -10
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +69 -25
  20. data/lib/action_view/helpers/form_helper.rb +238 -6
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +12 -11
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +5 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  28. data/lib/action_view/helpers/tag_helper.rb +7 -6
  29. data/lib/action_view/helpers/tags/base.rb +9 -5
  30. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/translator.rb +1 -6
  32. data/lib/action_view/helpers/text_helper.rb +3 -3
  33. data/lib/action_view/helpers/translation_helper.rb +16 -12
  34. data/lib/action_view/helpers/url_helper.rb +14 -14
  35. data/lib/action_view/layouts.rb +5 -5
  36. data/lib/action_view/log_subscriber.rb +6 -6
  37. data/lib/action_view/lookup_context.rb +73 -31
  38. data/lib/action_view/path_set.rb +5 -10
  39. data/lib/action_view/railtie.rb +24 -1
  40. data/lib/action_view/record_identifier.rb +2 -2
  41. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  42. data/lib/action_view/renderer/partial_renderer.rb +66 -55
  43. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
  44. data/lib/action_view/renderer/renderer.rb +16 -4
  45. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
  46. data/lib/action_view/renderer/template_renderer.rb +24 -18
  47. data/lib/action_view/rendering.rb +51 -31
  48. data/lib/action_view/routing_url_for.rb +12 -11
  49. data/lib/action_view/template.rb +102 -70
  50. data/lib/action_view/template/error.rb +21 -1
  51. data/lib/action_view/template/handlers.rb +27 -1
  52. data/lib/action_view/template/handlers/builder.rb +2 -2
  53. data/lib/action_view/template/handlers/erb.rb +17 -7
  54. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  55. data/lib/action_view/template/handlers/html.rb +1 -1
  56. data/lib/action_view/template/handlers/raw.rb +2 -2
  57. data/lib/action_view/template/html.rb +14 -5
  58. data/lib/action_view/template/inline.rb +22 -0
  59. data/lib/action_view/template/raw_file.rb +28 -0
  60. data/lib/action_view/template/resolver.rb +136 -133
  61. data/lib/action_view/template/sources.rb +13 -0
  62. data/lib/action_view/template/sources/file.rb +17 -0
  63. data/lib/action_view/template/text.rb +5 -3
  64. data/lib/action_view/test_case.rb +1 -1
  65. data/lib/action_view/testing/resolvers.rb +33 -20
  66. data/lib/action_view/unbound_template.rb +32 -0
  67. data/lib/action_view/view_paths.rb +25 -1
  68. data/lib/assets/compiled/rails-ujs.js +30 -4
  69. metadata +23 -18
  70. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -322,7 +322,7 @@ module ActionView
322
322
  end
323
323
 
324
324
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
325
- def _layout(formats)
325
+ def _layout(lookup_context, formats)
326
326
  if _conditional_layout?
327
327
  #{layout_definition}
328
328
  else
@@ -388,8 +388,8 @@ module ActionView
388
388
  case name
389
389
  when String then _normalize_layout(name)
390
390
  when Proc then name
391
- when true then Proc.new { |formats| _default_layout(formats, true) }
392
- when :default then Proc.new { |formats| _default_layout(formats, false) }
391
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
392
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
393
393
  when false, nil then nil
394
394
  else
395
395
  raise ArgumentError,
@@ -411,9 +411,9 @@ module ActionView
411
411
  #
412
412
  # ==== Returns
413
413
  # * <tt>template</tt> - The template object for the default layout (or +nil+)
414
- def _default_layout(formats, require_layout = false)
414
+ def _default_layout(lookup_context, formats, require_layout = false)
415
415
  begin
416
- value = _layout(formats) if action_has_layout?
416
+ value = _layout(lookup_context, formats) if action_has_layout?
417
417
  rescue NameError => e
418
418
  raise e, "Could not render layout: #{e.message}"
419
419
  end
@@ -16,17 +16,17 @@ module ActionView
16
16
 
17
17
  def render_template(event)
18
18
  info do
19
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
19
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
20
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
- message << " (#{event.duration.round(1)}ms)"
21
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
22
  end
23
23
  end
24
24
 
25
25
  def render_partial(event)
26
26
  info do
27
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
27
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
28
28
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
- message << " (#{event.duration.round(1)}ms)"
29
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
30
30
  message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
31
31
  message
32
32
  end
@@ -37,7 +37,7 @@ module ActionView
37
37
 
38
38
  info do
39
39
  " Rendered collection of #{from_rails_root(identifier)}" \
40
- " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
40
+ " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
41
41
  end
42
42
  end
43
43
 
@@ -85,7 +85,7 @@ module ActionView
85
85
 
86
86
  def log_rendering_start(payload)
87
87
  info do
88
- message = " Rendering #{from_rails_root(payload[:identifier])}".dup
88
+ message = +" Rendering #{from_rails_root(payload[:identifier])}"
89
89
  message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
90
90
  message
91
91
  end
@@ -3,6 +3,7 @@
3
3
  require "concurrent/map"
4
4
  require "active_support/core_ext/module/remove_method"
5
5
  require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/deprecation"
6
7
  require "action_view/template/resolver"
7
8
 
8
9
  module ActionView
@@ -15,6 +16,8 @@ module ActionView
15
16
  # only once during the request, it speeds up all cache accesses.
16
17
  class LookupContext #:nodoc:
17
18
  attr_accessor :prefixes, :rendered_format
19
+ deprecate :rendered_format
20
+ deprecate :rendered_format=
18
21
 
19
22
  mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
20
23
 
@@ -24,7 +27,7 @@ module ActionView
24
27
  registered_details << name
25
28
  Accessors::DEFAULT_PROCS[name] = block
26
29
 
27
- Accessors.send :define_method, :"default_#{name}", &block
30
+ Accessors.define_method(:"default_#{name}", &block)
28
31
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
32
  def #{name}
30
33
  @details.fetch(:#{name}, [])
@@ -57,21 +60,36 @@ module ActionView
57
60
  alias :eql? :equal?
58
61
 
59
62
  @details_keys = Concurrent::Map.new
63
+ @digest_cache = Concurrent::Map.new
60
64
 
61
- def self.get(details)
65
+ def self.digest_cache(details)
66
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
67
+ end
68
+
69
+ def self.details_cache_key(details)
62
70
  if details[:formats]
63
71
  details = details.dup
64
72
  details[:formats] &= Template::Types.symbols
65
73
  end
66
- @details_keys[details] ||= Concurrent::Map.new
74
+ @details_keys[details] ||= Object.new
67
75
  end
68
76
 
69
77
  def self.clear
78
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
79
+ path_set.each(&:clear_cache)
80
+ end
81
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
82
+ @view_context_class = nil
70
83
  @details_keys.clear
84
+ @digest_cache.clear
71
85
  end
72
86
 
73
87
  def self.digest_caches
74
- @details_keys.values
88
+ @digest_cache.values
89
+ end
90
+
91
+ def self.view_context_class(klass)
92
+ @view_context_class ||= klass.with_empty_template_cache
75
93
  end
76
94
  end
77
95
 
@@ -82,7 +100,7 @@ module ActionView
82
100
  # Calculate the details key. Remove the handlers from calculation to improve performance
83
101
  # since the user cannot modify it explicitly.
84
102
  def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
103
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
104
  end
87
105
 
88
106
  # Temporary skip passing the details_key forward.
@@ -96,7 +114,8 @@ module ActionView
96
114
  private
97
115
 
98
116
  def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
117
+ @details = @details.dup if @digest_cache || @details_key
118
+ @digest_cache = nil
100
119
  @details_key = nil
101
120
  @details[key] = value
102
121
  end
@@ -106,20 +125,13 @@ module ActionView
106
125
  module ViewPaths
107
126
  attr_reader :view_paths, :html_fallback_for_js
108
127
 
109
- # Whenever setting view paths, makes a copy so that we can manipulate them in
110
- # instance objects as we wish.
111
- def view_paths=(paths)
112
- @view_paths = ActionView::PathSet.new(Array(paths))
113
- end
114
-
115
128
  def find(name, prefixes = [], partial = false, keys = [], options = {})
116
129
  @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
130
  end
118
131
  alias :find_template :find
119
132
 
120
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
121
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
122
- end
133
+ alias :find_file :find
134
+ deprecate :find_file
123
135
 
124
136
  def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
137
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
@@ -138,19 +150,34 @@ module ActionView
138
150
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
151
  # a :file.
140
152
  def with_fallbacks
141
- added_resolvers = 0
142
- self.class.fallbacks.each do |resolver|
143
- next if view_paths.include?(resolver)
144
- view_paths.push(resolver)
145
- added_resolvers += 1
153
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
154
+
155
+ if block_given?
156
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
157
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
158
+ the lookup context returned by `with_fallbacks` instead.
159
+ eowarn
160
+
161
+ begin
162
+ _view_paths = @view_paths
163
+ @view_paths = view_paths
164
+ yield
165
+ ensure
166
+ @view_paths = _view_paths
167
+ end
168
+ else
169
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
170
  end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
171
  end
151
172
 
152
173
  private
153
174
 
175
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
176
+ # instance objects as we wish.
177
+ def build_view_paths(paths)
178
+ ActionView::PathSet.new(Array(paths))
179
+ end
180
+
154
181
  def args_for_lookup(name, prefixes, partial, keys, details_options)
155
182
  name, prefixes = normalize_name(name, prefixes)
156
183
  details, details_key = detail_args_for(details_options)
@@ -163,7 +190,7 @@ module ActionView
163
190
  user_details = @details.merge(options)
164
191
 
165
192
  if @cache
166
- details_key = DetailsKey.get(user_details)
193
+ details_key = DetailsKey.details_cache_key(user_details)
167
194
  else
168
195
  details_key = nil
169
196
  end
@@ -190,7 +217,7 @@ module ActionView
190
217
  end
191
218
 
192
219
  if @cache
193
- [details, DetailsKey.get(details)]
220
+ [details, DetailsKey.details_cache_key(details)]
194
221
  else
195
222
  [details, nil]
196
223
  end
@@ -202,13 +229,13 @@ module ActionView
202
229
  # name instead of the prefix.
203
230
  def normalize_name(name, prefixes)
204
231
  prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
232
+ parts = name.to_s.split("/")
206
233
  parts.shift if parts.first.empty?
207
234
  name = parts.pop
208
235
 
209
236
  return name, prefixes || [""] if parts.empty?
210
237
 
211
- parts = parts.join("/".freeze)
238
+ parts = parts.join("/")
212
239
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
240
 
214
241
  return name, prefixes
@@ -221,16 +248,23 @@ module ActionView
221
248
 
222
249
  def initialize(view_paths, details = {}, prefixes = [])
223
250
  @details_key = nil
251
+ @digest_cache = nil
224
252
  @cache = true
225
253
  @prefixes = prefixes
226
- @rendered_format = nil
227
254
 
228
255
  @details = initialize_details({}, details)
229
- self.view_paths = view_paths
256
+ @view_paths = build_view_paths(view_paths)
230
257
  end
231
258
 
232
259
  def digest_cache
233
- details_key
260
+ @digest_cache ||= DetailsKey.digest_cache(@details)
261
+ end
262
+
263
+ def with_prepended_formats(formats)
264
+ details = @details.dup
265
+ details[:formats] = formats
266
+
267
+ self.class.new(@view_paths, details, @prefixes)
234
268
  end
235
269
 
236
270
  def initialize_details(target, details)
@@ -245,7 +279,15 @@ module ActionView
245
279
  # add :html as fallback to :js.
246
280
  def formats=(values)
247
281
  if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
282
+ values = values.dup
283
+ values.concat(default_formats) if values.delete "*/*"
284
+ values.uniq!
285
+
286
+ invalid_values = (values - Template::Types.symbols)
287
+ unless invalid_values.empty?
288
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
289
+ end
290
+
249
291
  if values == [:js]
250
292
  values << :html
251
293
  @html_fallback_for_js = true
@@ -48,12 +48,11 @@ module ActionView #:nodoc:
48
48
  find_all(*args).first || raise(MissingTemplate.new(self, *args))
49
49
  end
50
50
 
51
- def find_file(path, prefixes = [], *args)
52
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
53
- end
51
+ alias :find_file :find
52
+ deprecate :find_file
54
53
 
55
54
  def find_all(path, prefixes = [], *args)
56
- _find_all path, prefixes, args, false
55
+ _find_all path, prefixes, args
57
56
  end
58
57
 
59
58
  def exists?(path, prefixes, *args)
@@ -71,15 +70,11 @@ module ActionView #:nodoc:
71
70
 
72
71
  private
73
72
 
74
- def _find_all(path, prefixes, args, outside_app)
73
+ def _find_all(path, prefixes, args)
75
74
  prefixes = [prefixes] if String === prefixes
76
75
  prefixes.each do |prefix|
77
76
  paths.each do |resolver|
78
- if outside_app
79
- templates = resolver.find_all_anywhere(path, prefix, *args)
80
- else
81
- templates = resolver.find_all(path, prefix, *args)
82
- end
77
+ templates = resolver.find_all(path, prefix, *args)
83
78
  return templates unless templates.empty?
84
79
  end
85
80
  end
@@ -6,9 +6,13 @@ require "rails"
6
6
  module ActionView
7
7
  # = Action View Railtie
8
8
  class Railtie < Rails::Engine # :nodoc:
9
+ NULL_OPTION = Object.new
10
+
9
11
  config.action_view = ActiveSupport::OrderedOptions.new
10
12
  config.action_view.embed_authenticity_token_in_remote_forms = nil
11
13
  config.action_view.debug_missing_translation = true
14
+ config.action_view.default_enforce_utf8 = nil
15
+ config.action_view.finalize_compiled_template_methods = NULL_OPTION
12
16
 
13
17
  config.eager_load_namespaces << ActionView
14
18
 
@@ -35,6 +39,25 @@ module ActionView
35
39
  end
36
40
  end
37
41
 
42
+ initializer "action_view.default_enforce_utf8" do |app|
43
+ ActiveSupport.on_load(:action_view) do
44
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
45
+ unless default_enforce_utf8.nil?
46
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
47
+ end
48
+ end
49
+ end
50
+
51
+ initializer "action_view.finalize_compiled_template_methods" do |app|
52
+ ActiveSupport.on_load(:action_view) do
53
+ option = app.config.action_view.delete(:finalize_compiled_template_methods)
54
+
55
+ if option != NULL_OPTION
56
+ ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
57
+ end
58
+ end
59
+ end
60
+
38
61
  initializer "action_view.logger" do
39
62
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
63
  end
@@ -58,7 +81,7 @@ module ActionView
58
81
  initializer "action_view.per_request_digest_cache" do |app|
59
82
  ActiveSupport.on_load(:action_view) do
60
83
  unless ActionView::Resolver.caching?
61
- app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
84
+ app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
62
85
  end
63
86
  end
64
87
  end
@@ -59,8 +59,8 @@ module ActionView
59
59
 
60
60
  include ModelNaming
61
61
 
62
- JOIN = "_".freeze
63
- NEW = "new".freeze
62
+ JOIN = "_"
63
+ NEW = "new"
64
64
 
65
65
  # The DOM class convention is to use the singular form of an object or class.
66
66
  #
@@ -17,7 +17,7 @@ module ActionView
17
17
  # that new object is called in turn. This abstracts the setup and rendering
18
18
  # into a separate classes for partials and templates.
19
19
  class AbstractRenderer #:nodoc:
20
- delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
20
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
21
21
 
22
22
  def initialize(lookup_context)
23
23
  @lookup_context = lookup_context
@@ -27,6 +27,53 @@ module ActionView
27
27
  raise NotImplementedError
28
28
  end
29
29
 
30
+ class RenderedCollection # :nodoc:
31
+ def self.empty(format)
32
+ EmptyCollection.new format
33
+ end
34
+
35
+ attr_reader :rendered_templates
36
+
37
+ def initialize(rendered_templates, spacer)
38
+ @rendered_templates = rendered_templates
39
+ @spacer = spacer
40
+ end
41
+
42
+ def body
43
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
44
+ end
45
+
46
+ def format
47
+ rendered_templates.first.format
48
+ end
49
+
50
+ class EmptyCollection
51
+ attr_reader :format
52
+
53
+ def initialize(format)
54
+ @format = format
55
+ end
56
+
57
+ def body; nil; end
58
+ end
59
+ end
60
+
61
+ class RenderedTemplate # :nodoc:
62
+ attr_reader :body, :layout, :template
63
+
64
+ def initialize(body, layout, template)
65
+ @body = body
66
+ @layout = layout
67
+ @template = template
68
+ end
69
+
70
+ def format
71
+ template.format
72
+ end
73
+
74
+ EMPTY_SPACER = Struct.new(:body).new
75
+ end
76
+
30
77
  private
31
78
 
32
79
  def extract_details(options) # :doc:
@@ -38,8 +85,6 @@ module ActionView
38
85
  end
39
86
 
40
87
  def instrument(name, **options) # :doc:
41
- options[:identifier] ||= (@template && @template.identifier) || @path
42
-
43
88
  ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
44
89
  yield payload
45
90
  end
@@ -51,5 +96,13 @@ module ActionView
51
96
 
52
97
  @lookup_context.formats = formats | @lookup_context.formats
53
98
  end
99
+
100
+ def build_rendered_template(content, template, layout = nil)
101
+ RenderedTemplate.new content, layout, template
102
+ end
103
+
104
+ def build_rendered_collection(templates, spacer)
105
+ RenderedCollection.new templates, spacer
106
+ end
54
107
  end
55
108
  end