actionview 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -436
  3. data/README.rdoc +1 -1
  4. data/lib/action_view/base.rb +29 -11
  5. data/lib/action_view/buffers.rb +1 -1
  6. data/lib/action_view/cache_expiry.rb +9 -3
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
  8. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  9. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +8 -2
  11. data/lib/action_view/digestor.rb +6 -2
  12. data/lib/action_view/gem_version.rb +3 -3
  13. data/lib/action_view/helpers/asset_tag_helper.rb +39 -8
  14. data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
  15. data/lib/action_view/helpers/cache_helper.rb +10 -2
  16. data/lib/action_view/helpers/capture_helper.rb +2 -2
  17. data/lib/action_view/helpers/controller_helper.rb +6 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +28 -4
  20. data/lib/action_view/helpers/form_helper.rb +277 -272
  21. data/lib/action_view/helpers/form_options_helper.rb +39 -35
  22. data/lib/action_view/helpers/form_tag_helper.rb +115 -72
  23. data/lib/action_view/helpers/javascript_helper.rb +5 -1
  24. data/lib/action_view/helpers/number_helper.rb +14 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +5 -6
  26. data/lib/action_view/helpers/rendering_helper.rb +160 -50
  27. data/lib/action_view/helpers/sanitize_helper.rb +6 -0
  28. data/lib/action_view/helpers/tag_helper.rb +225 -48
  29. data/lib/action_view/helpers/tags/base.rb +11 -9
  30. data/lib/action_view/helpers/tags/check_box.rb +9 -3
  31. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
  32. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/file_field.rb +7 -2
  35. data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
  36. data/lib/action_view/helpers/tags/label.rb +3 -10
  37. data/lib/action_view/helpers/tags/radio_button.rb +1 -1
  38. data/lib/action_view/helpers/tags/select.rb +6 -1
  39. data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
  40. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  41. data/lib/action_view/helpers/tags/text_field.rb +1 -1
  42. data/lib/action_view/helpers/text_helper.rb +11 -4
  43. data/lib/action_view/helpers/translation_helper.rb +6 -1
  44. data/lib/action_view/helpers/url_helper.rb +42 -90
  45. data/lib/action_view/layouts.rb +9 -11
  46. data/lib/action_view/locale/en.yml +3 -0
  47. data/lib/action_view/log_subscriber.rb +9 -8
  48. data/lib/action_view/railtie.rb +12 -2
  49. data/lib/action_view/record_identifier.rb +22 -1
  50. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  51. data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
  52. data/lib/action_view/render_parser.rb +21 -169
  53. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  54. data/lib/action_view/renderer/partial_renderer.rb +18 -2
  55. data/lib/action_view/renderer/renderer.rb +32 -38
  56. data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
  57. data/lib/action_view/renderer/template_renderer.rb +3 -3
  58. data/lib/action_view/rendering.rb +6 -7
  59. data/lib/action_view/structured_event_subscriber.rb +97 -0
  60. data/lib/action_view/template/error.rb +18 -3
  61. data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
  62. data/lib/action_view/template/handlers/erb.rb +77 -44
  63. data/lib/action_view/template/raw_file.rb +4 -0
  64. data/lib/action_view/template/renderable.rb +7 -1
  65. data/lib/action_view/template/resolver.rb +0 -3
  66. data/lib/action_view/template.rb +39 -12
  67. data/lib/action_view/test_case.rb +57 -62
  68. data/lib/action_view.rb +4 -0
  69. metadata +17 -14
  70. data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
@@ -4,6 +4,7 @@ require "active_support/core_ext/module/attr_internal"
4
4
  require "active_support/core_ext/module/attribute_accessors"
5
5
  require "active_support/ordered_options"
6
6
  require "action_view/log_subscriber"
7
+ require "action_view/structured_event_subscriber"
7
8
  require "action_view/helpers"
8
9
  require "action_view/context"
9
10
  require "action_view/template"
@@ -80,6 +81,23 @@ module ActionView # :nodoc:
80
81
  # This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
81
82
  # <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
82
83
  #
84
+ # By default, templates will accept any <tt>locals</tt> as keyword arguments. To restrict what <tt>locals</tt> a template accepts, add a <tt>locals:</tt> magic comment:
85
+ #
86
+ # <%# locals: (headline:) %>
87
+ #
88
+ # Headline: <%= headline %>
89
+ #
90
+ # In cases where the local variables are optional, declare the keyword argument with a default value:
91
+ #
92
+ # <%# locals: (headline: nil) %>
93
+ #
94
+ # <% unless headline.nil? %>
95
+ # Headline: <%= headline %>
96
+ # <% end %>
97
+ #
98
+ # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
99
+ # in the guides.
100
+ #
83
101
  # === Template caching
84
102
  #
85
103
  # By default, \Rails will compile each template to a method in order to render it. When you alter a template,
@@ -164,6 +182,10 @@ module ActionView # :nodoc:
164
182
  class_attribute :_routes
165
183
  class_attribute :logger
166
184
 
185
+ # Specify whether to omit autocomplete="off" on hidden inputs generated by helpers.
186
+ # Configured via `config.action_view.remove_hidden_field_autocomplete`
187
+ cattr_accessor :remove_hidden_field_autocomplete, default: false
188
+
167
189
  class << self
168
190
  delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB"
169
191
 
@@ -225,8 +247,6 @@ module ActionView # :nodoc:
225
247
  # :startdoc:
226
248
 
227
249
  def initialize(lookup_context, assigns, controller) # :nodoc:
228
- @_config = ActiveSupport::InheritableOptions.new
229
-
230
250
  @lookup_context = lookup_context
231
251
 
232
252
  @view_renderer = ActionView::Renderer.new @lookup_context
@@ -248,16 +268,14 @@ module ActionView # :nodoc:
248
268
 
249
269
  if has_strict_locals
250
270
  begin
251
- public_send(method, buffer, **locals, &block)
271
+ public_send(method, locals, buffer, **locals, &block)
252
272
  rescue ArgumentError => argument_error
253
- raise(
254
- ArgumentError,
255
- argument_error.
256
- message.
257
- gsub("unknown keyword:", "unknown local:").
258
- gsub("missing keyword:", "missing local:").
259
- gsub("no keywords accepted", "no locals accepted")
260
- )
273
+ public_send_line = __LINE__ - 2
274
+ frame = argument_error.backtrace_locations[1]
275
+ if frame.path == __FILE__ && frame.lineno == public_send_line
276
+ raise StrictLocalsError.new(argument_error, @current_template)
277
+ end
278
+ raise
261
279
  end
262
280
  else
263
281
  public_send(method, locals, buffer, &block)
@@ -45,7 +45,7 @@ module ActionView
45
45
  @raw_buffer << if value.html_safe?
46
46
  value
47
47
  else
48
- CGI.escapeHTML(value)
48
+ ERB::Util.unwrapped_html_escape(value)
49
49
  end
50
50
  end
51
51
  self
@@ -10,16 +10,17 @@ module ActionView
10
10
  @watcher = nil
11
11
  @previous_change = false
12
12
 
13
- rebuild_watcher
14
-
15
13
  ActionView::PathRegistry.file_system_resolver_hooks << method(:rebuild_watcher)
16
14
  end
17
15
 
18
16
  def updated?
17
+ build_watcher unless @watcher
19
18
  @previous_change || @watcher.updated?
20
19
  end
21
20
 
22
21
  def execute
22
+ return unless @watcher
23
+
23
24
  watcher = nil
24
25
  @mutex.synchronize do
25
26
  @previous_change = false
@@ -33,7 +34,7 @@ module ActionView
33
34
  ActionView::LookupContext::DetailsKey.clear
34
35
  end
35
36
 
36
- def rebuild_watcher
37
+ def build_watcher
37
38
  @mutex.synchronize do
38
39
  old_watcher = @watcher
39
40
 
@@ -51,6 +52,11 @@ module ActionView
51
52
  end
52
53
  end
53
54
 
55
+ def rebuild_watcher
56
+ return unless @watcher
57
+ build_watcher
58
+ end
59
+
54
60
  def dirs_to_watch
55
61
  all_view_paths.uniq.sort
56
62
  end
@@ -74,7 +74,7 @@ module ActionView
74
74
  end
75
75
 
76
76
  def dependencies
77
- render_dependencies + explicit_dependencies
77
+ WildcardResolver.new(@view_paths, render_dependencies + explicit_dependencies).resolve
78
78
  end
79
79
 
80
80
  attr_reader :name, :template
@@ -90,15 +90,15 @@ module ActionView
90
90
  end
91
91
 
92
92
  def render_dependencies
93
- render_dependencies = []
94
- render_calls = source.split(/\brender\b/).drop(1)
93
+ dependencies = []
94
+ render_calls = source.scan(/<%(?:(?:(?!<%).)*?\brender\b((?:(?!%>).)*?))%>/m).flatten
95
95
 
96
96
  render_calls.each do |arguments|
97
- add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
98
- add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
97
+ add_dependencies(dependencies, arguments, LAYOUT_DEPENDENCY)
98
+ add_dependencies(dependencies, arguments, RENDER_ARGUMENTS)
99
99
  end
100
100
 
101
- render_dependencies.uniq
101
+ dependencies
102
102
  end
103
103
 
104
104
  def add_dependencies(render_dependencies, arguments, pattern)
@@ -116,12 +116,37 @@ module ActionView
116
116
  end
117
117
 
118
118
  def add_static_dependency(dependencies, dependency, quote_type)
119
- if quote_type == '"'
120
- # Ignore if there is interpolation
121
- return if dependency.include?('#{')
122
- end
119
+ if quote_type == '"' && dependency.include?('#{')
120
+ scanner = StringScanner.new(dependency)
121
+
122
+ wildcard_dependency = +""
123
+
124
+ while !scanner.eos?
125
+ if scanner.scan_until(/\#{/)
126
+ unmatched_brackets = 1
127
+ wildcard_dependency << scanner.pre_match
128
+
129
+ while unmatched_brackets > 0 && !scanner.eos?
130
+ found = scanner.scan_until(/[{}]/)
131
+ return unless found
132
+
133
+ case scanner.matched
134
+ when "{"
135
+ unmatched_brackets += 1
136
+ when "}"
137
+ unmatched_brackets -= 1
138
+ end
139
+ end
140
+
141
+ wildcard_dependency << "*"
142
+ else
143
+ wildcard_dependency << scanner.rest
144
+ scanner.terminate
145
+ end
146
+ end
123
147
 
124
- if dependency
148
+ dependencies << wildcard_dependency
149
+ elsif dependency
125
150
  if dependency.include?("/")
126
151
  dependencies << dependency
127
152
  else
@@ -130,24 +155,8 @@ module ActionView
130
155
  end
131
156
  end
132
157
 
133
- def resolve_directories(wildcard_dependencies)
134
- return [] unless @view_paths
135
- return [] if wildcard_dependencies.empty?
136
-
137
- # Remove trailing "/*"
138
- prefixes = wildcard_dependencies.map { |query| query[0..-3] }
139
-
140
- @view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
141
- path.to_s if prefixes.include?(path.prefix)
142
- }.sort
143
- end
144
-
145
158
  def explicit_dependencies
146
- dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
147
-
148
- wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
149
-
150
- (explicits + resolve_directories(wildcards)).uniq
159
+ source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
151
160
  end
152
161
  end
153
162
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class RubyTracker # :nodoc:
6
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
7
+
8
+ def self.call(name, template, view_paths = nil)
9
+ new(name, template, view_paths).dependencies
10
+ end
11
+
12
+ def dependencies
13
+ WildcardResolver.new(view_paths, render_dependencies + explicit_dependencies).resolve
14
+ end
15
+
16
+ def self.supports_view_paths? # :nodoc:
17
+ true
18
+ end
19
+
20
+ def initialize(name, template, view_paths = nil, parser_class: RenderParser::Default)
21
+ @name, @template, @view_paths = name, template, view_paths
22
+ @parser_class = parser_class
23
+ end
24
+
25
+ private
26
+ attr_reader :template, :name, :view_paths
27
+
28
+ def render_dependencies
29
+ return [] unless template.source.include?("render")
30
+
31
+ compiled_source = template.handler.call(template, template.source)
32
+
33
+ @parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call|
34
+ render_call.gsub(%r|/_|, "/")
35
+ end
36
+ end
37
+
38
+ def explicit_dependencies
39
+ template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class WildcardResolver # :nodoc:
6
+ def initialize(view_paths, dependencies)
7
+ @view_paths = view_paths
8
+
9
+ @wildcard_dependencies, @explicit_dependencies =
10
+ dependencies.partition { |dependency| dependency.end_with?("/*") }
11
+ end
12
+
13
+ def resolve
14
+ return explicit_dependencies.uniq if !view_paths || wildcard_dependencies.empty?
15
+
16
+ (explicit_dependencies + resolved_wildcard_dependencies).uniq
17
+ end
18
+
19
+ private
20
+ attr_reader :explicit_dependencies, :wildcard_dependencies, :view_paths
21
+
22
+ def resolved_wildcard_dependencies
23
+ # Remove trailing "/*"
24
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
25
+
26
+ view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
27
+ path.to_s if prefixes.include?(path.prefix)
28
+ }.sort
29
+ end
30
+ end
31
+ end
32
+ end
@@ -9,7 +9,8 @@ module ActionView
9
9
  extend ActiveSupport::Autoload
10
10
 
11
11
  autoload :ERBTracker
12
- autoload :RipperTracker
12
+ autoload :RubyTracker
13
+ autoload :WildcardResolver
13
14
 
14
15
  @trackers = Concurrent::Map.new
15
16
 
@@ -35,6 +36,11 @@ module ActionView
35
36
  @trackers.delete(handler)
36
37
  end
37
38
 
38
- register_tracker :erb, ERBTracker
39
+ case ActionView.render_tracker
40
+ when :ruby
41
+ register_tracker :erb, RubyTracker
42
+ else
43
+ register_tracker :erb, ERBTracker
44
+ end
39
45
  end
40
46
  end
@@ -107,8 +107,12 @@ module ActionView
107
107
  end.join("-")
108
108
  end
109
109
 
110
- def to_dep_map
111
- children.any? ? { name => children.map(&:to_dep_map) } : name
110
+ def to_dep_map(seen = Set.new.compare_by_identity)
111
+ if seen.add?(self)
112
+ children.any? ? { name => children.map { |c| c.to_dep_map(seen) } } : name
113
+ else # the tree has a cycle
114
+ name
115
+ end
112
116
  end
113
117
  end
114
118
 
@@ -7,10 +7,10 @@ module ActionView
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
10
+ MAJOR = 8
11
11
  MINOR = 1
12
- TINY = 5
13
- PRE = "2"
12
+ TINY = 2
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -26,6 +26,8 @@ module ActionView
26
26
  mattr_accessor :image_decoding
27
27
  mattr_accessor :preload_links_header
28
28
  mattr_accessor :apply_stylesheet_media_default
29
+ mattr_accessor :auto_include_nonce_for_scripts
30
+ mattr_accessor :auto_include_nonce_for_styles
29
31
 
30
32
  # Returns an HTML script tag for each of the +sources+ provided.
31
33
  #
@@ -68,6 +70,8 @@ module ActionView
68
70
  # attribute, which indicates to the browser that the script is meant to
69
71
  # be executed after the document has been parsed. Additionally, prevents
70
72
  # sending the Preload Links header.
73
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
74
+ # for the script. Defaults to +true+.
71
75
  #
72
76
  # Any other specified options will be treated as HTML attributes for the
73
77
  # +script+ tag.
@@ -113,11 +117,11 @@ module ActionView
113
117
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
114
118
  preload_links = []
115
119
  use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
116
- nopush = options["nopush"].nil? ? true : options.delete("nopush")
120
+ nopush = options["nopush"].nil? || options.delete("nopush")
117
121
  crossorigin = options.delete("crossorigin")
118
122
  crossorigin = "anonymous" if crossorigin == true
119
123
  integrity = options["integrity"]
120
- rel = options["type"] == "module" ? "modulepreload" : "preload"
124
+ rel = options["type"] == "module" || options["type"] == :module ? "modulepreload" : "preload"
121
125
 
122
126
  sources_tags = sources.uniq.map { |source|
123
127
  href = path_to_javascript(source, path_options)
@@ -125,6 +129,7 @@ module ActionView
125
129
  preload_link = "<#{href}>; rel=#{rel}; as=script"
126
130
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
127
131
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
132
+ preload_link += "; nonce=#{content_security_policy_nonce}" if options["nonce"] == true
128
133
  preload_link += "; nopush" if nopush
129
134
  preload_links << preload_link
130
135
  end
@@ -132,8 +137,10 @@ module ActionView
132
137
  "src" => href,
133
138
  "crossorigin" => crossorigin
134
139
  }.merge!(options)
135
- if tag_options["nonce"] == true
140
+ if tag_options["nonce"] == true || (!tag_options.key?("nonce") && auto_include_nonce_for_scripts)
136
141
  tag_options["nonce"] = content_security_policy_nonce
142
+ elsif tag_options["nonce"] == false
143
+ tag_options.delete("nonce")
137
144
  end
138
145
  content_tag("script", "", tag_options)
139
146
  }.join("\n").html_safe
@@ -166,6 +173,10 @@ module ActionView
166
173
  # that path.
167
174
  # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
168
175
  # when it is set to true.
176
+ # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
177
+ # you have Content Security Policy enabled.
178
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired
179
+ # for the stylesheet. Defaults to +true+.
169
180
  #
170
181
  # ==== Examples
171
182
  #
@@ -190,6 +201,9 @@ module ActionView
190
201
  # stylesheet_link_tag "random.styles", "/css/stylish"
191
202
  # # => <link href="/assets/random.styles" rel="stylesheet" />
192
203
  # # <link href="/css/stylish.css" rel="stylesheet" />
204
+ #
205
+ # stylesheet_link_tag "style", nonce: true
206
+ # # => <link href="/assets/style.css" rel="stylesheet" nonce="..." />
193
207
  def stylesheet_link_tag(*sources)
194
208
  options = sources.extract_options!.stringify_keys
195
209
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
@@ -197,7 +211,7 @@ module ActionView
197
211
  preload_links = []
198
212
  crossorigin = options.delete("crossorigin")
199
213
  crossorigin = "anonymous" if crossorigin == true
200
- nopush = options["nopush"].nil? ? true : options.delete("nopush")
214
+ nopush = options["nopush"].nil? || options.delete("nopush")
201
215
  integrity = options["integrity"]
202
216
 
203
217
  sources_tags = sources.uniq.map { |source|
@@ -206,6 +220,7 @@ module ActionView
206
220
  preload_link = "<#{href}>; rel=preload; as=style"
207
221
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
208
222
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
223
+ preload_link += "; nonce=#{content_security_policy_nonce}" if options["nonce"] == true
209
224
  preload_link += "; nopush" if nopush
210
225
  preload_links << preload_link
211
226
  end
@@ -214,6 +229,11 @@ module ActionView
214
229
  "crossorigin" => crossorigin,
215
230
  "href" => href
216
231
  }.merge!(options)
232
+ if tag_options["nonce"] == true || (!tag_options.key?("nonce") && auto_include_nonce_for_styles && respond_to?(:content_security_policy_nonce))
233
+ tag_options["nonce"] = content_security_policy_nonce
234
+ elsif tag_options["nonce"] == false
235
+ tag_options.delete("nonce")
236
+ end
217
237
 
218
238
  if apply_stylesheet_media_default && tag_options["media"].blank?
219
239
  tag_options["media"] = "screen"
@@ -348,21 +368,32 @@ module ActionView
348
368
  crossorigin = options.delete(:crossorigin)
349
369
  crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
350
370
  integrity = options[:integrity]
371
+ fetchpriority = options.delete(:fetchpriority)
351
372
  nopush = options.delete(:nopush) || false
352
- rel = mime_type == "module" ? "modulepreload" : "preload"
373
+ rel = mime_type == "module" || mime_type == :module ? "modulepreload" : "preload"
374
+ add_nonce = content_security_policy_nonce &&
375
+ respond_to?(:request) &&
376
+ request.content_security_policy_nonce_directives&.include?("#{as_type}-src")
377
+
378
+ if add_nonce
379
+ options[:nonce] = content_security_policy_nonce
380
+ end
353
381
 
354
- link_tag = tag.link(**{
382
+ link_tag = tag.link(
355
383
  rel: rel,
356
384
  href: href,
357
385
  as: as_type,
358
386
  type: mime_type,
359
- crossorigin: crossorigin
360
- }.merge!(options.symbolize_keys))
387
+ crossorigin: crossorigin,
388
+ fetchpriority: fetchpriority,
389
+ **options.symbolize_keys)
361
390
 
362
391
  preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
363
392
  preload_link += "; type=#{mime_type}" if mime_type
364
393
  preload_link += "; crossorigin=#{crossorigin}" if crossorigin
394
+ preload_link += "; fetchpriority=#{fetchpriority}" if fetchpriority
365
395
  preload_link += "; integrity=#{integrity}" if integrity
396
+ preload_link += "; nonce=#{content_security_policy_nonce}" if add_nonce
366
397
  preload_link += "; nopush" if nopush
367
398
 
368
399
  send_preload_links_header([preload_link])
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module ActionView
6
4
  module Helpers # :nodoc:
7
5
  # = Action View Atom Feed \Helpers
@@ -172,7 +170,7 @@ module ActionView
172
170
 
173
171
  # Creates an entry tag for a specific record and prefills the id using class and id.
174
172
  #
175
- # Options:
173
+ # ==== Options
176
174
  #
177
175
  # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
178
176
  # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
@@ -93,6 +93,14 @@ module ActionView
93
93
  # render partial: 'attachments/attachment', collection: group_of_attachments
94
94
  # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
95
95
  #
96
+ # One last type of dependency can be determined implicitly:
97
+ #
98
+ # render "maintenance_tasks/runs/info/#{run.status}"
99
+ #
100
+ # Because the value passed to render ends in interpolation, Action View
101
+ # will mark all partials within the "maintenance_tasks/runs/info" folder as
102
+ # dependencies.
103
+ #
96
104
  # === Explicit dependencies
97
105
  #
98
106
  # Sometimes you'll have template dependencies that can't be derived at all. This is typically
@@ -189,7 +197,7 @@ module ActionView
189
197
  CachingRegistry.caching?
190
198
  end
191
199
 
192
- # Raises +UncacheableFragmentError+ when called from within a +cache+ block.
200
+ # Raises UncacheableFragmentError when called from within a +cache+ block.
193
201
  #
194
202
  # Useful to denote helper methods that can't participate in fragment caching:
195
203
  #
@@ -198,7 +206,7 @@ module ActionView
198
206
  # "#{project.name} - #{Time.now}"
199
207
  # end
200
208
  #
201
- # # Which will then raise if used within a +cache+ block:
209
+ # # Which will then raise if used within a `cache` block:
202
210
  # <% cache project do %>
203
211
  # <%= project_name_with_time(project) %>
204
212
  # <% end %>
@@ -44,10 +44,10 @@ module ActionView
44
44
  #
45
45
  # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
46
46
  #
47
- def capture(*args, &block)
47
+ def capture(*, **, &block)
48
48
  value = nil
49
49
  @output_buffer ||= ActionView::OutputBuffer.new
50
- buffer = @output_buffer.capture { value = yield(*args) }
50
+ buffer = @output_buffer.capture { value = yield(*, **) }
51
51
 
52
52
  string = if @output_buffer.equal?(value)
53
53
  buffer
@@ -20,11 +20,15 @@ module ActionView
20
20
  def assign_controller(controller)
21
21
  if @_controller = controller
22
22
  @_request = controller.request if controller.respond_to?(:request)
23
- @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
23
+ if controller.respond_to?(:config)
24
+ @_config = controller.config.inheritable_copy
25
+ else
26
+ @_config = ActiveSupport::InheritableOptions.new
27
+ end
24
28
  @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
25
29
  else
26
30
  @_request ||= nil
27
- @_config ||= nil
31
+ @_config = ActiveSupport::InheritableOptions.new
28
32
  @_default_form_builder ||= nil
29
33
  end
30
34
  end
@@ -17,7 +17,7 @@ module ActionView
17
17
  # You don't need to use these tags for regular forms as they generate their own hidden fields.
18
18
  #
19
19
  # For Ajax requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
20
- # +X-CSRF-Token+ HTTP header. If you are using rails-ujs, this happens automatically.
20
+ # +X-CSRF-Token+ HTTP header.
21
21
  #
22
22
  def csrf_meta_tags
23
23
  if defined?(protect_against_forgery?) && protect_against_forgery?
@@ -136,8 +136,15 @@ module ActionView
136
136
  from_year += 1 if from_time.month >= 3
137
137
  to_year = to_time.year
138
138
  to_year -= 1 if to_time.month < 3
139
- leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) }
139
+
140
+ leap_years = if from_year > to_year
141
+ 0
142
+ else
143
+ fyear = from_year - 1
144
+ (to_year / 4 - to_year / 100 + to_year / 400) - (fyear / 4 - fyear / 100 + fyear / 400)
145
+ end
140
146
  minute_offset_for_leap_year = leap_years * 1440
147
+
141
148
  # Discount the leap year days when calculating year distance.
142
149
  # e.g. if there are 20 leap year days between 2 dates having the same day
143
150
  # and month then based on 365 days calculation
@@ -179,6 +186,23 @@ module ActionView
179
186
 
180
187
  alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
181
188
 
189
+ # Like <tt>time_ago_in_words</tt>, but adds a prefix/suffix depending on whether the time is in the past or future.
190
+ # You can use the <tt>scope</tt> option to customize the translation scope. All other options
191
+ # are forwarded to <tt>time_ago_in_words</tt>.
192
+ #
193
+ # relative_time_in_words(3.minutes.from_now) # => "in 3 minutes"
194
+ # relative_time_in_words(3.minutes.ago) # => "3 minutes ago"
195
+ # relative_time_in_words(10.seconds.ago, include_seconds: true) # => "less than 10 seconds ago"
196
+ #
197
+ # See also #time_ago_in_words
198
+ def relative_time_in_words(from_time, options = {})
199
+ now = Time.now
200
+ time = distance_of_time_in_words(from_time, now, options.except(:scope))
201
+ key = from_time > now ? :future : :past
202
+
203
+ I18n.t(key, time: time, scope: options.fetch(:scope, :'datetime.relative'), locale: options[:locale])
204
+ end
205
+
182
206
  # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
183
207
  # attribute (identified by +method+) on an object assigned to the template (identified by +object+).
184
208
  #
@@ -1228,7 +1252,7 @@ module ActionView
1228
1252
  class FormBuilder
1229
1253
  # Wraps ActionView::Helpers::DateHelper#date_select for form builders:
1230
1254
  #
1231
- # <%= form_for @person do |f| %>
1255
+ # <%= form_with model: @person do |f| %>
1232
1256
  # <%= f.date_select :birth_date %>
1233
1257
  # <%= f.submit %>
1234
1258
  # <% end %>
@@ -1240,7 +1264,7 @@ module ActionView
1240
1264
 
1241
1265
  # Wraps ActionView::Helpers::DateHelper#time_select for form builders:
1242
1266
  #
1243
- # <%= form_for @race do |f| %>
1267
+ # <%= form_with model: @race do |f| %>
1244
1268
  # <%= f.time_select :average_lap %>
1245
1269
  # <%= f.submit %>
1246
1270
  # <% end %>
@@ -1252,7 +1276,7 @@ module ActionView
1252
1276
 
1253
1277
  # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
1254
1278
  #
1255
- # <%= form_for @person do |f| %>
1279
+ # <%= form_with model: @person do |f| %>
1256
1280
  # <%= f.datetime_select :last_request_at %>
1257
1281
  # <%= f.submit %>
1258
1282
  # <% end %>