actionview 8.1.0.beta1 → 8.1.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 625c1e6b4e3cc45ecce5dafa0697614d9ac34d31bd582f53c46620b60ff323a1
4
- data.tar.gz: 5aec58970306776aa8ffcc3738a2e8b8ebb0b266777946341601b8bf38c6ff47
3
+ metadata.gz: a79b9e5f7d9952afc09e3ecefce995705b2564f9834ce16f2b214f6968a93b76
4
+ data.tar.gz: ed03955e9a24857f701633b431c1ba0693de07da4442f0de99f069d2678dfc40
5
5
  SHA512:
6
- metadata.gz: e4e7e64f11986f77cf38942336bb217309275e4548fc6082f306551b25dc2b1bb016d50d85cb84619976e026696eba83796ca763aff8d86ac5b1009333b404ce
7
- data.tar.gz: 397cf668c2a7dbd78e93ac4765c8adbbe6d245ea3c704b840297cae38b51779997be6caf62855b1ba3fb16e8a84180ff85fd6079ea8253809fe810521d5d8fac
6
+ metadata.gz: b16dfb9f53fd8cca55eeaebec829d8793adefdbb4a7ebc4f7f9153e446177ebf18bb981fddeec887d709249e054d66f96d300c6d7715402bf07ebc89119b7b15
7
+ data.tar.gz: 15c6567e19a148c35f97cdbe1dcee0ed8309fd342e05d54eac440857c3bf087af86a63c7d4ab2b458970593ea04d83bea85426f12c05a16ebab6b5dc31f139f0
data/CHANGELOG.md CHANGED
@@ -1,11 +1,53 @@
1
+ ## Rails 8.1.0.rc1 (October 15, 2025) ##
2
+
3
+ * The BEGIN template annotation/comment was previously printed on the same line as the following element. We now insert a newline inside the comment so it spans two lines without adding visible whitespace to the HTML output to enhance readability.
4
+
5
+ Before:
6
+ ```
7
+ <!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --><p>This is grand!</p>
8
+ ```
9
+
10
+ After:
11
+ ```
12
+ <!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb
13
+ --><p>This is grand!</p>
14
+ ```
15
+ *Emmanuel Hayford*
16
+
17
+ * Add structured events for Action View:
18
+ - `action_view.render_template`
19
+ - `action_view.render_partial`
20
+ - `action_view.render_layout`
21
+ - `action_view.render_collection`
22
+ - `action_view.render_start`
23
+
24
+ *Gannon McGibbon*
25
+
26
+ * Fix label with `for` option not getting prefixed by form `namespace` value
27
+
28
+ *Abeid Ahmed*, *Hartley McGuire*
29
+
30
+ * Add `fetchpriority` to Link headers to match HTML generated by `preload_link_tag`.
31
+
32
+ *Guillermo Iguaran*
33
+
1
34
  ## Rails 8.1.0.beta1 (September 04, 2025) ##
2
35
 
36
+ * Add CSP `nonce` to Link headers generated by `preload_link_tag`.
37
+
38
+ *Alexander Gitter*
39
+
3
40
  * Allow `current_page?` to match against specific HTTP method(s) with a `method:` option.
4
41
 
5
42
  *Ben Sheldon*
6
43
 
7
- * remove `autocomplete="off"` on hidden inputs generated by `form_tag`, `token_tag`, `method_tag`, and the hidden
8
- parameter fields included in `button_to` forms will omit the `autocomplete="off"` attribute.
44
+ * Remove `autocomplete="off"` on hidden inputs generated by the following
45
+ tags:
46
+
47
+ * `form_tag`, `token_tag`, `method_tag`
48
+
49
+ As well as the hidden parameter fields included in `button_to`,
50
+ `check_box`, `select` (with `multiple`) and `file_field` forms.
9
51
 
10
52
  *nkulway*
11
53
 
@@ -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"
@@ -10,7 +10,7 @@ module ActionView
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
12
  TINY = 0
13
- PRE = "beta1"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -121,7 +121,7 @@ module ActionView
121
121
  crossorigin = options.delete("crossorigin")
122
122
  crossorigin = "anonymous" if crossorigin == true
123
123
  integrity = options["integrity"]
124
- rel = options["type"] == "module" ? "modulepreload" : "preload"
124
+ rel = options["type"] == "module" || options["type"] == :module ? "modulepreload" : "preload"
125
125
 
126
126
  sources_tags = sources.uniq.map { |source|
127
127
  href = path_to_javascript(source, path_options)
@@ -368,8 +368,9 @@ module ActionView
368
368
  crossorigin = options.delete(:crossorigin)
369
369
  crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
370
370
  integrity = options[:integrity]
371
+ fetchpriority = options.delete(:fetchpriority)
371
372
  nopush = options.delete(:nopush) || false
372
- rel = mime_type == "module" ? "modulepreload" : "preload"
373
+ rel = mime_type == "module" || mime_type == :module ? "modulepreload" : "preload"
373
374
  add_nonce = content_security_policy_nonce &&
374
375
  respond_to?(:request) &&
375
376
  request.content_security_policy_nonce_directives&.include?("#{as_type}-src")
@@ -384,11 +385,13 @@ module ActionView
384
385
  as: as_type,
385
386
  type: mime_type,
386
387
  crossorigin: crossorigin,
388
+ fetchpriority: fetchpriority,
387
389
  **options.symbolize_keys)
388
390
 
389
391
  preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
390
392
  preload_link += "; type=#{mime_type}" if mime_type
391
393
  preload_link += "; crossorigin=#{crossorigin}" if crossorigin
394
+ preload_link += "; fetchpriority=#{fetchpriority}" if fetchpriority
392
395
  preload_link += "; integrity=#{integrity}" if integrity
393
396
  preload_link += "; nonce=#{content_security_policy_nonce}" if add_nonce
394
397
  preload_link += "; nopush" if nopush
@@ -80,30 +80,32 @@ module ActionView
80
80
  end
81
81
  end
82
82
 
83
- def add_default_name_and_id_for_value(tag_value, options)
83
+ def add_default_name_and_field_for_value(tag_value, options, field = "id")
84
84
  if tag_value.nil?
85
- add_default_name_and_id(options)
85
+ add_default_name_and_field(options, field)
86
86
  else
87
- specified_id = options["id"]
88
- add_default_name_and_id(options)
87
+ specified_field = options[field]
88
+ add_default_name_and_field(options, field)
89
89
 
90
- if specified_id.blank? && options["id"].present?
91
- options["id"] += "_#{sanitized_value(tag_value)}"
90
+ if specified_field.blank? && options[field].present?
91
+ options[field] += "_#{sanitized_value(tag_value)}"
92
92
  end
93
93
  end
94
94
  end
95
+ alias_method :add_default_name_and_id_for_value, :add_default_name_and_field_for_value
95
96
 
96
- def add_default_name_and_id(options)
97
+ def add_default_name_and_field(options, field = "id")
97
98
  index = name_and_id_index(options)
98
99
  options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
99
100
 
100
101
  if generate_ids?
101
- options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
102
+ options[field] = options.fetch(field) { tag_id(index, options.delete("namespace")) }
102
103
  if namespace = options.delete("namespace")
103
- options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
104
+ options[field] = options[field] ? "#{namespace}_#{options[field]}" : namespace
104
105
  end
105
106
  end
106
107
  end
108
+ alias_method :add_default_name_and_id, :add_default_name_and_field
107
109
 
108
110
  def tag_name(multiple = false, index = nil)
109
111
  @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
@@ -21,10 +21,10 @@ module ActionView
21
21
  options["checked"] = "checked" if input_checked?(options)
22
22
 
23
23
  if options["multiple"]
24
- add_default_name_and_id_for_value(@checked_value, options)
24
+ add_default_name_and_field_for_value(@checked_value, options)
25
25
  options.delete("multiple")
26
26
  else
27
- add_default_name_and_id(options)
27
+ add_default_name_and_field(options)
28
28
  end
29
29
 
30
30
  include_hidden = options.delete("include_hidden") { true }
@@ -57,7 +57,13 @@ module ActionView
57
57
  end
58
58
 
59
59
  def hidden_field_for_checkbox(options)
60
- @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe
60
+ if @unchecked_value
61
+ tag_options = options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)
62
+ tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
63
+ tag("input", tag_options)
64
+ else
65
+ "".html_safe
66
+ end
61
67
  end
62
68
  end
63
69
  end
@@ -7,7 +7,7 @@ module ActionView
7
7
  def render
8
8
  include_hidden = @options.delete(:include_hidden)
9
9
  options = @options.stringify_keys
10
- add_default_name_and_id(options)
10
+ add_default_name_and_field(options)
11
11
 
12
12
  if options["multiple"] && include_hidden
13
13
  hidden_field_for_multiple_file(options) + super
@@ -18,7 +18,9 @@ module ActionView
18
18
 
19
19
  private
20
20
  def hidden_field_for_multiple_file(options)
21
- tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off")
21
+ tag_options = { "name" => options["name"], "type" => "hidden", "value" => "" }
22
+ tag_options["autocomplete"] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
23
+ tag("input", tag_options)
22
24
  end
23
25
  end
24
26
  end
@@ -48,18 +48,11 @@ module ActionView
48
48
  def render(&block)
49
49
  options = @options.stringify_keys
50
50
  tag_value = options.delete("value")
51
- name_and_id = options.dup
52
51
 
53
- if name_and_id["for"]
54
- name_and_id["id"] = name_and_id["for"]
55
- else
56
- name_and_id.delete("id")
57
- end
58
-
59
- add_default_name_and_id_for_value(tag_value, name_and_id)
52
+ add_default_name_and_field_for_value(tag_value, options, "for")
60
53
  options.delete("index")
54
+ options.delete("name")
61
55
  options.delete("namespace")
62
- options["for"] = name_and_id["id"] unless options.key?("for")
63
56
 
64
57
  builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
65
58
 
@@ -71,7 +64,7 @@ module ActionView
71
64
  render_component(builder)
72
65
  end
73
66
 
74
- label_tag(name_and_id["id"], content, options)
67
+ label_tag(options["for"], content, options)
75
68
  end
76
69
 
77
70
  private
@@ -18,7 +18,7 @@ module ActionView
18
18
  options["type"] = "radio"
19
19
  options["value"] = @tag_value
20
20
  options["checked"] = "checked" if input_checked?(options)
21
- add_default_name_and_id_for_value(@tag_value, options)
21
+ add_default_name_and_field_for_value(@tag_value, options)
22
22
  tag("input", options)
23
23
  end
24
24
 
@@ -11,7 +11,7 @@ module ActionView
11
11
  html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
12
12
  end
13
13
 
14
- add_default_name_and_id(html_options)
14
+ add_default_name_and_field(html_options)
15
15
 
16
16
  if placeholder_required?(html_options)
17
17
  raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
@@ -22,7 +22,9 @@ module ActionView
22
22
  select = content_tag("select", add_options(option_tags, options, value), html_options)
23
23
 
24
24
  if html_options["multiple"] && options.fetch(:include_hidden, true)
25
- tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
25
+ tag_options = { disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "" }
26
+ tag_options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
27
+ tag("input", tag_options) + select
26
28
  else
27
29
  select
28
30
  end
@@ -10,7 +10,7 @@ module ActionView
10
10
 
11
11
  def render
12
12
  options = @options.stringify_keys
13
- add_default_name_and_id(options)
13
+ add_default_name_and_field(options)
14
14
 
15
15
  if size = options.delete("size")
16
16
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
@@ -13,7 +13,7 @@ module ActionView
13
13
  options["size"] = options["maxlength"] unless options.key?("size")
14
14
  options["type"] ||= field_type
15
15
  options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
16
- add_default_name_and_id(options)
16
+ add_default_name_and_field(options)
17
17
  tag("input", options)
18
18
  end
19
19
 
@@ -3,10 +3,7 @@
3
3
  require "active_support/log_subscriber"
4
4
 
5
5
  module ActionView
6
- # = Action View Log Subscriber
7
- #
8
- # Provides functionality so that \Rails can output logs from Action View.
9
- class LogSubscriber < ActiveSupport::LogSubscriber
6
+ class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
10
7
  VIEWS_PATTERN = /^app\/views\//
11
8
 
12
9
  def initialize
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActionView
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ VIEWS_PATTERN = /^app\/views\//
8
+
9
+ def initialize
10
+ @root = nil
11
+ super
12
+ end
13
+
14
+ def render_template(event)
15
+ emit_debug_event("action_view.render_template",
16
+ identifier: from_rails_root(event.payload[:identifier]),
17
+ layout: from_rails_root(event.payload[:layout]),
18
+ duration_ms: event.duration.round(2),
19
+ gc_ms: event.gc_time.round(2),
20
+ )
21
+ end
22
+ debug_only :render_template
23
+
24
+ def render_partial(event)
25
+ emit_debug_event("action_view.render_partial",
26
+ identifier: from_rails_root(event.payload[:identifier]),
27
+ layout: from_rails_root(event.payload[:layout]),
28
+ duration_ms: event.duration.round(2),
29
+ gc_ms: event.gc_time.round(2),
30
+ cache_hit: event.payload[:cache_hit],
31
+ )
32
+ end
33
+ debug_only :render_partial
34
+
35
+ def render_layout(event)
36
+ emit_event("action_view.render_layout",
37
+ identifier: from_rails_root(event.payload[:identifier]),
38
+ duration_ms: event.duration.round(2),
39
+ gc_ms: event.gc_time.round(2),
40
+ )
41
+ end
42
+ debug_only :render_layout
43
+
44
+ def render_collection(event)
45
+ emit_debug_event("action_view.render_collection",
46
+ identifier: from_rails_root(event.payload[:identifier] || "templates"),
47
+ layout: from_rails_root(event.payload[:layout]),
48
+ duration_ms: event.duration.round(2),
49
+ gc_ms: event.gc_time.round(2),
50
+ cache_hits: event.payload[:cache_hits],
51
+ count: event.payload[:count],
52
+ )
53
+ end
54
+ debug_only :render_collection
55
+
56
+ module Utils # :nodoc:
57
+ private
58
+ def from_rails_root(string)
59
+ return unless string
60
+
61
+ string = string.sub("#{rails_root}/", "")
62
+ string.sub!(VIEWS_PATTERN, "")
63
+ string
64
+ end
65
+
66
+ def rails_root # :doc:
67
+ @root ||= Rails.try(:root)
68
+ end
69
+ end
70
+
71
+ include Utils
72
+
73
+ class Start # :nodoc:
74
+ include Utils
75
+
76
+ def start(name, id, payload)
77
+ ActiveSupport.event_reporter.debug("action_view.render_start",
78
+ is_layout: name == "render_layout.action_view",
79
+ identifier: from_rails_root(payload[:identifier]),
80
+ layout: from_rails_root(payload[:layout]),
81
+ )
82
+ end
83
+
84
+ def finish(name, id, payload)
85
+ end
86
+ end
87
+
88
+ def self.attach_to(*)
89
+ ActiveSupport::Notifications.subscribe("render_template.action_view", Start.new)
90
+ ActiveSupport::Notifications.subscribe("render_layout.action_view", Start.new)
91
+
92
+ super
93
+ end
94
+ end
95
+ end
96
+
97
+ ActionView::StructuredEventSubscriber.attach_to :action_view
@@ -86,7 +86,7 @@ module ActionView
86
86
  }
87
87
 
88
88
  if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
89
- options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
89
+ options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier}\n-->';"
90
90
  options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer"
91
91
  end
92
92
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionview
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.0.beta1
4
+ version: 8.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.1.0.beta1
18
+ version: 8.1.0.rc1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 8.1.0.beta1
25
+ version: 8.1.0.rc1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: builder
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -85,28 +85,28 @@ dependencies:
85
85
  requirements:
86
86
  - - '='
87
87
  - !ruby/object:Gem::Version
88
- version: 8.1.0.beta1
88
+ version: 8.1.0.rc1
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - '='
94
94
  - !ruby/object:Gem::Version
95
- version: 8.1.0.beta1
95
+ version: 8.1.0.rc1
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: activemodel
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - '='
101
101
  - !ruby/object:Gem::Version
102
- version: 8.1.0.beta1
102
+ version: 8.1.0.rc1
103
103
  type: :development
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - '='
108
108
  - !ruby/object:Gem::Version
109
- version: 8.1.0.beta1
109
+ version: 8.1.0.rc1
110
110
  description: Simple, battle-tested conventions and helpers for building web pages.
111
111
  email: david@loudthinking.com
112
112
  executables: []
@@ -216,6 +216,7 @@ files:
216
216
  - lib/action_view/renderer/template_renderer.rb
217
217
  - lib/action_view/rendering.rb
218
218
  - lib/action_view/routing_url_for.rb
219
+ - lib/action_view/structured_event_subscriber.rb
219
220
  - lib/action_view/tasks/cache_digests.rake
220
221
  - lib/action_view/template.rb
221
222
  - lib/action_view/template/error.rb
@@ -246,10 +247,10 @@ licenses:
246
247
  - MIT
247
248
  metadata:
248
249
  bug_tracker_uri: https://github.com/rails/rails/issues
249
- changelog_uri: https://github.com/rails/rails/blob/v8.1.0.beta1/actionview/CHANGELOG.md
250
- documentation_uri: https://api.rubyonrails.org/v8.1.0.beta1/
250
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.0.rc1/actionview/CHANGELOG.md
251
+ documentation_uri: https://api.rubyonrails.org/v8.1.0.rc1/
251
252
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
252
- source_code_uri: https://github.com/rails/rails/tree/v8.1.0.beta1/actionview
253
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.0.rc1/actionview
253
254
  rubygems_mfa_required: 'true'
254
255
  rdoc_options: []
255
256
  require_paths: