actionview 5.2.4.4 → 6.0.0.beta1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -91
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/context.rb +5 -4
  8. data/lib/action_view/digestor.rb +7 -6
  9. data/lib/action_view/gem_version.rb +4 -4
  10. data/lib/action_view/helpers.rb +0 -2
  11. data/lib/action_view/helpers/asset_tag_helper.rb +4 -27
  12. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  13. data/lib/action_view/helpers/cache_helper.rb +18 -10
  14. data/lib/action_view/helpers/capture_helper.rb +4 -0
  15. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  16. data/lib/action_view/helpers/date_helper.rb +69 -25
  17. data/lib/action_view/helpers/form_helper.rb +240 -8
  18. data/lib/action_view/helpers/form_options_helper.rb +23 -15
  19. data/lib/action_view/helpers/form_tag_helper.rb +9 -9
  20. data/lib/action_view/helpers/javascript_helper.rb +10 -11
  21. data/lib/action_view/helpers/number_helper.rb +5 -0
  22. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  23. data/lib/action_view/helpers/tag_helper.rb +7 -6
  24. data/lib/action_view/helpers/tags/base.rb +8 -4
  25. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  26. data/lib/action_view/helpers/tags/translator.rb +1 -6
  27. data/lib/action_view/helpers/text_helper.rb +3 -3
  28. data/lib/action_view/helpers/translation_helper.rb +11 -18
  29. data/lib/action_view/helpers/url_helper.rb +14 -14
  30. data/lib/action_view/log_subscriber.rb +6 -6
  31. data/lib/action_view/lookup_context.rb +4 -4
  32. data/lib/action_view/railtie.rb +18 -0
  33. data/lib/action_view/record_identifier.rb +2 -2
  34. data/lib/action_view/renderer/partial_renderer.rb +2 -2
  35. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +40 -1
  36. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  37. data/lib/action_view/rendering.rb +5 -4
  38. data/lib/action_view/routing_url_for.rb +12 -11
  39. data/lib/action_view/template.rb +25 -8
  40. data/lib/action_view/template/handlers/erb.rb +12 -2
  41. data/lib/action_view/template/resolver.rb +56 -16
  42. data/lib/action_view/test_case.rb +1 -1
  43. data/lib/action_view/testing/resolvers.rb +1 -1
  44. data/lib/assets/compiled/rails-ujs.js +39 -22
  45. metadata +14 -15
  46. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -200,9 +200,9 @@ module ActionView
200
200
  html_options = convert_options_to_data_attributes(options, html_options)
201
201
 
202
202
  url = url_for(options)
203
- html_options["href".freeze] ||= url
203
+ html_options["href"] ||= url
204
204
 
205
- content_tag("a".freeze, name || url, html_options, &block)
205
+ content_tag("a", name || url, html_options, &block)
206
206
  end
207
207
 
208
208
  # Generates a form containing a single button that submits to the URL created
@@ -253,7 +253,7 @@ module ActionView
253
253
  # # <input value="New" type="submit" />
254
254
  # # </form>"
255
255
  #
256
- # <%= button_to "New", new_article_path %>
256
+ # <%= button_to "New", new_articles_path %>
257
257
  # # => "<form method="post" action="/articles/new" class="button_to">
258
258
  # # <input value="New" type="submit" />
259
259
  # # </form>"
@@ -308,7 +308,7 @@ module ActionView
308
308
  params = html_options.delete("params")
309
309
 
310
310
  method = html_options.delete("method").to_s
311
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe
311
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
312
312
 
313
313
  form_method = method == "get" ? "get" : "post"
314
314
  form_options = html_options.delete("form") || {}
@@ -321,7 +321,7 @@ module ActionView
321
321
  request_method = method.empty? ? "post" : method
322
322
  token_tag(nil, form_options: { action: url, method: request_method })
323
323
  else
324
- "".freeze
324
+ ""
325
325
  end
326
326
 
327
327
  html_options = convert_options_to_data_attributes(options, html_options)
@@ -487,12 +487,12 @@ module ActionView
487
487
  option = html_options.delete(item).presence || next
488
488
  "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
489
489
  }.compact
490
- extras = extras.empty? ? "".freeze : "?" + extras.join("&")
490
+ extras = extras.empty? ? "" : "?" + extras.join("&")
491
491
 
492
492
  encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
493
493
  html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
494
494
 
495
- content_tag("a".freeze, name || email_address, html_options, &block)
495
+ content_tag("a", name || email_address, html_options, &block)
496
496
  end
497
497
 
498
498
  # True if the current request URI was generated by the given +options+.
@@ -575,21 +575,21 @@ module ActionView
575
575
  def convert_options_to_data_attributes(options, html_options)
576
576
  if html_options
577
577
  html_options = html_options.stringify_keys
578
- html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
578
+ html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
579
579
 
580
- method = html_options.delete("method".freeze)
580
+ method = html_options.delete("method")
581
581
 
582
582
  add_method_to_attributes!(html_options, method) if method
583
583
 
584
584
  html_options
585
585
  else
586
- link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
586
+ link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
587
587
  end
588
588
  end
589
589
 
590
590
  def link_to_remote_options?(options)
591
591
  if options.is_a?(Hash)
592
- options.delete("remote".freeze) || options.delete(:remote)
592
+ options.delete("remote") || options.delete(:remote)
593
593
  end
594
594
  end
595
595
 
@@ -618,11 +618,11 @@ module ActionView
618
618
  end
619
619
 
620
620
  def token_tag(token = nil, form_options: {})
621
- if token != false && protect_against_forgery?
621
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
622
622
  token ||= form_authenticity_token(form_options: form_options)
623
623
  tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
624
624
  else
625
- "".freeze
625
+ ""
626
626
  end
627
627
  end
628
628
 
@@ -636,7 +636,7 @@ module ActionView
636
636
  # to_form_params(name: 'David', nationality: 'Danish')
637
637
  # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
638
638
  #
639
- # to_form_params(country: {name: 'Denmark'})
639
+ # to_form_params(country: { name: 'Denmark' })
640
640
  # # => [{name: 'country[name]', value: 'Denmark'}]
641
641
  #
642
642
  # to_form_params(countries: ['Denmark', 'Sweden']})
@@ -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
@@ -24,7 +24,7 @@ module ActionView
24
24
  registered_details << name
25
25
  Accessors::DEFAULT_PROCS[name] = block
26
26
 
27
- Accessors.send :define_method, :"default_#{name}", &block
27
+ Accessors.define_method(:"default_#{name}", &block)
28
28
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
29
  def #{name}
30
30
  @details.fetch(:#{name}, [])
@@ -202,13 +202,13 @@ module ActionView
202
202
  # name instead of the prefix.
203
203
  def normalize_name(name, prefixes)
204
204
  prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
205
+ parts = name.to_s.split("/")
206
206
  parts.shift if parts.first.empty?
207
207
  name = parts.pop
208
208
 
209
209
  return name, prefixes || [""] if parts.empty?
210
210
 
211
- parts = parts.join("/".freeze)
211
+ parts = parts.join("/")
212
212
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
213
 
214
214
  return name, prefixes
@@ -245,7 +245,7 @@ module ActionView
245
245
  # add :html as fallback to :js.
246
246
  def formats=(values)
247
247
  if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
248
+ values.concat(default_formats) if values.delete "*/*"
249
249
  if values == [:js]
250
250
  values << :html
251
251
  @html_fallback_for_js = true
@@ -9,6 +9,8 @@ module ActionView
9
9
  config.action_view = ActiveSupport::OrderedOptions.new
10
10
  config.action_view.embed_authenticity_token_in_remote_forms = nil
11
11
  config.action_view.debug_missing_translation = true
12
+ config.action_view.default_enforce_utf8 = nil
13
+ config.action_view.finalize_compiled_template_methods = true
12
14
 
13
15
  config.eager_load_namespaces << ActionView
14
16
 
@@ -35,6 +37,22 @@ module ActionView
35
37
  end
36
38
  end
37
39
 
40
+ initializer "action_view.default_enforce_utf8" do |app|
41
+ ActiveSupport.on_load(:action_view) do
42
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
43
+ unless default_enforce_utf8.nil?
44
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
45
+ end
46
+ end
47
+ end
48
+
49
+ initializer "action_view.finalize_compiled_template_methods" do |app|
50
+ ActiveSupport.on_load(:action_view) do
51
+ ActionView::Template.finalize_compiled_template_methods =
52
+ app.config.action_view.delete(:finalize_compiled_template_methods)
53
+ end
54
+ end
55
+
38
56
  initializer "action_view.logger" do
39
57
  ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
58
  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
  #
@@ -363,7 +363,7 @@ module ActionView
363
363
  @options = options
364
364
  @block = block
365
365
 
366
- @locals = options[:locals] || {}
366
+ @locals = options[:locals] ? options[:locals].symbolize_keys : {}
367
367
  @details = extract_details(options)
368
368
 
369
369
  prepend_formats(options[:formats])
@@ -523,7 +523,7 @@ module ActionView
523
523
 
524
524
  def retrieve_variable(path, as)
525
525
  variable = as || begin
526
- base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
526
+ base = path[-1] == "/" ? "" : File.basename(path)
527
527
  raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
528
528
  $1.to_sym
529
529
  end
@@ -14,15 +14,35 @@ module ActionView
14
14
  def cache_collection_render(instrumentation_payload)
15
15
  return yield unless @options[:cached]
16
16
 
17
+ # Result is a hash with the key represents the
18
+ # key used for cache lookup and the value is the item
19
+ # on which the partial is being rendered
17
20
  keyed_collection = collection_by_cache_keys
21
+
22
+ # Pull all partials from cache
23
+ # Result is a hash, key matches the entry in
24
+ # `keyed_collection` where the cache was retrieved and the
25
+ # value is the value that was present in the cache
18
26
  cached_partials = collection_cache.read_multi(*keyed_collection.keys)
19
27
  instrumentation_payload[:cache_hits] = cached_partials.size
20
28
 
29
+ # Extract the items for the keys that are not found
30
+ # Set the uncached values to instance variable @collection
31
+ # which is used by the caller
21
32
  @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
33
+
34
+ # If all elements are already in cache then
35
+ # rendered partials will be an empty array
36
+ #
37
+ # If the cache is missing elements then
38
+ # the block will be called against the remaining items
39
+ # in the @collection.
22
40
  rendered_partials = @collection.empty? ? [] : yield
23
41
 
24
42
  index = 0
25
43
  fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
44
+ # This block is called once
45
+ # for every cache miss while preserving order.
26
46
  rendered_partials[index].tap { index += 1 }
27
47
  end
28
48
  end
@@ -40,10 +60,29 @@ module ActionView
40
60
  end
41
61
 
42
62
  def expanded_cache_key(key)
43
- key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
63
+ key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path, digest_path: digest_path))
44
64
  key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
45
65
  end
46
66
 
67
+ def digest_path
68
+ @digest_path ||= @view.digest_path_from_virtual(@template.virtual_path)
69
+ end
70
+
71
+ # `order_by` is an enumerable object containing keys of the cache,
72
+ # all keys are passed in whether found already or not.
73
+ #
74
+ # `cached_partials` is a hash. If the value exists
75
+ # it represents the rendered partial from the cache
76
+ # otherwise `Hash#fetch` will take the value of its block.
77
+ #
78
+ # This method expects a block that will return the rendered
79
+ # partial. An example is to render all results
80
+ # for each element that was not found in the cache and store it as an array.
81
+ # Order it so that the first empty cache element in `cached_partials`
82
+ # corresponds to the first element in `rendered_partials`.
83
+ #
84
+ # If the partial is not already cached it will also be
85
+ # written back to the underlying cache store.
47
86
  def fetch_or_cache_partial(cached_partials, order_by:)
48
87
  order_by.map do |cache_key|
49
88
  cached_partials.fetch(cache_key) do
@@ -33,7 +33,7 @@ module ActionView
33
33
  logger = ActionView::Base.logger
34
34
  return unless logger
35
35
 
36
- message = "\n#{exception.class} (#{exception.message}):\n".dup
36
+ message = +"\n#{exception.class} (#{exception.message}):\n"
37
37
  message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
38
38
  message << " " << exception.backtrace.join("\n ")
39
39
  logger.fatal("#{message}\n\n")
@@ -64,10 +64,11 @@ module ActionView
64
64
  # An instance of a view class. The default view class is ActionView::Base.
65
65
  #
66
66
  # The view class must have the following methods:
67
- # View.new[lookup_context, assigns, controller]
68
- # Create a new ActionView instance for a controller and we can also pass the arguments.
69
- # View#render(option)
70
- # Returns String with the rendered template
67
+ #
68
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> Create a new
69
+ # ActionView instance for a controller and we can also pass the arguments.
70
+ #
71
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
71
72
  #
72
73
  # Override this method in a module to change the default behavior.
73
74
  def view_context
@@ -84,25 +84,24 @@ module ActionView
84
84
  super(only_path: _generate_paths_by_default)
85
85
  when Hash
86
86
  options = options.symbolize_keys
87
- unless options.key?(:only_path)
88
- options[:only_path] = only_path?(options[:host])
89
- end
87
+ ensure_only_path_option(options)
90
88
 
91
89
  super(options)
92
90
  when ActionController::Parameters
93
- unless options.key?(:only_path)
94
- options[:only_path] = only_path?(options[:host])
95
- end
91
+ ensure_only_path_option(options)
96
92
 
97
93
  super(options)
98
94
  when :back
99
95
  _back_url
100
96
  when Array
101
97
  components = options.dup
102
- if _generate_paths_by_default
103
- polymorphic_path(components, components.extract_options!)
98
+ options = components.extract_options!
99
+ ensure_only_path_option(options)
100
+
101
+ if options[:only_path]
102
+ polymorphic_path(components, options)
104
103
  else
105
- polymorphic_url(components, components.extract_options!)
104
+ polymorphic_url(components, options)
106
105
  end
107
106
  else
108
107
  method = _generate_paths_by_default ? :path : :url
@@ -138,8 +137,10 @@ module ActionView
138
137
  true
139
138
  end
140
139
 
141
- def only_path?(host)
142
- _generate_paths_by_default unless host
140
+ def ensure_only_path_option(options)
141
+ unless options.key?(:only_path)
142
+ options[:only_path] = _generate_paths_by_default unless options[:host]
143
+ end
143
144
  end
144
145
  end
145
146
  end
@@ -9,6 +9,8 @@ module ActionView
9
9
  class Template
10
10
  extend ActiveSupport::Autoload
11
11
 
12
+ mattr_accessor :finalize_compiled_template_methods, default: true
13
+
12
14
  # === Encodings in ActionView::Template
13
15
  #
14
16
  # ActionView::Template is one of a few sources of potential
@@ -186,7 +188,7 @@ module ActionView
186
188
  end
187
189
 
188
190
  def inspect
189
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "".freeze) : identifier
191
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier
190
192
  end
191
193
 
192
194
  # This method is responsible for properly setting the encoding of the
@@ -233,6 +235,19 @@ module ActionView
233
235
  end
234
236
  end
235
237
 
238
+
239
+ # Exceptions are marshalled when using the parallel test runner with DRb, so we need
240
+ # to ensure that references to the template object can be marshalled as well. This means forgoing
241
+ # the marshalling of the compiler mutex and instantiating that again on unmarshalling.
242
+ def marshal_dump # :nodoc:
243
+ [ @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ]
244
+ end
245
+
246
+ def marshal_load(array) # :nodoc:
247
+ @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array
248
+ @compile_mutex = Mutex.new
249
+ end
250
+
236
251
  private
237
252
 
238
253
  # Compile a template. This method ensures a template is compiled
@@ -284,7 +299,7 @@ module ActionView
284
299
 
285
300
  # Make sure that the resulting String to be eval'd is in the
286
301
  # encoding of the code
287
- source = <<-end_src.dup
302
+ source = +<<-end_src
288
303
  def #{method_name}(local_assigns, output_buffer)
289
304
  _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
290
305
  ensure
@@ -307,7 +322,9 @@ module ActionView
307
322
  end
308
323
 
309
324
  mod.module_eval(source, identifier, 0)
310
- ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
325
+ if finalize_compiled_template_methods
326
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
327
+ end
311
328
  end
312
329
 
313
330
  def handle_render_error(view, e)
@@ -331,19 +348,19 @@ module ActionView
331
348
  locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
332
349
 
333
350
  # Assign for the same variable is to suppress unused variable warning
334
- locals.each_with_object("".dup) { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
351
+ locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
335
352
  end
336
353
 
337
354
  def method_name
338
355
  @method_name ||= begin
339
- m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".dup
340
- m.tr!("-".freeze, "_".freeze)
356
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
357
+ m.tr!("-", "_")
341
358
  m
342
359
  end
343
360
  end
344
361
 
345
362
  def identifier_method_name
346
- inspect.tr("^a-z_".freeze, "_".freeze)
363
+ inspect.tr("^a-z_", "_")
347
364
  end
348
365
 
349
366
  def instrument(action, &block) # :doc:
@@ -351,7 +368,7 @@ module ActionView
351
368
  end
352
369
 
353
370
  def instrument_render_template(&block)
354
- ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block)
371
+ ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
355
372
  end
356
373
 
357
374
  def instrument_payload