tag:github.com,2008:https://github.com/rails/rails/releasesRelease notes from rails2026-01-08T20:16:56Ztag:github.com,2008:Repository/8514/v8.1.22026-01-08T20:17:17Z8.1.2<h2>Active Support</h2>
<ul>
<li>
<p>Make <code>delegate</code> and <code>delegate_missing_to</code> work in BasicObject subclasses.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Fix Inflectors when using a locale that fallbacks to <code>:en</code>.</p>
<p><em>Said Kaldybaev</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::TimeWithZone#as_json</code> to consistently return UTF-8 strings.</p>
<p>Previously the returned string would sometime be encoded in US-ASCII, which in<br>
some cases may be problematic.</p>
<p>Now the method consistently always return UTF-8 strings.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>TimeWithZone#xmlschema</code> when wrapping a <code>DateTime</code> instance in local time.</p>
<p>Previously it would return an invalid time.</p>
<p><em>Dmytro Rymar</em></p>
</li>
<li>
<p>Implement LocalCache strategy on <code>ActiveSupport::Cache::MemoryStore</code>. The memory store<br>
needs to respond to the same interface as other cache stores (e.g. <code>ActiveSupport::NullStore</code>).</p>
<p><em>Mikey Gough</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::Inflector.humanize</code> with international characters.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveSupport::Inflector.humanize("áÉÍÓÚ") # => "Áéíóú"
ActiveSupport::Inflector.humanize("аБВГДЕ") # => "Абвгде""><pre><span class="pl-v">ActiveSupport</span>::<span class="pl-v">Inflector</span><span class="pl-kos">.</span><span class="pl-en">humanize</span><span class="pl-kos">(</span><span class="pl-s">"áÉÍÓÚ"</span><span class="pl-kos">)</span> <span class="pl-c"># => "Áéíóú"</span>
<span class="pl-v">ActiveSupport</span>::<span class="pl-v">Inflector</span><span class="pl-kos">.</span><span class="pl-en">humanize</span><span class="pl-kos">(</span><span class="pl-s">"аБВГДЕ"</span><span class="pl-kos">)</span> <span class="pl-c"># => "Абвгде"</span></pre></div>
<p><em>Jose Luis Duran</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix counting cached queries in <code>ActiveRecord::RuntimeRegistry</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix merging relations with arel equality predicates with null relations.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix SQLite3 schema dump for non-autoincrement integer primary keys.</p>
<p>Previously, <code>schema.rb</code> should incorrectly restore that table with an auto incrementing<br>
primary key.</p>
<p><em>Chris Hasiński</em></p>
</li>
<li>
<p>Fix PostgreSQL <code>schema_search_path</code> not being reapplied after <code>reset!</code> or <code>reconnect!</code>.</p>
<p>The <code>schema_search_path</code> configured in <code>database.yml</code> is now correctly<br>
reapplied instead of falling back to PostgreSQL defaults.</p>
<p><em>Tobias Egli</em></p>
</li>
<li>
<p>Restore the ability of enum to be foats.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="enum :rating, { low: 0.0, medium: 0.5, high: 1.0 },"><pre><span class="pl-en">enum</span> <span class="pl-pds">:rating</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-pds">low</span>: <span class="pl-c1">0.0</span><span class="pl-kos">,</span> <span class="pl-pds">medium</span>: <span class="pl-c1">0.5</span><span class="pl-kos">,</span> <span class="pl-pds">high</span>: <span class="pl-c1">1.0</span> <span class="pl-kos">}</span><span class="pl-kos">,</span><span class="pl-pds"></span></pre></div>
<p>In Rails 8.1.0, enum values are eagerly validated, and floats weren't expected.</p>
<p><em>Said Kaldybaev</em></p>
</li>
<li>
<p>Ensure batched preloaded associations accounts for klass when grouping to avoid issues with STI.</p>
<p><em>zzak</em>, <em>Stjepan Hadjic</em></p>
</li>
<li>
<p>Fix <code>ActiveRecord::SoleRecordExceeded#record</code> to return the relation.</p>
<p>This was the case until Rails 7.2, but starting from 8.0 it<br>
started mistakenly returning the model class.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Improve PostgreSQLAdapter resilience to Timeout.timeout.</p>
<p>Better handle asynchronous exceptions being thrown inside<br>
the <code>reconnect!</code> method.</p>
<p>This may fixes some deep errors such as:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="undefined method `key?' for nil:NilClass (NoMethodError)
if !type_map.key?(oid)"><pre class="notranslate"><code>undefined method `key?' for nil:NilClass (NoMethodError)
if !type_map.key?(oid)
</code></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix structured events for Active Record was not being emitted.</p>
<p><em>Yuji Yaginuma</em></p>
</li>
<li>
<p>Fix <code>eager_load</code> when loading <code>has_many</code> assocations with composite primary keys.</p>
<p>This would result in some records being loaded multiple times.</p>
<p><em>Martin-Alexander</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Fix <code>file_field</code> to join mime types with a comma when provided as Array</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="file_field(:article, :image, accept: ['image/png', 'image/gif', 'image/jpeg'])"><pre><span class="pl-en">file_field</span><span class="pl-kos">(</span><span class="pl-pds">:article</span><span class="pl-kos">,</span> <span class="pl-pds">:image</span><span class="pl-kos">,</span> <span class="pl-pds">accept</span>: <span class="pl-kos">[</span><span class="pl-s">'image/png'</span><span class="pl-kos">,</span> <span class="pl-s">'image/gif'</span><span class="pl-kos">,</span> <span class="pl-s">'image/jpeg'</span><span class="pl-kos">]</span><span class="pl-kos">)</span></pre></div>
<p>Now behaves likes:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="file_field(:article, :image, accept: 'image/png,image/gif,image/jpeg')"><pre class="notranslate"><code>file_field(:article, :image, accept: 'image/png,image/gif,image/jpeg')
</code></pre></div>
<p><em>Bogdan Gusiev</em></p>
</li>
<li>
<p>Fix strict locals parsing to handle multiline definitions.</p>
<p><em>Said Kaldybaev</em></p>
</li>
<li>
<p>Fix <code>content_security_policy_nonce</code> error in mailers when using <code>content_security_policy_nonce_auto</code> setting.</p>
<p>The <code>content_security_policy_nonce helper</code> is provided by <code>ActionController::ContentSecurityPolicy</code>, and it relies on <code>request.content_security_policy_nonc</code>e. Mailers lack both the module and the request object.</p>
<p><em>Jarrett Lusso</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Add <code>config.action_controller.live_streaming_excluded_keys</code> to control execution state sharing in ActionController::Live.</p>
<p>When using ActionController::Live, actions are executed in a separate thread that shares<br>
state from the parent thread. This new configuration allows applications to opt-out specific<br>
state keys that should not be shared.</p>
<p>This is useful when streaming inside a <code>connected_to</code> block, where you may want<br>
the streaming thread to use its own database connection context.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# config/application.rb
config.action_controller.live_streaming_excluded_keys = [:active_record_connected_to_stack]"><pre><span class="pl-c"># config/application.rb</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">action_controller</span><span class="pl-kos">.</span><span class="pl-en">live_streaming_excluded_keys</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-pds">:active_record_connected_to_stack</span><span class="pl-kos">]</span></pre></div>
<p>By default, all keys are shared.</p>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Fix <code>IpSpoofAttackError</code> message to include <code>Forwarded</code> header content.</p>
<p>Without it, the error message may be misleading.</p>
<p><em>zzak</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Fix <code>ActiveJob.perform_all_later</code> to respect <code>job_class.enqueue_after_transaction_commit</code>.</p>
<p>Previously, <code>perform_all_later</code> would enqueue all jobs immediately, even if<br>
they had <code>enqueue_after_transaction_commit = true</code>. Now it correctly defers<br>
jobs with this setting until after transaction commits, matching the behavior<br>
of <code>perform_later</code>.</p>
<p><em>OuYangJinTing</em></p>
</li>
<li>
<p>Fix using custom serializers with <code>ActiveJob::Arguments.serialize</code> when<br>
<code>ActiveJob::Base</code> hasn't been loaded.</p>
<p><em>Hartley McGuire</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Restore ADC when signing URLs with IAM for GCS</p>
<p>ADC was previously used for automatic authorization when signing URLs with IAM.<br>
Now it is again, but the auth client is memoized so that new credentials are only<br>
requested when the current ones expire. Other auth methods can now be used<br>
instead by setting the authorization on <code>ActiveStorage::Service::GCSService#iam_client</code>.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveStorage::Blob.service.iam_client.authorization = Google::Auth::ImpersonatedServiceAccountCredentials.new(options)"><pre><span class="pl-v">ActiveStorage</span>::<span class="pl-v">Blob</span><span class="pl-kos">.</span><span class="pl-en">service</span><span class="pl-kos">.</span><span class="pl-en">iam_client</span><span class="pl-kos">.</span><span class="pl-en">authorization</span> <span class="pl-c1">=</span> <span class="pl-v">Google</span>::<span class="pl-v">Auth</span>::<span class="pl-v">ImpersonatedServiceAccountCredentials</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-en">options</span><span class="pl-kos">)</span></pre></div>
<p>This is safer than setting <code>Google::Apis::RequestOptions.default.authorization</code><br>
because it only applies to Active Storage and does not affect other Google API<br>
clients.</p>
<p><em>Justin Malčić</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Skip all system test files on app generation.</p>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Fix <code>db:system:change</code> to correctly update Dockerfile base packages.</p>
<p><em>Josiah Smith</em></p>
</li>
<li>
<p>Fix devcontainer volume mount when app name differs from folder name.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Fixed the <code>rails notes</code> command to properly extract notes in CSS files.</p>
<p><em>David White</em></p>
</li>
<li>
<p>Fixed the default Dockerfile to properly include the <code>vendor/</code> directory during <code>bundle install</code>.</p>
<p><em>Zhong Sheng</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v8.1.12025-10-28T23:46:29Z8.1.1<h2>Active Support</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Respect <code>remove_hidden_field_autocomplete</code> config in form builder <code>hidden_field</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Allow methods starting with underscore to be action methods.</p>
<p>Disallowing methods starting with an underscore from being action methods<br>
was an unintended side effect of the performance optimization in<br>
<a class="commit-link" data-hovercard-type="commit" data-hovercard-url="https://github.com/rails/rails/commit/207a254cedef2c381c2898bac960b91ce14ab3a7/hovercard" href="https://github.com/rails/rails/commit/207a254cedef2c381c2898bac960b91ce14ab3a7"><tt>207a254</tt></a>.</p>
<p>Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="3544256968" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/55985" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/55985/hovercard" href="https://github.com/rails/rails/issues/55985">#55985</a>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Only index new serializers.</p>
<p><em>Jesse Sharps</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Do not assume and force SSL in production by default when using Kamal, to allow for out of the box Kamal deployments.</p>
<p>It is still recommended to assume and force SSL in production as soon as you can.</p>
<p><em>Jerome Dalbert</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v8.0.42025-10-28T23:43:23Z8.0.4<h2>Active Support</h2>
<ul>
<li>
<p>Fix <code>Enumerable#sole</code> to return the full tuple instead of just the first element of the tuple.</p>
<p><em>Olivier Bellone</em></p>
</li>
<li>
<p>Fix parallel tests hanging when worker processes die abruptly.</p>
<p>Previously, if a worker process was killed (e.g., OOM killed, <code>kill -9</code>) during parallel<br>
test execution, the test suite would hang forever waiting for the dead worker.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix <code>NameError</code> when <code>class_attribute</code> is defined on instance singleton classes.</p>
<p>Previously, calling <code>class_attribute</code> on an instance's singleton class would raise<br>
a <code>NameError</code> when accessing the attribute through the instance.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="object = MyClass.new
object.singleton_class.class_attribute :foo, default: "bar"
object.foo # previously raised NameError, now returns "bar""><pre><span class="pl-s1">object</span> <span class="pl-c1">=</span> <span class="pl-v">MyClass</span><span class="pl-kos">.</span><span class="pl-en">new</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">singleton_class</span><span class="pl-kos">.</span><span class="pl-en">class_attribute</span> <span class="pl-pds">:foo</span><span class="pl-kos">,</span> <span class="pl-pds">default</span>: <span class="pl-s">"bar"</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">foo</span> <span class="pl-c"># previously raised NameError, now returns "bar"</span></pre></div>
<p><em>Joshua Young</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix SQLite3 data loss during table alterations with CASCADE foreign keys.</p>
<p>When altering a table in SQLite3 that is referenced by child tables with<br>
<code>ON DELETE CASCADE</code> foreign keys, ActiveRecord would silently delete all<br>
data from the child tables. This occurred because SQLite requires table<br>
recreation for schema changes, and during this process the original table<br>
is temporarily dropped, triggering CASCADE deletes on child tables.</p>
<p>The root cause was incorrect ordering of operations. The original code<br>
wrapped <code>disable_referential_integrity</code> inside a transaction, but<br>
<code>PRAGMA foreign_keys</code> cannot be modified inside a transaction in SQLite -<br>
attempting to do so simply has no effect. This meant foreign keys remained<br>
enabled during table recreation, causing CASCADE deletes to fire.</p>
<p>The fix reverses the order to follow the official SQLite 12-step ALTER TABLE<br>
procedure: <code>disable_referential_integrity</code> now wraps the transaction instead<br>
of being wrapped by it. This ensures foreign keys are properly disabled<br>
before the transaction starts and re-enabled after it commits, preventing<br>
CASCADE deletes while maintaining data integrity through atomic transactions.</p>
<p><em>Ruy Rocha</em></p>
</li>
<li>
<p>Add support for bound SQL literals in CTEs.</p>
<p><em>Nicolas Bachschmidt</em></p>
</li>
<li>
<p>Fix <code>belongs_to</code> associations not to clear the entire composite primary key.</p>
<p>When clearing a <code>belongs_to</code> association that references a model with composite primary key,<br>
only the optional part of the key should be cleared.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Fix invalid records being autosaved when distantly associated records are marked for deletion.</p>
<p><em>Ian Terrell</em>, <em>axlekb AB</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Restore <code>add_default_name_and_id</code> method.</p>
<p><em>Hartley McGuire</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Submit test requests using <code>as: :html</code> with <code>Content-Type: x-www-form-urlencoded</code></p>
<p><em>Sean Doyle</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v7.2.32025-10-28T23:24:18Z7.2.3<h2>Active Support</h2>
<ul>
<li>
<p>Fix <code>Enumerable#sole</code> to return the full tuple instead of just the first element of the tuple.</p>
<p><em>Olivier Bellone</em></p>
</li>
<li>
<p>Fix parallel tests hanging when worker processes die abruptly.</p>
<p>Previously, if a worker process was killed (e.g., OOM killed, <code>kill -9</code>) during parallel<br>
test execution, the test suite would hang forever waiting for the dead worker.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p><code>ActiveSupport::FileUpdateChecker</code> does not depend on <code>Time.now</code> to prevent unnecessary reloads with time travel test helpers</p>
<p><em>Jan Grodowski</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::BroadcastLogger</code> from executing a block argument for each logger (tagged, info, etc.).</p>
<p><em>Jared Armstrong</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::HashWithIndifferentAccess#transform_keys!</code> removing defaults.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::HashWithIndifferentAccess#tranform_keys!</code> to handle collisions.</p>
<p>If the transformation would result in a key equal to another not yet transformed one,<br>
it would result in keys being lost.</p>
<p>Before:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=">> {a: 1, b: 2}.with_indifferent_access.transform_keys!(&:succ)
=> {"c" => 1}"><pre>>> <span class="pl-kos">{</span><span class="pl-pds">a</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-pds">b</span>: <span class="pl-c1">2</span><span class="pl-kos">}</span><span class="pl-kos">.</span><span class="pl-en">with_indifferent_access</span><span class="pl-kos">.</span><span class="pl-en">transform_keys!</span><span class="pl-kos">(</span>&<span class="pl-pds">:succ</span><span class="pl-kos">)</span>
<span class="pl-c1">=></span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=></span> <span class="pl-c1">1</span><span class="pl-kos">}</span></pre></div>
<p>After:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=">> {a: 1, b: 2}.with_indifferent_access.transform_keys!(&:succ)
=> {"c" => 1, "d" => 2}"><pre>>> <span class="pl-kos">{</span><span class="pl-pds">a</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-pds">b</span>: <span class="pl-c1">2</span><span class="pl-kos">}</span><span class="pl-kos">.</span><span class="pl-en">with_indifferent_access</span><span class="pl-kos">.</span><span class="pl-en">transform_keys!</span><span class="pl-kos">(</span>&<span class="pl-pds">:succ</span><span class="pl-kos">)</span>
<span class="pl-c1">=></span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=></span> <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-s">"d"</span> <span class="pl-c1">=></span> <span class="pl-c1">2</span><span class="pl-kos">}</span></pre></div>
<p><em>Jason T Johnson</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::Cache::MemCacheStore#read_multi</code> to handle network errors.</p>
<p>This method specifically wasn't handling network errors like other codepaths.</p>
<p><em>Alessandro Dal Grande</em></p>
</li>
<li>
<p>Fix Active Support Cache <code>fetch_multi</code> when local store is active.</p>
<p><code>fetch_multi</code> now properly yield to the provided block for missing entries<br>
that have been recorded as such in the local store.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix execution wrapping to report all exceptions, including <code>Exception</code>.</p>
<p>If a more serious error like <code>SystemStackError</code> or <code>NoMemoryError</code> happens,<br>
the error reporter should be able to report these kinds of exceptions.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Fix <code>RedisCacheStore</code> and <code>MemCacheStore</code> to also handle connection pool related errors.</p>
<p>These errors are rescued and reported to <code>Rails.error</code>.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::Cache#read_multi</code> to respect version expiry when using local cache.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::MessageVerifier</code> and <code>ActiveSupport::MessageEncryptor</code> configuration of <code>on_rotation</code> callback.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="verifier.rotate(old_secret).on_rotation { ... }"><pre><span class="pl-en">verifier</span><span class="pl-kos">.</span><span class="pl-en">rotate</span><span class="pl-kos">(</span><span class="pl-en">old_secret</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">on_rotation</span> <span class="pl-kos">{</span> ... <span class="pl-kos">}</span></pre></div>
<p>Now both work as documented.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::MessageVerifier</code> to always be able to verify both URL-safe and URL-unsafe payloads.</p>
<p>This is to allow transitioning seemlessly from either configuration without immediately invalidating<br>
all previously generated signed messages.</p>
<p><em>Jean Boussier</em>, <em>Florent Beaurain</em>, <em>Ali Sepehri</em></p>
</li>
<li>
<p>Fix <code>cache.fetch</code> to honor the provided expiry when <code>:race_condition_ttl</code> is used.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cache.fetch("key", expires_in: 1.hour, race_condition_ttl: 5.second) do
"something"
end"><pre><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">fetch</span><span class="pl-kos">(</span><span class="pl-s">"key"</span><span class="pl-kos">,</span> <span class="pl-pds">expires_in</span>: <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">hour</span><span class="pl-kos">,</span> <span class="pl-pds">race_condition_ttl</span>: <span class="pl-c1">5</span><span class="pl-kos">.</span><span class="pl-en">second</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-s">"something"</span>
<span class="pl-k">end</span></pre></div>
<p>In the above example, the final cache entry would have a 10 seconds TTL instead<br>
of the requested 1 hour.</p>
<p><em>Dhia</em></p>
</li>
<li>
<p>Better handle procs with splat arguments in <code>set_callback</code>.</p>
<p><em>Radamés Roriz</em></p>
</li>
<li>
<p>Fix <code>String#mb_chars</code> to not mutate the receiver.</p>
<p>Previously it would call <code>force_encoding</code> on the receiver,<br>
now it dups the receiver first.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Improve <code>ErrorSubscriber</code> to also mark error causes as reported.</p>
<p>This avoid some cases of errors being reported twice, notably in views because of how<br>
errors are wrapped in <code>ActionView::Template::Error</code>.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>Module#module_parent_name</code> to return the correct name after the module has been named.</p>
<p>When called on an anonymous module, the return value wouldn't change after the module was given a name<br>
later by being assigned to a constant.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="mod = Module.new
mod.module_parent_name # => "Object"
MyModule::Something = mod
mod.module_parent_name # => "MyModule""><pre><span class="pl-s1">mod</span> <span class="pl-c1">=</span> <span class="pl-v">Module</span><span class="pl-kos">.</span><span class="pl-en">new</span>
<span class="pl-s1">mod</span><span class="pl-kos">.</span><span class="pl-en">module_parent_name</span> <span class="pl-c"># => "Object"</span>
<span class="pl-v">MyModule</span>::<span class="pl-v">Something</span> <span class="pl-c1">=</span> <span class="pl-s1">mod</span>
<span class="pl-s1">mod</span><span class="pl-kos">.</span><span class="pl-en">module_parent_name</span> <span class="pl-c"># => "MyModule"</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a bug in <code>ERB::Util.tokenize</code> that causes incorrect tokenization when ERB tags are preceeded by multibyte characters.</p>
<p><em>Martin Emde</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>
<p>Fix <code>has_secure_password</code> to perform confirmation validation of the password even when blank.</p>
<p>The validation was incorrectly skipped when the password only contained whitespace characters.</p>
<p><em>Fabio Sangiovanni</em></p>
</li>
<li>
<p>Handle missing attributes for <code>ActiveModel::Translation#human_attribute_name</code>.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Fix <code>ActiveModel::AttributeAssignment#assign_attributes</code> to accept objects without <code>each</code>.</p>
<p><em>Kouhei Yanagita</em></p>
</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix SQLite3 data loss during table alterations with CASCADE foreign keys.</p>
<p>When altering a table in SQLite3 that is referenced by child tables with<br>
<code>ON DELETE CASCADE</code> foreign keys, ActiveRecord would silently delete all<br>
data from the child tables. This occurred because SQLite requires table<br>
recreation for schema changes, and during this process the original table<br>
is temporarily dropped, triggering CASCADE deletes on child tables.</p>
<p>The root cause was incorrect ordering of operations. The original code<br>
wrapped <code>disable_referential_integrity</code> inside a transaction, but<br>
<code>PRAGMA foreign_keys</code> cannot be modified inside a transaction in SQLite -<br>
attempting to do so simply has no effect. This meant foreign keys remained<br>
enabled during table recreation, causing CASCADE deletes to fire.</p>
<p>The fix reverses the order to follow the official SQLite 12-step ALTER TABLE<br>
procedure: <code>disable_referential_integrity</code> now wraps the transaction instead<br>
of being wrapped by it. This ensures foreign keys are properly disabled<br>
before the transaction starts and re-enabled after it commits, preventing<br>
CASCADE deletes while maintaining data integrity through atomic transactions.</p>
<p><em>Ruy Rocha</em></p>
</li>
<li>
<p>Fix <code>belongs_to</code> associations not to clear the entire composite primary key.</p>
<p>When clearing a <code>belongs_to</code> association that references a model with composite primary key,<br>
only the optional part of the key should be cleared.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Fix invalid records being autosaved when distantly associated records are marked for deletion.</p>
<p><em>Ian Terrell</em>, <em>axlekb AB</em></p>
</li>
<li>
<p>Prevent persisting invalid record.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Fix count with group by qualified name on loaded relation.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Fix <code>sum</code> with qualified name on loaded relation.</p>
<p><em>Chris Gunther</em></p>
</li>
<li>
<p>Fix prepared statements on mysql2 adapter.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix query cache for pinned connections in multi threaded transactional tests.</p>
<p>When a pinned connection is used across separate threads, they now use a separate cache store<br>
for each thread.</p>
<p>This improve accuracy of system tests, and any test using multiple threads.</p>
<p><em>Heinrich Lee Yu</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Don't add <code>id_value</code> attribute alias when attribute/column with that name already exists.</p>
<p><em>Rob Lewis</em></p>
</li>
<li>
<p>Fix false positive change detection involving STI and polymorhic has one relationships.</p>
<p>Polymorphic <code>has_one</code> relationships would always be considered changed when defined in a STI child<br>
class, causing nedless extra autosaves.</p>
<p><em>David Fritsch</em></p>
</li>
<li>
<p>Fix stale associaton detection for polymophic <code>belong_to</code>.</p>
<p><em>Florent Beaurain</em>, <em>Thomas Crambert</em></p>
</li>
<li>
<p>Fix removal of PostgreSQL version comments in <code>structure.sql</code> for latest PostgreSQL versions which include <code>\restrict</code>.</p>
<p><em>Brendan Weibrecht</em></p>
</li>
<li>
<p>Fix <code>#merge</code> with <code>#or</code> or <code>#and</code> and a mixture of attributes and SQL strings resulting in an incorrect query.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="base = Comment.joins(:post).where(user_id: 1).where("recent = 1")
puts base.merge(base.where(draft: true).or(Post.where(archived: true))).to_sql"><pre><span class="pl-s1">base</span> <span class="pl-c1">=</span> <span class="pl-v">Comment</span><span class="pl-kos">.</span><span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-pds">:post</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">user_id</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-s">"recent = 1"</span><span class="pl-kos">)</span>
<span class="pl-en">puts</span> <span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">merge</span><span class="pl-kos">(</span><span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">draft</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">or</span><span class="pl-kos">(</span><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">archived</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to_sql</span></pre></div>
<p>Before:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p>After:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE "comments"."user_id" = 1
AND (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix inline <code>has_and_belongs_to_many</code> fixtures for tables with composite primary keys.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>annotate</code> comments to propagate to <code>update_all</code>/<code>delete_all</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix checking whether an unpersisted record is <code>include?</code>d in a strictly<br>
loaded <code>has_and_belongs_to_many</code> association.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix inline has_and_belongs_to_many fixtures for tables with composite primary keys.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p><code>create_or_find_by</code> will now correctly rollback a transaction.</p>
<p>When using <code>create_or_find_by</code>, raising a ActiveRecord::Rollback error<br>
in a <code>after_save</code> callback had no effect, the transaction was committed<br>
and a record created.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Gracefully handle <code>Timeout.timeout</code> firing during connection configuration.</p>
<p>Use of <code>Timeout.timeout</code> could result in improperly initialized database connection.</p>
<p>This could lead to a partially configured connection being used, resulting in various exceptions,<br>
the most common being with the PostgreSQLAdapter raising <code>undefined method 'key?' for nil</code><br>
or <code>TypeError: wrong argument type nil (expected PG::TypeMap)</code>.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>The SQLite3 adapter quotes non-finite Numeric values like "Infinity" and "NaN".</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Handle libpq returning a database version of 0 on no/bad connection in <code>PostgreSQLAdapter</code>.</p>
<p>Before, this version would be cached and an error would be raised during connection configuration when<br>
comparing it with the minimum required version for the adapter. This meant that the connection could<br>
never be successfully configured on subsequent reconnection attempts.</p>
<p>Now, this is treated as a connection failure consistent with libpq, raising a <code>ActiveRecord::ConnectionFailed</code><br>
and ensuring the version isn't cached, which allows the version to be retrieved on the next connection attempt.</p>
<p><em>Joshua Young</em>, <em>Rian McGuire</em></p>
</li>
<li>
<p>Fix error handling during connection configuration.</p>
<p>Active Record wasn't properly handling errors during the connection configuration phase.<br>
This could lead to a partially configured connection being used, resulting in various exceptions,<br>
the most common being with the PostgreSQLAdapter raising <code>undefined method </code>key?' for nil<code>or</code>TypeError: wrong argument type nil (expected PG::TypeMap)`.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a case where a non-retryable query could be marked retryable.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Handle circular references when autosaving associations.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Prevent persisting invalid record.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Fix support for PostgreSQL enum types with commas in their name.</p>
<p><em>Arthur Hess</em></p>
</li>
<li>
<p>Fix inserts on MySQL with no RETURNING support for a table with multiple auto populated columns.</p>
<p><em>Nikita Vasilevsky</em></p>
</li>
<li>
<p>Fix joining on a scoped association with string joins and bind parameters.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class Instructor < ActiveRecord::Base
has_many :instructor_roles, -> { active }
end
class InstructorRole < ActiveRecord::Base
scope :active, -> {
joins("JOIN students ON instructor_roles.student_id = students.id")
.where(students { status: 1 })
}
end
Instructor.joins(:instructor_roles).first"><pre><span class="pl-k">class</span> <span class="pl-v">Instructor</span> < <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span>
<span class="pl-en">has_many</span> <span class="pl-pds">:instructor_roles</span><span class="pl-kos">,</span> <span class="pl-c1">-></span> <span class="pl-kos">{</span> <span class="pl-en">active</span> <span class="pl-kos">}</span>
<span class="pl-k">end</span>
<span class="pl-k">class</span> <span class="pl-v">InstructorRole</span> < <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span>
<span class="pl-en">scope</span> <span class="pl-pds">:active</span><span class="pl-kos">,</span> <span class="pl-c1">-></span> <span class="pl-kos">{</span>
<span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-s">"JOIN students ON instructor_roles.student_id = students.id"</span><span class="pl-kos">)</span>
<span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-en">students</span> <span class="pl-kos">{</span> <span class="pl-pds">status</span>: <span class="pl-c1">1</span> <span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-k">end</span>
<span class="pl-v">Instructor</span><span class="pl-kos">.</span><span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-pds">:instructor_roles</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">first</span></pre></div>
<p>The above example would result in <code>ActiveRecord::StatementInvalid</code> because the<br>
<code>active</code> scope bind parameters would be lost.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a potential race condition with system tests and transactional fixtures.</p>
<p><em>Sjoerd Lagarde</em></p>
</li>
<li>
<p>Fix count with group by qualified name on loaded relation.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Fix sum with qualified name on loaded relation.</p>
<p><em>Chris Gunther</em></p>
</li>
<li>
<p>Fix autosave associations to no longer validated unmodified associated records.</p>
<p>Active Record was incorrectly performing validation on associated record that<br>
weren't created nor modified as part of the transaction:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Post.create!(author: User.find(1)) # Fail if user is invalid"><pre><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">create!</span><span class="pl-kos">(</span><span class="pl-pds">author</span>: <span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">find</span><span class="pl-kos">(</span><span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-c"># Fail if user is invalid</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Remember when a database connection has recently been verified (for<br>
two seconds, by default), to avoid repeated reverifications during a<br>
single request.</p>
<p>This should recreate a similar rate of verification as in Rails 7.1,<br>
where connections are leased for the duration of a request, and thus<br>
only verified once.</p>
<p><em>Matthew Draper</em></p>
</li>
<li>
<p>Fix prepared statements on mysql2 adapter.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a race condition in <code>ActiveRecord::Base#method_missing</code> when lazily defining attributes.</p>
<p>If multiple thread were concurrently triggering attribute definition on the same model,<br>
it could result in a <code>NoMethodError</code> being raised.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix MySQL default functions getting dropped when changing a column's nullability.</p>
<p><em>Bastian Bartmann</em></p>
</li>
<li>
<p>Fix <code>add_unique_constraint</code>/<code>add_check_constraint</code>/<code>/</code>add_foreign_key` to be revertible when<br>
given invalid options.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix asynchronous destroying of polymorphic <code>belongs_to</code> associations.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>NOT VALID constraints should not dump in <code>create_table</code>.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Fix finding by nil composite primary key association.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix parsing of SQLite foreign key names when they contain non-ASCII characters</p>
<p><em>Zacharias Knudsen</em></p>
</li>
<li>
<p>Fix parsing of MySQL 8.0.16+ CHECK constraints when they contain new lines.</p>
<p><em>Steve Hill</em></p>
</li>
<li>
<p>Ensure normalized attribute queries use <code>IS NULL</code> consistently for <code>nil</code> and normalized <code>nil</code> values.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Restore back the ability to pass only database name for <code>DATABASE_URL</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>order</code> with using association name as an alias.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Improve invalid argument error for with.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Deduplicate <code>with</code> CTE expressions.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Fix <code>javascript_include_tag</code> <code>type</code> option to accept either strings and symbols.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="javascript_include_tag "application", type: :module
javascript_include_tag "application", type: "module""><pre><span class="pl-en">javascript_include_tag</span> <span class="pl-s">"application"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:module</span>
<span class="pl-en">javascript_include_tag</span> <span class="pl-s">"application"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-s">"module"</span></pre></div>
<p>Previously, only the string value was recoginized.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>excerpt</code> helper with non-whitespace separator.</p>
<p><em>Jonathan Hefner</em></p>
</li>
<li>
<p>Respect <code>html_options[:form]</code> when <code>collection_checkboxes</code> generates the<br>
hidden <code><input></code>.</p>
<p><em>Riccardo Odone</em></p>
</li>
<li>
<p>Layouts have access to local variables passed to <code>render</code>.</p>
<p>This fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="287923807" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/31680" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/31680/hovercard" href="https://github.com/rails/rails/issues/31680">#31680</a> which was a regression in Rails 5.1.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Argument errors related to strict locals in templates now raise an<br>
<code>ActionView::StrictLocalsError</code>, and all other argument errors are reraised as-is.</p>
<p>Previously, any <code>ArgumentError</code> raised during template rendering was swallowed during strict<br>
local error handling, so that an <code>ArgumentError</code> unrelated to strict locals (e.g., a helper<br>
method invoked with incorrect arguments) would be replaced by a similar <code>ArgumentError</code> with an<br>
unrelated backtrace, making it difficult to debug templates.</p>
<p>Now, any <code>ArgumentError</code> unrelated to strict locals is reraised, preserving the original<br>
backtrace for developers.</p>
<p>Also note that <code>ActionView::StrictLocalsError</code> is a subclass of <code>ArgumentError</code>, so any existing<br>
code that rescues <code>ArgumentError</code> will continue to work.</p>
<p>Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2378084863" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/52227" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/52227/hovercard" href="https://github.com/rails/rails/issues/52227">#52227</a>.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Fix stack overflow error in dependency tracker when dealing with circular dependencies</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a crash in ERB template error highlighting when the error occurs on a<br>
line in the compiled template that is past the end of the source template.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Improve reliability of ERB template error highlighting.<br>
Fix infinite loops and crashes in highlighting and<br>
improve tolerance for alternate ERB handlers.</p>
<p><em>Martin Emde</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Submit test requests using <code>as: :html</code> with <code>Content-Type: x-www-form-urlencoded</code></p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Address <code>rack 3.2</code> deprecations warnings.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="warning: Status code :unprocessable_entity is deprecated and will be removed in a future version of Rack.
Please use :unprocessable_content instead."><pre class="notranslate"><code>warning: Status code :unprocessable_entity is deprecated and will be removed in a future version of Rack.
Please use :unprocessable_content instead.
</code></pre></div>
<p>Rails API will transparently convert one into the other for the forseable future.</p>
<p><em>Earlopain</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Always return empty body for HEAD requests in <code>PublicExceptions</code> and<br>
<code>DebugExceptions</code>.</p>
<p>This is required by <code>Rack::Lint</code> (per RFC9110).</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix <code>url_for</code> to handle <code>:path_params</code> gracefully when it's not a <code>Hash</code>.</p>
<p>Prevents various security scanners from causing exceptions.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Fix <code>ActionDispatch::Executor</code> to unwrap exceptions like other error reporting middlewares.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix NoMethodError when a non-string CSRF token is passed through headers.</p>
<p><em>Ryan Heneise</em></p>
</li>
<li>
<p>Fix invalid response when rescuing <code>ActionController::Redirecting::UnsafeRedirectError</code> in a controller.</p>
<p><em>Alex Ghiculescu</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Include the actual Active Job locale when serializing rather than I18n locale.</p>
<p><em>Adrien S</em></p>
</li>
<li>
<p>Avoid crashing in Active Job logger when logging enqueueing errors</p>
<p><code>ActiveJob.perform_all_later</code> could fail with a <code>TypeError</code> when all<br>
provided jobs failed to be enqueueed.</p>
<p><em>Efstathios Stivaros</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>
<p>Fixed compatibility with <code>redis</code> gem <code>5.4.1</code></p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fixed a possible race condition in <code>stream_from</code>.</p>
<p><em>OuYangJinTing</em></p>
</li>
<li>
<p>Ensure the Postgresql adapter always use a dedicated connection even during system tests.</p>
<p>Fix an issue with the Action Cable Postgresql adapter causing deadlock or various weird<br>
pg client error during system tests.</p>
<p><em>Jean Boussier</em></p>
</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Fix <code>config.active_storage.touch_attachment_records</code> to work with eager loading.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>A Blob will no longer autosave associated Attachment.</p>
<p>This fixes an issue where a record with an attachment would have<br>
its dirty attributes reset, preventing your <code>after commit</code> callbacks<br>
on that record to behave as expected.</p>
<p>Note that this change doesn't require any changes on your application<br>
and is supposed to be internal. Active Storage Attachment will continue<br>
to be autosaved (through a different relation).</p>
<p><em>Edouard-chin</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Use <code>secret_key_base</code> from ENV or credentials when present locally.</p>
<p>When ENV["SECRET_KEY_BASE"] or<br>
<code>Rails.application.credentials.secret_key_base</code> is set for test or<br>
development, it is used for the <code>Rails.config.secret_key_base</code>,<br>
instead of generating a <code>tmp/local_secret.txt</code> file.</p>
<p><em>Petrik de Heus</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v7.1.62025-10-28T23:19:19Z7.1.6<h2>Active Support</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Gracefully handle <code>Timeout.timeout</code> firing during connection configuration.</p>
<p>Use of <code>Timeout.timeout</code> could result in improperly initialized database connection.</p>
<p>This could lead to a partially configured connection being used, resulting in various exceptions,<br>
the most common being with the PostgreSQLAdapter raising <code>undefined method </code>key?' for nil<code>or</code>TypeError: wrong argument type nil (expected PG::TypeMap)`.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix error handling during connection configuration.</p>
<p>Active Record wasn't properly handling errors during the connection configuration phase.<br>
This could lead to a partially configured connection being used, resulting in various exceptions,<br>
the most common being with the PostgreSQLAdapter raising <code>undefined method </code>key?' for nil<code>or</code>TypeError: wrong argument type nil (expected PG::TypeMap)`.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix prepared statements on mysql2 adapter.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix a race condition in <code>ActiveRecord::Base#method_missing</code> when lazily defining attributes.</p>
<p>If multiple thread were concurrently triggering attribute definition on the same model,<br>
it could result in a <code>NoMethodError</code> being raised.</p>
<p><em>Jean Boussier</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>
<p>Fixed compatibility with <code>redis</code> gem <code>5.4.1</code></p>
<p><em>Jean Boussier</em></p>
</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v7.0.102025-10-28T23:12:55Z7.0.10<p>See <a href="https://github.com/rails/rails/releases/tag/v7.0.9">https://github.com/rails/rails/releases/tag/v7.0.9</a> for information about this release.</p>rafaelfrancatag:github.com,2008:Repository/8514/v7.0.92025-10-28T23:02:29Z7.0.9<h2>Active Support</h2>
<ul>
<li>
<p>Fix <code>ActiveSupport::Notifications.publish_event</code> to preserve units.</p>
<p>This solves the incorrect reporting of time spent running Active Record<br>
asynchronous queries (by a factor <code>1000</code>).</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix ActiveSupport::Deprecation to handle blaming generated code</p>
<p><em>Jean Boussier</em>, <em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>#to_fs(:human_size)</code> to correctly work with negative numbers.</p>
<p><em>Earlopain</em></p>
</li>
<li>
<p>Add <code>bigdecimal</code> as Active Support dependency that is a bundled gem candidate for Ruby 3.4.</p>
<p><code>bigdecimal</code> 3.1.4 or higher version will be installed.<br>
Ruby 2.7 and 3.0 users who want <code>bigdecimal</code> version 2.0.0 or 3.0.0 behavior as a default gem,<br>
pin the <code>bigdecimal</code> version in your application Gemfile.</p>
<p><em>Koichi ITO</em></p>
</li>
<li>
<p>Ensure <code>{down,up}case_first</code> returns non-frozen string.</p>
<p><em>Jonathan Hefner</em></p>
</li>
<li>
<p>Add <code>drb</code>, <code>mutex_m</code> and <code>base64</code> that are bundled gem candidates for Ruby 3.4</p>
<p><em>Yasuo Honda</em></p>
</li>
<li>
<p>Fix <code>delete_matched</code> for file cache store to work with keys longer than the<br>
max filename size.</p>
<p><em>fatkodima</em> and <em>Jonathan Hefner</em></p>
</li>
<li>
<p>Fix MemoryStore to prevent race conditions when incrementing or decrementing.</p>
<p><em>Pierre Jambet</em></p>
</li>
<li>
<p>Fix MemoryStore to preserve entries TTL when incrementing or decrementing</p>
<p>This is to be more consistent with how MemCachedStore and RedisCacheStore behaves.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>NumberHelper: handle objects responding to_d.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>NumberHelper: handle very large numbers.</p>
<p><em>Alex Ghiculescu</em>, <em>fatkodima</em></p>
</li>
<li>
<p>Fix Range#overlaps? not taking empty ranges into account on Ruby < 3.3</p>
<p><em>Nobuyoshi Nakada</em>, <em>Shouichi Kamiya</em>, <em>Hartley McGuire</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix an issue that could cause database connection leaks</p>
<p>If Active Record successfully connected to the database, but then failed<br>
to read the server informations, the connection would be leaked until the<br>
Ruby garbage collector triggers.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix single quote escapes on default generated MySQL columns</p>
<p>MySQL 5.7.5+ supports generated columns, which can be used to create a column that is computed from an expression.</p>
<p>Previously, the schema dump would output a string with double escapes for generated columns with single quotes in the default expression.</p>
<p>This would result in issues when importing the schema on a fresh instance of a MySQL database.</p>
<p>Now, the string will not be escaped and will be valid Ruby upon importing of the schema.</p>
<p><em>Yash Kapadia</em></p>
</li>
<li>
<p>Fix <code>Relation#transaction</code> to not apply a default scope</p>
<p>The method was incorrectly setting a default scope around its block:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Post.where(published: true).transaction do
Post.count # SELECT COUNT(*) FROM posts WHERE published = FALSE;
end"><pre><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">published</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">transaction</span> <span class="pl-k">do</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">count</span> <span class="pl-c"># SELECT COUNT(*) FROM posts WHERE published = FALSE;</span>
<span class="pl-k">end</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix renaming primary key index when renaming a table with a UUID primary key<br>
in PostgreSQL.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>where(field: values)</code> queries when <code>field</code> is a serialized attribute<br>
(for example, when <code>field</code> uses <code>ActiveRecord::Base.serialize</code> or is a JSON<br>
column).</p>
<p><em>João Alves</em></p>
</li>
<li>
<p>Don't mark Float::INFINITY as changed when reassigning it</p>
<p>When saving a record with a float infinite value, it shouldn't mark as changed</p>
<p><em>Maicol Bentancor</em></p>
</li>
<li>
<p><code>ActiveRecord::Base.table_name</code> now returns <code>nil</code> instead of raising<br>
"undefined method <code>abstract_class?</code> for Object:Class".</p>
<p><em>a5-stable</em></p>
</li>
<li>
<p>Fix upserting for custom <code>:on_duplicate</code> and <code>:unique_by</code> consisting of all<br>
inserts keys.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>NoMethodError</code> when casting a PostgreSQL <code>money</code> value that uses a<br>
comma as its radix point and has no leading currency symbol. For example,<br>
when casting <code>"3,50"</code>.</p>
<p><em>Andreas Reischuck</em> and <em>Jonathan Hefner</em></p>
</li>
<li>
<p>Fix duplicate quoting for check constraint expressions in schema dump when using MySQL</p>
<p>A check constraint with an expression, that already contains quotes, lead to an invalid schema<br>
dump with the mysql2 adapter.</p>
<p>Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="915248782" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/42424" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/42424/hovercard" href="https://github.com/rails/rails/issues/42424">#42424</a>.</p>
<p><em>Felix Tscheulin</em></p>
</li>
<li>
<p>Fix MySQL expression index dumping with escaped quotes.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix uniqueness validation on association not using overridden primary key.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Fix the <code>number_to_human_size</code> view helper to correctly work with negative numbers.</p>
<p><em>Earlopain</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Fix <code>ActionDispatch::Executor</code> middleware to report errors handled by <code>ActionDispatch::ShowExceptions</code>.</p>
<p>In the default production environment, <code>ShowExceptions</code> rescue uncaught errors<br>
and returns a response. Because if this the executor wouldn't report production<br>
errors with the default Rails configuration.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Add <code>racc</code> as a dependency since it will become a bundled gem in Ruby 3.4.0</p>
<p><em>Hartley McGuire</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Preserve the serialized timezone when deserializing <code>ActiveSupport::TimeWithZone</code> arguments.</p>
<p><em>Joshua Young</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Fix <code>ActiveStorage::Representations::ProxyController</code> not returning the proper<br>
preview image variant for previewable files.</p>
<p><em>Chedli Bourguiba</em></p>
</li>
<li>
<p>Make untracked variants obey <code>config.active_storage.content_types_to_serve_as_binary</code><br>
and <code>config.active_storage.content_types_allowed_inline</code>.</p>
<p><em>Chedli Bourguiba</em> and <em>Jonathan Hefner</em></p>
</li>
<li>
<p>Fix direct upload forms when submit button contains nested elements.</p>
<p><em>Marc Köhlbrugge</em></p>
</li>
<li>
<p>Prevent <code>ActiveRecord::StrictLoadingViolationError</code> when strict loading is<br>
enabled and the variant of an Active Storage preview has already been<br>
processed (for example, by calling <code>ActiveStorage::Preview#url</code>).</p>
<p><em>Jonathan Hefner</em></p>
</li>
<li>
<p>Fix variants not included when eager loading multiple records containing a single attachment</p>
<p>When using the <code>with_attached_#{name}</code> scope for a <code>has_one_attached</code> relation,<br>
attachment variants were not eagerly loaded.</p>
<p><em>Russell Porter</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Railties</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v8.1.02025-10-22T00:35:05Z8.1.0<h2>Active Support</h2>
<ul>
<li>
<p>Remove deprecated passing a Time object to <code>Time#since</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>Benchmark.ms</code> method. It is now defined in the <code>benchmark</code> gem.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated addition for <code>Time</code> instances with <code>ActiveSupport::TimeWithZone</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated support for <code>to_time</code> to preserve the system local time. It will now always preserve the receiver<br>
timezone.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Deprecate <code>config.active_support.to_time_preserves_timezone</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Standardize event name formatting in <code>assert_event_reported</code> error messages.</p>
<p>The event name in failure messages now uses <code>.inspect</code> (e.g., <code>name: "user.created"</code>)<br>
to match <code>assert_events_reported</code> and provide type clarity between strings and symbols.<br>
This only affects tests that assert on the failure message format itself.</p>
<p><em>George Ma</em></p>
</li>
<li>
<p>Fix <code>Enumerable#sole</code> to return the full tuple instead of just the first element of the tuple.</p>
<p><em>Olivier Bellone</em></p>
</li>
<li>
<p>Fix parallel tests hanging when worker processes die abruptly.</p>
<p>Previously, if a worker process was killed (e.g., OOM killed, <code>kill -9</code>) during parallel<br>
test execution, the test suite would hang forever waiting for the dead worker.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Add <code>config.active_support.escape_js_separators_in_json</code>.</p>
<p>Introduce a new framework default to skip escaping LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029) in JSON.</p>
<p>Historically these characters were not valid inside JavaScript literal strings but that changed in ECMAScript 2019.<br>
As such it's no longer a concern in modern browsers: <a href="https://caniuse.com/mdn-javascript_builtins_json_json_superset" rel="nofollow">https://caniuse.com/mdn-javascript_builtins_json_json_superset</a>.</p>
<p><em>Étienne Barrié</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>NameError</code> when <code>class_attribute</code> is defined on instance singleton classes.</p>
<p>Previously, calling <code>class_attribute</code> on an instance's singleton class would raise<br>
a <code>NameError</code> when accessing the attribute through the instance.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="object = MyClass.new
object.singleton_class.class_attribute :foo, default: "bar"
object.foo # previously raised NameError, now returns "bar""><pre><span class="pl-s1">object</span> <span class="pl-c1">=</span> <span class="pl-v">MyClass</span><span class="pl-kos">.</span><span class="pl-en">new</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">singleton_class</span><span class="pl-kos">.</span><span class="pl-en">class_attribute</span> <span class="pl-pds">:foo</span><span class="pl-kos">,</span> <span class="pl-pds">default</span>: <span class="pl-s">"bar"</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">foo</span> <span class="pl-c"># previously raised NameError, now returns "bar"</span></pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Introduce <code>ActiveSupport::Testing::EventReporterAssertions#with_debug_event_reporting</code><br>
to enable event reporter debug mode in tests.</p>
<p>The previous way to enable debug mode is by using <code>#with_debug</code> on the<br>
event reporter itself, which is too verbose. This new helper will help<br>
clear up any confusion on how to test debug events.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add <code>ActiveSupport::StructuredEventSubscriber</code> for consuming notifications and<br>
emitting structured event logs. Events may be emitted with the <code>#emit_event</code><br>
or <code>#emit_debug_event</code> methods.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class MyStructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber
def notification(event)
emit_event("my.notification", data: 1)
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">MyStructuredEventSubscriber</span> < <span class="pl-v">ActiveSupport</span>::<span class="pl-v">StructuredEventSubscriber</span>
<span class="pl-k">def</span> <span class="pl-en">notification</span><span class="pl-kos">(</span><span class="pl-s1">event</span><span class="pl-kos">)</span>
<span class="pl-en">emit_event</span><span class="pl-kos">(</span><span class="pl-s">"my.notification"</span><span class="pl-kos">,</span> <span class="pl-pds">data</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p><code>ActiveSupport::FileUpdateChecker</code> does not depend on <code>Time.now</code> to prevent unecessary reloads with time travel test helpers</p>
<p><em>Jan Grodowski</em></p>
</li>
<li>
<p>Add <code>ActiveSupport::Cache::Store#namespace=</code> and <code>#namespace</code>.</p>
<p>Can be used as an alternative to <code>Store#clear</code> in some situations such as parallel<br>
testing.</p>
<p><em>Nick Schwaderer</em></p>
</li>
<li>
<p>Create <code>parallel_worker_id</code> helper for running parallel tests. This allows users to<br>
know which worker they are currently running in.</p>
<p><em>Nick Schwaderer</em></p>
</li>
<li>
<p>Make the cache of <code>ActiveSupport::Cache::Strategy::LocalCache::Middleware</code> updatable.</p>
<p>If the cache client at <code>Rails.cache</code> of a booted application changes, the corresponding<br>
mounted middleware needs to update in order for request-local caches to be setup properly.<br>
Otherwise, redundant cache operations will erroneously hit the datastore.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add <code>assert_events_reported</code> test helper for <code>ActiveSupport::EventReporter</code>.</p>
<p>This new assertion allows testing multiple events in a single block, regardless of order:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="assert_events_reported([
{ name: "user.created", payload: { id: 123 } },
{ name: "email.sent", payload: { to: "[email protected]" } }
]) do
create_user_and_send_welcome_email
end"><pre><span class="pl-en">assert_events_reported</span><span class="pl-kos">(</span><span class="pl-kos">[</span>
<span class="pl-kos">{</span> <span class="pl-pds">name</span>: <span class="pl-s">"user.created"</span><span class="pl-kos">,</span> <span class="pl-pds">payload</span>: <span class="pl-kos">{</span> <span class="pl-pds">id</span>: <span class="pl-c1">123</span> <span class="pl-kos">}</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">{</span> <span class="pl-pds">name</span>: <span class="pl-s">"email.sent"</span><span class="pl-kos">,</span> <span class="pl-pds">payload</span>: <span class="pl-kos">{</span> <span class="pl-pds">to</span>: <span class="pl-s">"[email protected]"</span> <span class="pl-kos">}</span> <span class="pl-kos">}</span>
<span class="pl-kos">]</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-en">create_user_and_send_welcome_email</span>
<span class="pl-k">end</span></pre></div>
<p><em>George Ma</em></p>
</li>
<li>
<p>Add <code>ActiveSupport::TimeZone#standard_name</code> method.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="zone = ActiveSupport::TimeZone['Hawaii']
# Old way
ActiveSupport::TimeZone::MAPPING[zone.name]
# New way
zone.standard_name # => 'Pacific/Honolulu'"><pre><span class="pl-s1">zone</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">TimeZone</span><span class="pl-kos">[</span><span class="pl-s">'Hawaii'</span><span class="pl-kos">]</span>
<span class="pl-c"># Old way</span>
<span class="pl-v">ActiveSupport</span>::<span class="pl-v">TimeZone</span>::<span class="pl-c1">MAPPING</span><span class="pl-kos">[</span><span class="pl-s1">zone</span><span class="pl-kos">.</span><span class="pl-en">name</span><span class="pl-kos">]</span>
<span class="pl-c"># New way</span>
<span class="pl-s1">zone</span><span class="pl-kos">.</span><span class="pl-en">standard_name</span> <span class="pl-c"># => 'Pacific/Honolulu'</span></pre></div>
<p><em>Bogdan Gusiev</em></p>
</li>
<li>
<p>Add Structured Event Reporter, accessible via <code>Rails.event</code>.</p>
<p>The Event Reporter provides a unified interface for producing structured events in Rails<br>
applications:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Rails.event.notify("user.signup", user_id: 123, email: "[email protected]")"><pre><span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">event</span><span class="pl-kos">.</span><span class="pl-en">notify</span><span class="pl-kos">(</span><span class="pl-s">"user.signup"</span><span class="pl-kos">,</span> <span class="pl-pds">user_id</span>: <span class="pl-c1">123</span><span class="pl-kos">,</span> <span class="pl-pds">email</span>: <span class="pl-s">"[email protected]"</span><span class="pl-kos">)</span></pre></div>
<p>It supports adding tags to events:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Rails.event.tagged("graphql") do
# Event includes tags: { graphql: true }
Rails.event.notify("user.signup", user_id: 123, email: "[email protected]")
end"><pre><span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">event</span><span class="pl-kos">.</span><span class="pl-en">tagged</span><span class="pl-kos">(</span><span class="pl-s">"graphql"</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-c"># Event includes tags: { graphql: true }</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">event</span><span class="pl-kos">.</span><span class="pl-en">notify</span><span class="pl-kos">(</span><span class="pl-s">"user.signup"</span><span class="pl-kos">,</span> <span class="pl-pds">user_id</span>: <span class="pl-c1">123</span><span class="pl-kos">,</span> <span class="pl-pds">email</span>: <span class="pl-s">"[email protected]"</span><span class="pl-kos">)</span>
<span class="pl-k">end</span></pre></div>
<p>As well as context:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# All events will contain context: {request_id: "abc123", shop_id: 456}
Rails.event.set_context(request_id: "abc123", shop_id: 456)"><pre><span class="pl-c"># All events will contain context: {request_id: "abc123", shop_id: 456}</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">event</span><span class="pl-kos">.</span><span class="pl-en">set_context</span><span class="pl-kos">(</span><span class="pl-pds">request_id</span>: <span class="pl-s">"abc123"</span><span class="pl-kos">,</span> <span class="pl-pds">shop_id</span>: <span class="pl-c1">456</span><span class="pl-kos">)</span></pre></div>
<p>Events are emitted to subscribers. Applications register subscribers to<br>
control how events are serialized and emitted. Subscribers must implement<br>
an <code>#emit</code> method, which receives the event hash:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class LogSubscriber
def emit(event)
payload = event[:payload].map { |key, value| "#{key}=#{value}" }.join(" ")
source_location = event[:source_location]
log = "[#{event[:name]}] #{payload} at #{source_location[:filepath]}:#{source_location[:lineno]}"
Rails.logger.info(log)
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">LogSubscriber</span>
<span class="pl-k">def</span> <span class="pl-en">emit</span><span class="pl-kos">(</span><span class="pl-s1">event</span><span class="pl-kos">)</span>
<span class="pl-s1">payload</span> <span class="pl-c1">=</span> <span class="pl-s1">event</span><span class="pl-kos">[</span><span class="pl-pds">:payload</span><span class="pl-kos">]</span><span class="pl-kos">.</span><span class="pl-en">map</span> <span class="pl-kos">{</span> |<span class="pl-s1">key</span><span class="pl-kos">,</span> <span class="pl-s1">value</span>| <span class="pl-s">"<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">key</span><span class="pl-kos">}</span></span>=<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">value</span><span class="pl-kos">}</span></span>"</span> <span class="pl-kos">}</span><span class="pl-kos">.</span><span class="pl-en">join</span><span class="pl-kos">(</span><span class="pl-s">" "</span><span class="pl-kos">)</span>
<span class="pl-s1">source_location</span> <span class="pl-c1">=</span> <span class="pl-s1">event</span><span class="pl-kos">[</span><span class="pl-pds">:source_location</span><span class="pl-kos">]</span>
<span class="pl-s1">log</span> <span class="pl-c1">=</span> <span class="pl-s">"[<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">event</span><span class="pl-kos">[</span><span class="pl-pds">:name</span><span class="pl-kos">]</span><span class="pl-kos">}</span></span>] <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">payload</span><span class="pl-kos">}</span></span> at <span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">source_location</span><span class="pl-kos">[</span><span class="pl-pds">:filepath</span><span class="pl-kos">]</span><span class="pl-kos">}</span></span>:<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-s1">source_location</span><span class="pl-kos">[</span><span class="pl-pds">:lineno</span><span class="pl-kos">]</span><span class="pl-kos">}</span></span>"</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">logger</span><span class="pl-kos">.</span><span class="pl-en">info</span><span class="pl-kos">(</span><span class="pl-s1">log</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p>Make <code>ActiveSupport::Logger</code> <code>#freeze</code>-friendly.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Make <code>ActiveSupport::Gzip.compress</code> deterministic based on input.</p>
<p><code>ActiveSupport::Gzip.compress</code> used to include a timestamp in the output,<br>
causing consecutive calls with the same input data to have different output<br>
if called during different seconds. It now always sets the timestamp to <code>0</code><br>
so that the output is identical for any given input.</p>
<p><em>Rob Brackett</em></p>
</li>
<li>
<p>Given an array of <code>Thread::Backtrace::Location</code> objects, the new method<br>
<code>ActiveSupport::BacktraceCleaner#clean_locations</code> returns an array with the<br>
clean ones:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="clean_locations = backtrace_cleaner.clean_locations(caller_locations)"><pre><span class="pl-s1">clean_locations</span> <span class="pl-c1">=</span> <span class="pl-en">backtrace_cleaner</span><span class="pl-kos">.</span><span class="pl-s1">clean_locations</span><span class="pl-kos">(</span><span class="pl-en">caller_locations</span><span class="pl-kos">)</span></pre></div>
<p>Filters and silencers receive strings as usual. However, the <code>path</code><br>
attributes of the locations in the returned array are the original,<br>
unfiltered ones, since locations are immutable.</p>
<p><em>Xavier Noria</em></p>
</li>
<li>
<p>Improve <code>CurrentAttributes</code> and <code>ExecutionContext</code> state managment in test cases.</p>
<p>Previously these two global state would be entirely cleared out whenever calling<br>
into code that is wrapped by the Rails executor, typically Action Controller or<br>
Active Job helpers:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="test "#index works" do
CurrentUser.id = 42
get :index
CurrentUser.id == nil
end"><pre><span class="pl-en">test</span> <span class="pl-s">"#index works"</span> <span class="pl-k">do</span>
<span class="pl-v">CurrentUser</span><span class="pl-kos">.</span><span class="pl-en">id</span> <span class="pl-c1">=</span> <span class="pl-c1">42</span>
<span class="pl-en">get</span> <span class="pl-pds">:index</span>
<span class="pl-v">CurrentUser</span><span class="pl-kos">.</span><span class="pl-en">id</span> == <span class="pl-c1">nil</span>
<span class="pl-k">end</span></pre></div>
<p>Now re-entering the executor properly save and restore that state.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>The new method <code>ActiveSupport::BacktraceCleaner#first_clean_location</code><br>
returns the first clean location of the caller's call stack, or <code>nil</code>.<br>
Locations are <code>Thread::Backtrace::Location</code> objects. Useful when you want to<br>
report the application-level location where something happened as an object.</p>
<p><em>Xavier Noria</em></p>
</li>
<li>
<p>FileUpdateChecker and EventedFileUpdateChecker ignore changes in Gem.path now.</p>
<p><em>Ermolaev Andrey</em>, <em>zzak</em></p>
</li>
<li>
<p>The new method <code>ActiveSupport::BacktraceCleaner#first_clean_frame</code> returns<br>
the first clean frame of the caller's backtrace, or <code>nil</code>. Useful when you<br>
want to report the application-level frame where something happened as a<br>
string.</p>
<p><em>Xavier Noria</em></p>
</li>
<li>
<p>Always clear <code>CurrentAttributes</code> instances.</p>
<p>Previously <code>CurrentAttributes</code> instance would be reset at the end of requests.<br>
Meaning its attributes would be re-initialized.</p>
<p>This is problematic because it assume these objects don't hold any state<br>
other than their declared attribute, which isn't always the case, and<br>
can lead to state leak across request.</p>
<p>Now <code>CurrentAttributes</code> instances are abandoned at the end of a request,<br>
and a new instance is created at the start of the next request.</p>
<p><em>Jean Boussier</em>, <em>Janko Marohnić</em></p>
</li>
<li>
<p>Add public API for <code>before_fork_hook</code> in parallel testing.</p>
<p>Introduces a public API for calling the before fork hooks implemented by parallel testing.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="parallelize_before_fork do
# perform an action before test processes are forked
end"><pre><span class="pl-en">parallelize_before_fork</span> <span class="pl-k">do</span>
<span class="pl-c"># perform an action before test processes are forked</span>
<span class="pl-k">end</span></pre></div>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Implement ability to skip creating parallel testing databases.</p>
<p>With parallel testing, Rails will create a database per process. If this isn't<br>
desirable or you would like to implement databases handling on your own, you can<br>
now turn off this default behavior.</p>
<p>To skip creating a database per process, you can change it via the<br>
<code>parallelize</code> method:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="parallelize(workers: 10, parallelize_databases: false)"><pre><span class="pl-en">parallelize</span><span class="pl-kos">(</span><span class="pl-pds">workers</span>: <span class="pl-c1">10</span><span class="pl-kos">,</span> <span class="pl-pds">parallelize_databases</span>: <span class="pl-c1">false</span><span class="pl-kos">)</span></pre></div>
<p>or via the application configuration:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.active_support.parallelize_databases = false"><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_support</span><span class="pl-kos">.</span><span class="pl-en">parallelize_databases</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span></pre></div>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Allow to configure maximum cache key sizes</p>
<p>When the key exceeds the configured limit (250 bytes by default), it will be truncated and<br>
the digest of the rest of the key appended to it.</p>
<p>Note that previously <code>ActiveSupport::Cache::RedisCacheStore</code> allowed up to 1kb cache keys before<br>
truncation, which is now reduced to 250 bytes.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.cache_store = :redis_cache_store, { max_key_size: 64 }"><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">cache_store</span> <span class="pl-c1">=</span> <span class="pl-pds">:redis_cache_store</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-pds">max_key_size</span>: <span class="pl-c1">64</span> <span class="pl-kos">}</span></pre></div>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Use <code>UNLINK</code> command instead of <code>DEL</code> in <code>ActiveSupport::Cache::RedisCacheStore</code> for non-blocking deletion.</p>
<p><em>Aron Roh</em></p>
</li>
<li>
<p>Add <code>Cache#read_counter</code> and <code>Cache#write_counter</code></p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Rails.cache.write_counter("foo", 1)
Rails.cache.read_counter("foo") # => 1
Rails.cache.increment("foo")
Rails.cache.read_counter("foo") # => 2"><pre><span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">write_counter</span><span class="pl-kos">(</span><span class="pl-s">"foo"</span><span class="pl-kos">,</span> <span class="pl-c1">1</span><span class="pl-kos">)</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">read_counter</span><span class="pl-kos">(</span><span class="pl-s">"foo"</span><span class="pl-kos">)</span> <span class="pl-c"># => 1</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">increment</span><span class="pl-kos">(</span><span class="pl-s">"foo"</span><span class="pl-kos">)</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">cache</span><span class="pl-kos">.</span><span class="pl-en">read_counter</span><span class="pl-kos">(</span><span class="pl-s">"foo"</span><span class="pl-kos">)</span> <span class="pl-c"># => 2</span></pre></div>
<p><em>Alex Ghiculescu</em></p>
</li>
<li>
<p>Introduce ActiveSupport::Testing::ErrorReporterAssertions#capture_error_reports</p>
<p>Captures all reported errors from within the block that match the given<br>
error class.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="reports = capture_error_reports(IOError) do
Rails.error.report(IOError.new("Oops"))
Rails.error.report(IOError.new("Oh no"))
Rails.error.report(StandardError.new)
end
assert_equal 2, reports.size
assert_equal "Oops", reports.first.error.message
assert_equal "Oh no", reports.last.error.message"><pre><span class="pl-s1">reports</span> <span class="pl-c1">=</span> <span class="pl-en">capture_error_reports</span><span class="pl-kos">(</span><span class="pl-v">IOError</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">.</span><span class="pl-en">report</span><span class="pl-kos">(</span><span class="pl-v">IOError</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"Oops"</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">.</span><span class="pl-en">report</span><span class="pl-kos">(</span><span class="pl-v">IOError</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"Oh no"</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">.</span><span class="pl-en">report</span><span class="pl-kos">(</span><span class="pl-v">StandardError</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-en">assert_equal</span> <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-s1">reports</span><span class="pl-kos">.</span><span class="pl-en">size</span>
<span class="pl-en">assert_equal</span> <span class="pl-s">"Oops"</span><span class="pl-kos">,</span> <span class="pl-s1">reports</span><span class="pl-kos">.</span><span class="pl-en">first</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">.</span><span class="pl-en">message</span>
<span class="pl-en">assert_equal</span> <span class="pl-s">"Oh no"</span><span class="pl-kos">,</span> <span class="pl-s1">reports</span><span class="pl-kos">.</span><span class="pl-en">last</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">.</span><span class="pl-en">message</span></pre></div>
<p><em>Andrew Novoselac</em></p>
</li>
<li>
<p>Introduce ActiveSupport::ErrorReporter#add_middleware</p>
<p>When reporting an error, the error context middleware will be called with the reported error<br>
and base execution context. The stack may mutate the context hash. The mutated context will<br>
then be passed to error subscribers. Middleware receives the same parameters as <code>ErrorReporter#report</code>.</p>
<p><em>Andrew Novoselac</em>, <em>Sam Schmidt</em></p>
</li>
<li>
<p>Change execution wrapping to report all exceptions, including <code>Exception</code>.</p>
<p>If a more serious error like <code>SystemStackError</code> or <code>NoMemoryError</code> happens,<br>
the error reporter should be able to report these kinds of exceptions.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p><code>ActiveSupport::Testing::Parallelization.before_fork_hook</code> allows declaration of callbacks that<br>
are invoked immediately before forking test workers.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Allow the <code>#freeze_time</code> testing helper to accept a date or time argument.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Time.current # => Sun, 09 Jul 2024 15:34:49 EST -05:00
freeze_time Time.current + 1.day
sleep 1
Time.current # => Mon, 10 Jul 2024 15:34:49 EST -05:00"><pre><span class="pl-v">Time</span><span class="pl-kos">.</span><span class="pl-en">current</span> <span class="pl-c"># => Sun, 09 Jul 2024 15:34:49 EST -05:00</span>
<span class="pl-en">freeze_time</span> <span class="pl-v">Time</span><span class="pl-kos">.</span><span class="pl-en">current</span> + <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">day</span>
<span class="pl-en">sleep</span> <span class="pl-c1">1</span>
<span class="pl-v">Time</span><span class="pl-kos">.</span><span class="pl-en">current</span> <span class="pl-c"># => Mon, 10 Jul 2024 15:34:49 EST -05:00</span></pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p><code>ActiveSupport::JSON</code> now accepts options</p>
<p>It is now possible to pass options to <code>ActiveSupport::JSON</code>:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveSupport::JSON.decode('{"key": "value"}', symbolize_names: true) # => { key: "value" }"><pre><span class="pl-v">ActiveSupport</span>::<span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">decode</span><span class="pl-kos">(</span><span class="pl-s">'{"key": "value"}'</span><span class="pl-kos">,</span> <span class="pl-pds">symbolize_names</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span> <span class="pl-c"># => { key: "value" }</span></pre></div>
<p><em>matthaigh27</em></p>
</li>
<li>
<p><code>ActiveSupport::Testing::NotificationAssertions</code>'s <code>assert_notification</code> now matches against payload subsets by default.</p>
<p>Previously the following assertion would fail due to excess key vals in the notification payload. Now with payload subset matching, it will pass.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="assert_notification("post.submitted", title: "Cool Post") do
ActiveSupport::Notifications.instrument("post.submitted", title: "Cool Post", body: "Cool Body")
end"><pre><span class="pl-en">assert_notification</span><span class="pl-kos">(</span><span class="pl-s">"post.submitted"</span><span class="pl-kos">,</span> <span class="pl-pds">title</span>: <span class="pl-s">"Cool Post"</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">ActiveSupport</span>::<span class="pl-v">Notifications</span><span class="pl-kos">.</span><span class="pl-en">instrument</span><span class="pl-kos">(</span><span class="pl-s">"post.submitted"</span><span class="pl-kos">,</span> <span class="pl-pds">title</span>: <span class="pl-s">"Cool Post"</span><span class="pl-kos">,</span> <span class="pl-pds">body</span>: <span class="pl-s">"Cool Body"</span><span class="pl-kos">)</span>
<span class="pl-k">end</span></pre></div>
<p>Additionally, you can now persist a matched notification for more customized assertions.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="notification = assert_notification("post.submitted", title: "Cool Post") do
ActiveSupport::Notifications.instrument("post.submitted", title: "Cool Post", body: Body.new("Cool Body"))
end
assert_instance_of(Body, notification.payload[:body])"><pre><span class="pl-s1">notification</span> <span class="pl-c1">=</span> <span class="pl-en">assert_notification</span><span class="pl-kos">(</span><span class="pl-s">"post.submitted"</span><span class="pl-kos">,</span> <span class="pl-pds">title</span>: <span class="pl-s">"Cool Post"</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">ActiveSupport</span>::<span class="pl-v">Notifications</span><span class="pl-kos">.</span><span class="pl-en">instrument</span><span class="pl-kos">(</span><span class="pl-s">"post.submitted"</span><span class="pl-kos">,</span> <span class="pl-pds">title</span>: <span class="pl-s">"Cool Post"</span><span class="pl-kos">,</span> <span class="pl-pds">body</span>: <span class="pl-v">Body</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"Cool Body"</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-en">assert_instance_of</span><span class="pl-kos">(</span><span class="pl-v">Body</span><span class="pl-kos">,</span> <span class="pl-s1">notification</span><span class="pl-kos">.</span><span class="pl-en">payload</span><span class="pl-kos">[</span><span class="pl-pds">:body</span><span class="pl-kos">]</span><span class="pl-kos">)</span></pre></div>
<p><em>Nicholas La Roux</em></p>
</li>
<li>
<p>Deprecate <code>String#mb_chars</code> and <code>ActiveSupport::Multibyte::Chars</code>.</p>
<p>These APIs are a relic of the Ruby 1.8 days when Ruby strings weren't encoding<br>
aware. There is no legitimate reasons to need these APIs today.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Deprecate <code>ActiveSupport::Configurable</code></p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p><code>nil.to_query("key")</code> now returns <code>key</code>.</p>
<p>Previously it would return <code>key=</code>, preventing round tripping with <code>Rack::Utils.parse_nested_query</code>.</p>
<p><em>Erol Fornoles</em></p>
</li>
<li>
<p>Avoid wrapping redis in a <code>ConnectionPool</code> when using <code>ActiveSupport::Cache::RedisCacheStore</code> if the <code>:redis</code><br>
option is already a <code>ConnectionPool</code>.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Alter <code>ERB::Util.tokenize</code> to return :PLAIN token with full input string when string doesn't contain ERB tags.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Fix a bug in <code>ERB::Util.tokenize</code> that causes incorrect tokenization when ERB tags are preceded by multibyte characters.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Add <code>ActiveSupport::Testing::NotificationAssertions</code> module to help with testing <code>ActiveSupport::Notifications</code>.</p>
<p><em>Nicholas La Roux</em>, <em>Yishu See</em>, <em>Sean Doyle</em></p>
</li>
<li>
<p><code>ActiveSupport::CurrentAttributes#attributes</code> now will return a new hash object on each call.</p>
<p>Previously, the same hash object was returned each time that method was called.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p><code>ActiveSupport::JSON.encode</code> supports CIDR notation.</p>
<p>Previously:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveSupport::JSON.encode(IPAddr.new("172.16.0.0/24")) # => "\"172.16.0.0\"""><pre><span class="pl-v">ActiveSupport</span>::<span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">encode</span><span class="pl-kos">(</span><span class="pl-v">IPAddr</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"172.16.0.0/24"</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-c"># => "\"172.16.0.0\""</span></pre></div>
<p>After this change:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveSupport::JSON.encode(IPAddr.new("172.16.0.0/24")) # => "\"172.16.0.0/24\"""><pre><span class="pl-v">ActiveSupport</span>::<span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">encode</span><span class="pl-kos">(</span><span class="pl-v">IPAddr</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"172.16.0.0/24"</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-c"># => "\"172.16.0.0/24\""</span></pre></div>
<p><em>Taketo Takashima</em></p>
</li>
<li>
<p>Make <code>ActiveSupport::FileUpdateChecker</code> faster when checking many file-extensions.</p>
<p><em>Jonathan del Strother</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>
<p>Add <code>reset_token: { expires_in: ... }</code> option to <code>has_secure_password</code>.</p>
<p>Allows configuring the expiry duration of password reset tokens (default remains 15 minutes for backwards compatibility).</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="has_secure_password reset_token: { expires_in: 1.hour }"><pre><span class="pl-en">has_secure_password</span> <span class="pl-pds">reset_token</span>: <span class="pl-kos">{</span> <span class="pl-pds">expires_in</span>: <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">hour</span> <span class="pl-kos">}</span></pre></div>
<p><em>Jevin Sew</em>, <em>Abeid Ahmed</em></p>
</li>
<li>
<p>Add <code>except_on:</code> option for validation callbacks.</p>
<p><em>Ben Sheldon</em></p>
</li>
<li>
<p>Backport <code>ActiveRecord::Normalization</code> to <code>ActiveModel::Attributes::Normalization</code></p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class User
include ActiveModel::Attributes
include ActiveModel::Attributes::Normalization
attribute :email, :string
normalizes :email, with: -> email { email.strip.downcase }
end
user = User.new
user.email = " [email protected]\n"
user.email # => "[email protected]""><pre><span class="pl-k">class</span> <span class="pl-v">User</span>
<span class="pl-en">include</span> <span class="pl-v">ActiveModel</span>::<span class="pl-v">Attributes</span>
<span class="pl-en">include</span> <span class="pl-v">ActiveModel</span>::<span class="pl-v">Attributes</span>::<span class="pl-v">Normalization</span>
<span class="pl-en">attribute</span> <span class="pl-pds">:email</span><span class="pl-kos">,</span> <span class="pl-pds">:string</span>
<span class="pl-en">normalizes</span> <span class="pl-pds">:email</span><span class="pl-kos">,</span> <span class="pl-pds">with</span>: <span class="pl-c1">-></span> <span class="pl-s1">email</span> <span class="pl-kos">{</span> <span class="pl-s1">email</span><span class="pl-kos">.</span><span class="pl-en">strip</span><span class="pl-kos">.</span><span class="pl-en">downcase</span> <span class="pl-kos">}</span>
<span class="pl-k">end</span>
<span class="pl-s1">user</span> <span class="pl-c1">=</span> <span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">new</span>
<span class="pl-s1">user</span><span class="pl-kos">.</span><span class="pl-en">email</span> <span class="pl-c1">=</span> <span class="pl-s">" [email protected]<span class="pl-cce">\n</span>"</span>
<span class="pl-s1">user</span><span class="pl-kos">.</span><span class="pl-en">email</span> <span class="pl-c"># => "[email protected]"</span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix SQLite3 data loss during table alterations with CASCADE foreign keys.</p>
<p>When altering a table in SQLite3 that is referenced by child tables with<br>
<code>ON DELETE CASCADE</code> foreign keys, ActiveRecord would silently delete all<br>
data from the child tables. This occurred because SQLite requires table<br>
recreation for schema changes, and during this process the original table<br>
is temporarily dropped, triggering CASCADE deletes on child tables.</p>
<p>The root cause was incorrect ordering of operations. The original code<br>
wrapped <code>disable_referential_integrity</code> inside a transaction, but<br>
<code>PRAGMA foreign_keys</code> cannot be modified inside a transaction in SQLite -<br>
attempting to do so simply has no effect. This meant foreign keys remained<br>
enabled during table recreation, causing CASCADE deletes to fire.</p>
<p>The fix reverses the order to follow the official SQLite 12-step ALTER TABLE<br>
procedure: <code>disable_referential_integrity</code> now wraps the transaction instead<br>
of being wrapped by it. This ensures foreign keys are properly disabled<br>
before the transaction starts and re-enabled after it commits, preventing<br>
CASCADE deletes while maintaining data integrity through atomic transactions.</p>
<p><em>Ruy Rocha</em></p>
</li>
<li>
<p>Add replicas to test database parallelization setup.</p>
<p>Setup and configuration of databases for parallel testing now includes replicas.</p>
<p>This fixes an issue when using a replica database, database selector middleware,<br>
and non-transactional tests, where integration tests running in parallel would select<br>
the base test database, i.e. <code>db_test</code>, instead of the numbered parallel worker database,<br>
i.e. <code>db_test_{n}</code>.</p>
<p><em>Adam Maas</em></p>
</li>
<li>
<p>Support virtual (not persisted) generated columns on PostgreSQL 18+</p>
<p>PostgreSQL 18 introduces virtual (not persisted) generated columns,<br>
which are now the default unless the <code>stored: true</code> option is explicitly specified on PostgreSQL 18+.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="create_table :users do |t|
t.string :name
t.virtual :lower_name, type: :string, as: "LOWER(name)", stored: false
t.virtual :name_length, type: :integer, as: "LENGTH(name)"
end"><pre><span class="pl-en">create_table</span> <span class="pl-pds">:users</span> <span class="pl-k">do</span> |<span class="pl-s1">t</span>|
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">string</span> <span class="pl-pds">:name</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">virtual</span> <span class="pl-pds">:lower_name</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:string</span><span class="pl-kos">,</span> <span class="pl-pds">as</span>: <span class="pl-s">"LOWER(name)"</span><span class="pl-kos">,</span> <span class="pl-pds">stored</span>: <span class="pl-c1">false</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">virtual</span> <span class="pl-pds">:name_length</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:integer</span><span class="pl-kos">,</span> <span class="pl-pds">as</span>: <span class="pl-s">"LENGTH(name)"</span>
<span class="pl-k">end</span></pre></div>
<p><em>Yasuo Honda</em></p>
</li>
<li>
<p>Optimize schema dumping to prevent duplicate file generation.</p>
<p><code>ActiveRecord::Tasks::DatabaseTasks.dump_all</code> now tracks which schema files<br>
have already been dumped and skips dumping the same file multiple times.<br>
This improves performance when multiple database configurations share the<br>
same schema dump path.</p>
<p><em>Mikey Gough</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Add structured events for Active Record:</p>
<ul>
<li><code>active_record.strict_loading_violation</code></li>
<li><code>active_record.sql</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add support for integer shard keys.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Now accepts symbols as shard keys.
ActiveRecord::Base.connects_to(shards: {
1: { writing: :primary_shard_one, reading: :primary_shard_one },
2: { writing: :primary_shard_two, reading: :primary_shard_two},
})
ActiveRecord::Base.connected_to(shard: 1) do
# ..
end"><pre><span class="pl-c"># Now accepts symbols as shard keys.</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">connects_to</span><span class="pl-kos">(</span><span class="pl-pds">shards</span>: <span class="pl-kos">{</span>
<span class="pl-c1">1</span>: <span class="pl-kos">{</span> <span class="pl-pds">writing</span>: <span class="pl-pds">:primary_shard_one</span><span class="pl-kos">,</span> <span class="pl-pds">reading</span>: <span class="pl-pds">:primary_shard_one</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">2</span>: <span class="pl-kos">{</span> <span class="pl-pds">writing</span>: <span class="pl-pds">:primary_shard_two</span><span class="pl-kos">,</span> <span class="pl-pds">reading</span>: <span class="pl-pds">:primary_shard_two</span><span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">connected_to</span><span class="pl-kos">(</span><span class="pl-pds">shard</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-c"># ..</span>
<span class="pl-k">end</span></pre></div>
<p><em>Nony Dutton</em></p>
</li>
<li>
<p>Add <code>ActiveRecord::Base.only_columns</code></p>
<p>Similar in use case to <code>ignored_columns</code> but listing columns to consider rather than the ones<br>
to ignore.</p>
<p>Can be useful when working with a legacy or shared database schema, or to make safe schema change<br>
in two deploys rather than three.</p>
<p><em>Anton Kandratski</em></p>
</li>
<li>
<p>Use <code>PG::Connection#close_prepared</code> (protocol level Close) to deallocate<br>
prepared statements when available.</p>
<p>To enable its use, you must have pg >= 1.6.0, libpq >= 17, and a PostgreSQL<br>
database version >= 17.</p>
<p><em>Hartley McGuire</em>, <em>Andrew Jackson</em></p>
</li>
<li>
<p>Fix query cache for pinned connections in multi threaded transactional tests</p>
<p>When a pinned connection is used across separate threads, they now use a separate cache store<br>
for each thread.</p>
<p>This improve accuracy of system tests, and any test using multiple threads.</p>
<p><em>Heinrich Lee Yu</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix time attribute dirty tracking with timezone conversions.</p>
<p>Time-only attributes now maintain a fixed date of 2000-01-01 during timezone conversions,<br>
preventing them from being incorrectly marked as changed due to date shifts.</p>
<p>This fixes an issue where time attributes would be marked as changed when setting the same time value<br>
due to timezone conversion causing internal date shifts.</p>
<p><em>Prateek Choudhary</em></p>
</li>
<li>
<p>Skip calling <code>PG::Connection#cancel</code> in <code>cancel_any_running_query</code><br>
when using libpq >= 18 with pg < 1.6.0, due to incompatibility.<br>
Rollback still runs, but may take longer.</p>
<p><em>Yasuo Honda</em>, <em>Lars Kanis</em></p>
</li>
<li>
<p>Don't add <code>id_value</code> attribute alias when attribute/column with that name already exists.</p>
<p><em>Rob Lewis</em></p>
</li>
<li>
<p>Remove deprecated <code>:unsigned_float</code> and <code>:unsigned_decimal</code> column methods for MySQL.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>:retries</code> option for the SQLite3 adapter.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Introduce new database configuration options <code>keepalive</code>, <code>max_age</code>, and<br>
<code>min_connections</code> -- and rename <code>pool</code> to <code>max_connections</code> to match.</p>
<p>There are no changes to default behavior, but these allow for more specific<br>
control over pool behavior.</p>
<p><em>Matthew Draper</em>, <em>Chris AtLee</em>, <em>Rachael Wright-Munn</em></p>
</li>
<li>
<p>Move <code>LIMIT</code> validation from query generation to when <code>limit()</code> is called.</p>
<p><em>Hartley McGuire</em>, <em>Shuyang</em></p>
</li>
<li>
<p>Add <code>ActiveRecord::CheckViolation</code> error class for check constraint violations.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Add <code>ActiveRecord::ExclusionViolation</code> error class for exclusion constraint violations.</p>
<p>When an exclusion constraint is violated in PostgreSQL, the error will now be raised<br>
as <code>ActiveRecord::ExclusionViolation</code> instead of the generic <code>ActiveRecord::StatementInvalid</code>,<br>
making it easier to handle these specific constraint violations in application code.</p>
<p>This follows the same pattern as other constraint violation error classes like<br>
<code>RecordNotUnique</code> for unique constraint violations and <code>InvalidForeignKey</code> for<br>
foreign key constraint violations.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p>Attributes filtered by <code>filter_attributes</code> will now also be filtered by <code>filter_parameters</code><br>
so sensitive information is not leaked.</p>
<p><em>Jill Klang</em></p>
</li>
<li>
<p>Add <code>connection.current_transaction.isolation</code> API to check current transaction's isolation level.</p>
<p>Returns the isolation level if it was explicitly set via the <code>isolation:</code> parameter<br>
or through <code>ActiveRecord.with_transaction_isolation_level</code>, otherwise returns <code>nil</code>.<br>
Nested transactions return the parent transaction's isolation level.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Returns nil when no transaction
User.connection.current_transaction.isolation # => nil
# Returns explicitly set isolation level
User.transaction(isolation: :serializable) do
User.connection.current_transaction.isolation # => :serializable
end
# Returns nil when isolation not explicitly set
User.transaction do
User.connection.current_transaction.isolation # => nil
end
# Nested transactions inherit parent's isolation
User.transaction(isolation: :read_committed) do
User.transaction do
User.connection.current_transaction.isolation # => :read_committed
end
end"><pre><span class="pl-c"># Returns nil when no transaction</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">connection</span><span class="pl-kos">.</span><span class="pl-en">current_transaction</span><span class="pl-kos">.</span><span class="pl-en">isolation</span> <span class="pl-c"># => nil</span>
<span class="pl-c"># Returns explicitly set isolation level</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">transaction</span><span class="pl-kos">(</span><span class="pl-pds">isolation</span>: <span class="pl-pds">:serializable</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">connection</span><span class="pl-kos">.</span><span class="pl-en">current_transaction</span><span class="pl-kos">.</span><span class="pl-en">isolation</span> <span class="pl-c"># => :serializable</span>
<span class="pl-k">end</span>
<span class="pl-c"># Returns nil when isolation not explicitly set</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">transaction</span> <span class="pl-k">do</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">connection</span><span class="pl-kos">.</span><span class="pl-en">current_transaction</span><span class="pl-kos">.</span><span class="pl-en">isolation</span> <span class="pl-c"># => nil</span>
<span class="pl-k">end</span>
<span class="pl-c"># Nested transactions inherit parent's isolation</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">transaction</span><span class="pl-kos">(</span><span class="pl-pds">isolation</span>: <span class="pl-pds">:read_committed</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">transaction</span> <span class="pl-k">do</span>
<span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">connection</span><span class="pl-kos">.</span><span class="pl-en">current_transaction</span><span class="pl-kos">.</span><span class="pl-en">isolation</span> <span class="pl-c"># => :read_committed</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Kir Shatrov</em></p>
</li>
<li>
<p>Fix <code>#merge</code> with <code>#or</code> or <code>#and</code> and a mixture of attributes and SQL strings resulting in an incorrect query.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="base = Comment.joins(:post).where(user_id: 1).where("recent = 1")
puts base.merge(base.where(draft: true).or(Post.where(archived: true))).to_sql"><pre><span class="pl-s1">base</span> <span class="pl-c1">=</span> <span class="pl-v">Comment</span><span class="pl-kos">.</span><span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-pds">:post</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">user_id</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-s">"recent = 1"</span><span class="pl-kos">)</span>
<span class="pl-en">puts</span> <span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">merge</span><span class="pl-kos">(</span><span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">draft</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">or</span><span class="pl-kos">(</span><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">archived</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to_sql</span></pre></div>
<p>Before:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p>After:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE "comments"."user_id" = 1
AND (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Make schema dumper to account for <code>ActiveRecord.dump_schemas</code> when dumping in <code>:ruby</code> format.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Add <code>:touch</code> option to <code>update_column</code>/<code>update_columns</code> methods.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Will update :updated_at/:updated_on alongside :nice column.
user.update_column(:nice, true, touch: true)
# Will update :updated_at/:updated_on alongside :last_ip column
user.update_columns(last_ip: request.remote_ip, touch: true)"><pre><span class="pl-c"># Will update :updated_at/:updated_on alongside :nice column.</span>
<span class="pl-en">user</span><span class="pl-kos">.</span><span class="pl-en">update_column</span><span class="pl-kos">(</span><span class="pl-pds">:nice</span><span class="pl-kos">,</span> <span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-pds">touch</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span>
<span class="pl-c"># Will update :updated_at/:updated_on alongside :last_ip column</span>
<span class="pl-en">user</span><span class="pl-kos">.</span><span class="pl-en">update_columns</span><span class="pl-kos">(</span><span class="pl-pds">last_ip</span>: <span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">remote_ip</span><span class="pl-kos">,</span> <span class="pl-pds">touch</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span></pre></div>
<p><em>Dmitrii Ivliev</em></p>
</li>
<li>
<p>Optimize Active Record batching further when using ranges.</p>
<p>Tested on a PostgreSQL table with 10M records and batches of 10k records, the generation<br>
of relations for the 1000 batches was <code>4.8x</code> faster (<code>6.8s</code> vs. <code>1.4s</code>), used <code>900x</code><br>
less bandwidth (<code>180MB</code> vs. <code>0.2MB</code>) and allocated <code>45x</code> less memory (<code>490MB</code> vs. <code>11MB</code>).</p>
<p><em>Maxime Réty</em>, <em>fatkodima</em></p>
</li>
<li>
<p>Include current character length in error messages for index and table name length validations.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Add <code>rename_schema</code> method for PostgreSQL.</p>
<p><em>T S Vallender</em></p>
</li>
<li>
<p>Implement support for deprecating associations:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="has_many :posts, deprecated: true"><pre><span class="pl-en">has_many</span> <span class="pl-pds">:posts</span><span class="pl-kos">,</span> <span class="pl-pds">deprecated</span>: <span class="pl-c1">true</span></pre></div>
<p>With that, Active Record will report any usage of the <code>posts</code> association.</p>
<p>Three reporting modes are supported (<code>:warn</code>, <code>:raise</code>, and <code>:notify</code>), and<br>
backtraces can be enabled or disabled. Defaults are <code>:warn</code> mode and<br>
disabled backtraces.</p>
<p>Please, check the docs for further details.</p>
<p><em>Xavier Noria</em></p>
</li>
<li>
<p>PostgreSQL adapter create DB now supports <code>locale_provider</code> and <code>locale</code>.</p>
<p><em>Bengt-Ove Hollaender</em></p>
</li>
<li>
<p>Use ntuples to populate row_count instead of count for Postgres</p>
<p><em>Jonathan Calvert</em></p>
</li>
<li>
<p>Fix checking whether an unpersisted record is <code>include?</code>d in a strictly<br>
loaded <code>has_and_belongs_to_many</code> association.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Add ability to change transaction isolation for all pools within a block.</p>
<p>This functionality is useful if your application needs to change the database<br>
transaction isolation for a request or action.</p>
<p>Calling <code>ActiveRecord.with_transaction_isolation_level(level) {}</code> in an around filter or<br>
middleware will set the transaction isolation for all pools accessed within the block,<br>
but not for the pools that aren't.</p>
<p>This works with explicit and implicit transactions:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveRecord.with_transaction_isolation_level(:read_committed) do
Tag.transaction do # opens a transaction explicitly
Tag.create!
end
end"><pre><span class="pl-v">ActiveRecord</span><span class="pl-kos">.</span><span class="pl-en">with_transaction_isolation_level</span><span class="pl-kos">(</span><span class="pl-pds">:read_committed</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">Tag</span><span class="pl-kos">.</span><span class="pl-en">transaction</span> <span class="pl-k">do</span> <span class="pl-c"># opens a transaction explicitly</span>
<span class="pl-v">Tag</span><span class="pl-kos">.</span><span class="pl-en">create!</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveRecord.with_transaction_isolation_level(:read_committed) do
Tag.create! # opens a transaction implicitly
end"><pre><span class="pl-v">ActiveRecord</span><span class="pl-kos">.</span><span class="pl-en">with_transaction_isolation_level</span><span class="pl-kos">(</span><span class="pl-pds">:read_committed</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">Tag</span><span class="pl-kos">.</span><span class="pl-en">create!</span> <span class="pl-c"># opens a transaction implicitly</span>
<span class="pl-k">end</span></pre></div>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Raise <code>ActiveRecord::MissingRequiredOrderError</code> when order dependent finder methods (e.g. <code>#first</code>, <code>#last</code>) are<br>
called without <code>order</code> values on the relation, and the model does not have any order columns (<code>implicit_order_column</code>,<br>
<code>query_constraints</code>, or <code>primary_key</code>) to fall back on.</p>
<p>This change will be introduced with a new framework default for Rails 8.1, and the current behavior of not raising<br>
an error has been deprecated with the aim of removing the configuration option in Rails 8.2.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.active_record.raise_on_missing_required_finder_order_columns = true"><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_record</span><span class="pl-kos">.</span><span class="pl-en">raise_on_missing_required_finder_order_columns</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span></pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p><code>:class_name</code> is now invalid in polymorphic <code>belongs_to</code> associations.</p>
<p>Reason is <code>:class_name</code> does not make sense in those associations because<br>
the class name of target records is dynamic and stored in the type column.</p>
<p>Existing polymorphic associations setting this option can just delete it.<br>
While it did not raise, it had no effect anyway.</p>
<p><em>Xavier Noria</em></p>
</li>
<li>
<p>Add support for multiple databases to <code>db:migrate:reset</code>.</p>
<p><em>Joé Dupuis</em></p>
</li>
<li>
<p>Add <code>affected_rows</code> to <code>ActiveRecord::Result</code>.</p>
<p><em>Jenny Shen</em></p>
</li>
<li>
<p>Enable passing retryable SqlLiterals to <code>#where</code>.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Set default for primary keys in <code>insert_all</code>/<code>upsert_all</code>.</p>
<p>Previously in Postgres, updating and inserting new records in one upsert wasn't possible<br>
due to null primary key values. <code>nil</code> primary key values passed into <code>insert_all</code>/<code>upsert_all</code><br>
are now implicitly set to the default insert value specified by adapter.</p>
<p><em>Jenny Shen</em></p>
</li>
<li>
<p>Add a load hook <code>active_record_database_configurations</code> for <code>ActiveRecord::DatabaseConfigurations</code></p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Use <code>TRUE</code> and <code>FALSE</code> for SQLite queries with boolean columns.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Bump minimum supported SQLite to 3.23.0.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Allow allocated Active Records to lookup associations.</p>
<p>Previously, the association cache isn't setup on allocated record objects, so association<br>
lookups will crash. Test frameworks like mocha use allocate to check for stubbable instance<br>
methods, which can trigger an association lookup.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Encryption now supports <code>support_unencrypted_data: true</code> being set per-attribute.</p>
<p>Previously this only worked if <code>ActiveRecord::Encryption.config.support_unencrypted_data == true</code>.<br>
Now, if the global config is turned off, you can still opt in for a specific attribute.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# ActiveRecord::Encryption.config.support_unencrypted_data = true
class User < ActiveRecord::Base
encrypts :name, support_unencrypted_data: false # only supports encrypted data
encrypts :email # supports encrypted or unencrypted data
end"><pre><span class="pl-c"># ActiveRecord::Encryption.config.support_unencrypted_data = true</span>
<span class="pl-k">class</span> <span class="pl-v">User</span> < <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span>
<span class="pl-en">encrypts</span> <span class="pl-pds">:name</span><span class="pl-kos">,</span> <span class="pl-pds">support_unencrypted_data</span>: <span class="pl-c1">false</span> <span class="pl-c"># only supports encrypted data</span>
<span class="pl-en">encrypts</span> <span class="pl-pds">:email</span> <span class="pl-c"># supports encrypted or unencrypted data</span>
<span class="pl-k">end</span></pre></div>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# ActiveRecord::Encryption.config.support_unencrypted_data = false
class User < ActiveRecord::Base
encrypts :name, support_unencrypted_data: true # supports encrypted or unencrypted data
encrypts :email # only supports encrypted data
end"><pre><span class="pl-c"># ActiveRecord::Encryption.config.support_unencrypted_data = false</span>
<span class="pl-k">class</span> <span class="pl-v">User</span> < <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span>
<span class="pl-en">encrypts</span> <span class="pl-pds">:name</span><span class="pl-kos">,</span> <span class="pl-pds">support_unencrypted_data</span>: <span class="pl-c1">true</span> <span class="pl-c"># supports encrypted or unencrypted data</span>
<span class="pl-en">encrypts</span> <span class="pl-pds">:email</span> <span class="pl-c"># only supports encrypted data</span>
<span class="pl-k">end</span></pre></div>
<p><em>Alex Ghiculescu</em></p>
</li>
<li>
<p>Model generator no longer needs a database connection to validate column types.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Allow signed ID verifiers to be configurable via <code>Rails.application.message_verifiers</code></p>
<p>Prior to this change, the primary way to configure signed ID verifiers was<br>
to set <code>signed_id_verifier</code> on each model class:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Post.signed_id_verifier = ActiveSupport::MessageVerifier.new(...)
Comment.signed_id_verifier = ActiveSupport::MessageVerifier.new(...)"><pre><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span>
<span class="pl-v">Comment</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span></pre></div>
<p>And if the developer did not set <code>signed_id_verifier</code>, a verifier would be<br>
instantiated with a secret derived from <code>secret_key_base</code> and the following<br>
options:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="{ digest: "SHA256", serializer: JSON, url_safe: true }"><pre><span class="pl-kos">{</span> <span class="pl-pds">digest</span>: <span class="pl-s">"SHA256"</span><span class="pl-kos">,</span> <span class="pl-pds">serializer</span>: <span class="pl-c1">JSON</span><span class="pl-kos">,</span> <span class="pl-pds">url_safe</span>: <span class="pl-c1">true</span> <span class="pl-kos">}</span></pre></div>
<p>Thus it was cumbersome to rotate configuration for all verifiers.</p>
<p>This change defines a new Rails config: <a href="https://guides.rubyonrails.org/v8.1/configuring.html#config-active-record-use-legacy-signed-id-verifier" rel="nofollow"><code>config.active_record.use_legacy_signed_id_verifier</code></a>.<br>
The default value is <code>:generate_and_verify</code>, which preserves the previous<br>
behavior. However, when set to <code>:verify</code>, signed ID verifiers will use<br>
configuration from <code>Rails.application.message_verifiers</code> (specifically,<br>
<code>Rails.application.message_verifiers["active_record/signed_id"]</code>) to<br>
generate and verify signed IDs, but will also verify signed IDs using the<br>
older configuration.</p>
<p>To avoid complication, the new behavior only applies when <code>signed_id_verifier_secret</code><br>
is not set on a model class or any of its ancestors. Additionally,<br>
<code>signed_id_verifier_secret</code> is now deprecated. If you are currently setting<br>
<code>signed_id_verifier_secret</code> on a model class, you can set <code>signed_id_verifier</code><br>
instead:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# BEFORE
Post.signed_id_verifier_secret = "my secret"
# AFTER
Post.signed_id_verifier = ActiveSupport::MessageVerifier.new("my secret", digest: "SHA256", serializer: JSON, url_safe: true)"><pre><span class="pl-c"># BEFORE</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier_secret</span> <span class="pl-c1">=</span> <span class="pl-s">"my secret"</span>
<span class="pl-c"># AFTER</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"my secret"</span><span class="pl-kos">,</span> <span class="pl-pds">digest</span>: <span class="pl-s">"SHA256"</span><span class="pl-kos">,</span> <span class="pl-pds">serializer</span>: <span class="pl-c1">JSON</span><span class="pl-kos">,</span> <span class="pl-pds">url_safe</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span></pre></div>
<p>To ease migration, <code>signed_id_verifier</code> has also been changed to behave as a<br>
<code>class_attribute</code> (i.e. inheritable), but <em>only when <code>signed_id_verifier_secret</code><br>
is not set</em>:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# BEFORE
ActiveRecord::Base.signed_id_verifier = ActiveSupport::MessageVerifier.new(...)
Post.signed_id_verifier == ActiveRecord::Base.signed_id_verifier # => false
# AFTER
ActiveRecord::Base.signed_id_verifier = ActiveSupport::MessageVerifier.new(...)
Post.signed_id_verifier == ActiveRecord::Base.signed_id_verifier # => true
Post.signed_id_verifier_secret = "my secret" # => deprecation warning
Post.signed_id_verifier == ActiveRecord::Base.signed_id_verifier # => false"><pre><span class="pl-c"># BEFORE</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> == <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c"># => false</span>
<span class="pl-c"># AFTER</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> == <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c"># => true</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier_secret</span> <span class="pl-c1">=</span> <span class="pl-s">"my secret"</span> <span class="pl-c"># => deprecation warning</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> == <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c"># => false</span></pre></div>
<p>Note, however, that it is recommended to eventually migrate from<br>
model-specific verifiers to a unified configuration managed by<br>
<code>Rails.application.message_verifiers</code>. <code>ActiveSupport::MessageVerifier#rotate</code><br>
can facilitate that transition. For example:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# BEFORE
# Generate and verify signed Post IDs using Post-specific configuration
Post.signed_id_verifier = ActiveSupport::MessageVerifier.new("post secret", ...)
# AFTER
# Generate and verify signed Post IDs using the unified configuration
Post.signed_id_verifier = Post.signed_id_verifier.dup
# Fall back to Post-specific configuration when verifying signed IDs
Post.signed_id_verifier.rotate("post secret", ...)"><pre><span class="pl-c"># BEFORE</span>
<span class="pl-c"># Generate and verify signed Post IDs using Post-specific configuration</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveSupport</span>::<span class="pl-v">MessageVerifier</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"post secret"</span><span class="pl-kos">,</span> ...<span class="pl-kos">)</span>
<span class="pl-c"># AFTER</span>
<span class="pl-c"># Generate and verify signed Post IDs using the unified configuration</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span> <span class="pl-c1">=</span> <span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span><span class="pl-kos">.</span><span class="pl-en">dup</span>
<span class="pl-c"># Fall back to Post-specific configuration when verifying signed IDs</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">signed_id_verifier</span><span class="pl-kos">.</span><span class="pl-en">rotate</span><span class="pl-kos">(</span><span class="pl-s">"post secret"</span><span class="pl-kos">,</span> ...<span class="pl-kos">)</span></pre></div>
<p><em>Ali Sepehri</em>, <em>Jonathan Hefner</em></p>
</li>
<li>
<p>Prepend <code>extra_flags</code> in postgres' <code>structure_load</code></p>
<p>When specifying <code>structure_load_flags</code> with a postgres adapter, the flags<br>
were appended to the default flags, instead of prepended.<br>
This caused issues with flags not being taken into account by postgres.</p>
<p><em>Alice Loeser</em></p>
</li>
<li>
<p>Allow bypassing primary key/constraint addition in <code>implicit_order_column</code></p>
<p>When specifying multiple columns in an array for <code>implicit_order_column</code>, adding<br>
<code>nil</code> as the last element will prevent appending the primary key to order<br>
conditions. This allows more precise control of indexes used by<br>
generated queries. It should be noted that this feature does introduce the risk<br>
of API misbehavior if the specified columns are not fully unique.</p>
<p><em>Issy Long</em></p>
</li>
<li>
<p>Allow setting the <code>schema_format</code> via database configuration.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="primary:
schema_format: ruby"><pre class="notranslate"><code>primary:
schema_format: ruby
</code></pre></div>
<p>Useful for multi-database setups when apps require different formats per-database.</p>
<p><em>T S Vallender</em></p>
</li>
<li>
<p>Support disabling indexes for MySQL v8.0.0+ and MariaDB v10.6.0+</p>
<p>MySQL 8.0.0 added an option to disable indexes from being used by the query<br>
optimizer by making them "invisible". This allows the index to still be maintained<br>
and updated but no queries will be permitted to use it. This can be useful for adding<br>
new invisible indexes or making existing indexes invisible before dropping them<br>
to ensure queries are not negatively affected.<br>
See <a href="https://dev.mysql.com/blog-archive/mysql-8-0-invisible-indexes/" rel="nofollow">https://dev.mysql.com/blog-archive/mysql-8-0-invisible-indexes/</a> for more details.</p>
<p>MariaDB 10.6.0 also added support for this feature by allowing indexes to be "ignored"<br>
in queries. See <a href="https://mariadb.com/kb/en/ignored-indexes/" rel="nofollow">https://mariadb.com/kb/en/ignored-indexes/</a> for more details.</p>
<p>Active Record now supports this option for MySQL 8.0.0+ and MariaDB 10.6.0+ for<br>
index creation and alteration where the new index option <code>enabled: true/false</code> can be<br>
passed to column and index methods as below:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="add_index :users, :email, enabled: false
enable_index :users, :email
add_column :users, :dob, :string, index: { enabled: false }
change_table :users do |t|
t.index :name, enabled: false
t.index :dob
t.disable_index :dob
t.column :username, :string, index: { enabled: false }
t.references :account, index: { enabled: false }
end
create_table :users do |t|
t.string :name, index: { enabled: false }
t.string :email
t.index :email, enabled: false
end"><pre><span class="pl-en">add_index</span> <span class="pl-pds">:users</span><span class="pl-kos">,</span> <span class="pl-pds">:email</span><span class="pl-kos">,</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span>
<span class="pl-en">enable_index</span> <span class="pl-pds">:users</span><span class="pl-kos">,</span> <span class="pl-pds">:email</span>
<span class="pl-en">add_column</span> <span class="pl-pds">:users</span><span class="pl-kos">,</span> <span class="pl-pds">:dob</span><span class="pl-kos">,</span> <span class="pl-pds">:string</span><span class="pl-kos">,</span> <span class="pl-pds">index</span>: <span class="pl-kos">{</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span>
<span class="pl-en">change_table</span> <span class="pl-pds">:users</span> <span class="pl-k">do</span> |<span class="pl-s1">t</span>|
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">index</span> <span class="pl-pds">:name</span><span class="pl-kos">,</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">index</span> <span class="pl-pds">:dob</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">disable_index</span> <span class="pl-pds">:dob</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">column</span> <span class="pl-pds">:username</span><span class="pl-kos">,</span> <span class="pl-pds">:string</span><span class="pl-kos">,</span> <span class="pl-pds">index</span>: <span class="pl-kos">{</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">references</span> <span class="pl-pds">:account</span><span class="pl-kos">,</span> <span class="pl-pds">index</span>: <span class="pl-kos">{</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span>
<span class="pl-k">end</span>
<span class="pl-en">create_table</span> <span class="pl-pds">:users</span> <span class="pl-k">do</span> |<span class="pl-s1">t</span>|
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">string</span> <span class="pl-pds">:name</span><span class="pl-kos">,</span> <span class="pl-pds">index</span>: <span class="pl-kos">{</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">string</span> <span class="pl-pds">:email</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">index</span> <span class="pl-pds">:email</span><span class="pl-kos">,</span> <span class="pl-pds">enabled</span>: <span class="pl-c1">false</span>
<span class="pl-k">end</span></pre></div>
<p><em>Merve Taner</em></p>
</li>
<li>
<p>Respect <code>implicit_order_column</code> in <code>ActiveRecord::Relation#reverse_order</code>.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Add column types to <code>ActiveRecord::Result</code> for SQLite3.</p>
<p><em>Andrew Kane</em></p>
</li>
<li>
<p>Raise <code>ActiveRecord::ReadOnlyError</code> when pessimistically locking with a readonly role.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix using the <code>SQLite3Adapter</code>'s <code>dbconsole</code> method outside of a Rails application.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix migrating multiple databases with <code>ActiveRecord::PendingMigration</code> action.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Enable automatically retrying idempotent association queries on connection<br>
errors.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Add <code>allow_retry</code> to <code>sql.active_record</code> instrumentation.</p>
<p>This enables identifying queries which queries are automatically retryable on connection errors.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Better support UPDATE with JOIN for Postgresql and SQLite3</p>
<p>Previously when generating update queries with one or more JOIN clauses,<br>
Active Record would use a sub query which would prevent to reference the joined<br>
tables in the <code>SET</code> clause, for instance:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Comment.joins(:post).update_all("title = posts.title")"><pre><span class="pl-v">Comment</span><span class="pl-kos">.</span><span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-pds">:post</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">update_all</span><span class="pl-kos">(</span><span class="pl-s">"title = posts.title"</span><span class="pl-kos">)</span></pre></div>
<p>This is now supported as long as the relation doesn't also use a <code>LIMIT</code>, <code>ORDER</code> or<br>
<code>GROUP BY</code> clause. This was supported by the MySQL adapter for a long time.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Introduce a before-fork hook in <code>ActiveSupport::Testing::Parallelization</code> to clear existing<br>
connections, to avoid fork-safety issues with the mysql2 adapter.</p>
<p>Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="842582318" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/41776" data-hovercard-type="pull_request" data-hovercard-url="/rails/rails/pull/41776/hovercard" href="https://github.com/rails/rails/pull/41776">#41776</a></p>
<p><em>Mike Dalessio</em>, <em>Donal McBreen</em></p>
</li>
<li>
<p>PoolConfig no longer keeps a reference to the connection class.</p>
<p>Keeping a reference to the class caused subtle issues when combined with reloading in<br>
development. Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2809629945" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/54343" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/54343/hovercard" href="https://github.com/rails/rails/issues/54343">#54343</a>.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Fix SQL notifications sometimes not sent when using async queries.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Post.async_count
ActiveSupport::Notifications.subscribed(->(*) { "Will never reach here" }) do
Post.count
end"><pre><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">async_count</span>
<span class="pl-v">ActiveSupport</span>::<span class="pl-v">Notifications</span><span class="pl-kos">.</span><span class="pl-en">subscribed</span><span class="pl-kos">(</span><span class="pl-c1">-></span><span class="pl-kos">(</span>*<span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-s">"Will never reach here"</span> <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">count</span>
<span class="pl-k">end</span></pre></div>
<p>In rare circumstances and under the right race condition, Active Support notifications<br>
would no longer be dispatched after using an asynchronous query.<br>
This is now fixed.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Eliminate queries loading dumped schema cache on Postgres</p>
<p>Improve resiliency by avoiding needing to open a database connection to load the<br>
type map while defining attribute methods at boot when a schema cache file is<br>
configured on PostgreSQL databases.</p>
<p><em>James Coleman</em></p>
</li>
<li>
<p><code>ActiveRecord::Coder::JSON</code> can be instantiated</p>
<p>Options can now be passed to <code>ActiveRecord::Coder::JSON</code> when instantiating the coder. This allows:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="serialize :config, coder: ActiveRecord::Coder::JSON.new(symbolize_names: true)"><pre><span class="pl-en">serialize</span> <span class="pl-pds">:config</span><span class="pl-kos">,</span> <span class="pl-pds">coder</span>: <span class="pl-v">ActiveRecord</span>::<span class="pl-v">Coder</span>::<span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-pds">symbolize_names</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span></pre></div>
<p><em>matthaigh27</em></p>
</li>
<li>
<p>Deprecate using <code>insert_all</code>/<code>upsert_all</code> with unpersisted records in associations.</p>
<p>Using these methods on associations containing unpersisted records will now<br>
show a deprecation warning, as the unpersisted records will be lost after<br>
the operation.</p>
<p><em>Nick Schwaderer</em></p>
</li>
<li>
<p>Make column name optional for <code>index_exists?</code>.</p>
<p>This aligns well with <code>remove_index</code> signature as well, where<br>
index name doesn't need to be derived from the column names.</p>
<p><em>Ali Ismayiliov</em></p>
</li>
<li>
<p>Change the payload name of <code>sql.active_record</code> notification for eager<br>
loading from "SQL" to "#{model.name} Eager Load".</p>
<p><em>zzak</em></p>
</li>
<li>
<p>Enable automatically retrying idempotent <code>#exists?</code> queries on connection<br>
errors.</p>
<p><em>Hartley McGuire</em>, <em>classidied</em></p>
</li>
<li>
<p>Deprecate usage of unsupported methods in conjunction with <code>update_all</code>:</p>
<p><code>update_all</code> will now print a deprecation message if a query includes either <code>WITH</code>,<br>
<code>WITH RECURSIVE</code> or <code>DISTINCT</code> statements. Those were never supported and were ignored<br>
when generating the SQL query.</p>
<p>An error will be raised in a future Rails release. This behavior will be consistent<br>
with <code>delete_all</code> which currently raises an error for unsupported statements.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>The table columns inside <code>schema.rb</code> are now sorted alphabetically.</p>
<p>Previously they'd be sorted by creation order, which can cause merge conflicts when two<br>
branches modify the same table concurrently.</p>
<p><em>John Duff</em></p>
</li>
<li>
<p>Introduce versions formatter for the schema dumper.</p>
<p>It is now possible to override how schema dumper formats versions information inside the<br>
<code>structure.sql</code> file. Currently, the versions are simply sorted in the decreasing order.<br>
Within large teams, this can potentially cause many merge conflicts near the top of the list.</p>
<p>Now, the custom formatter can be provided with a custom sorting logic (e.g. by hash values<br>
of the versions), which can greatly reduce the number of conflicts.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Serialized attributes can now be marked as comparable.</p>
<p>A not rare issue when working with serialized attributes is that the serialized representation of an object<br>
can change over time. Either because you are migrating from one serializer to the other (e.g. YAML to JSON or to msgpack),<br>
or because the serializer used subtly changed its output.</p>
<p>One example is libyaml that used to have some extra trailing whitespaces, and recently fixed that.<br>
When this sorts of thing happen, you end up with lots of records that report being changed even though<br>
they aren't, which in the best case leads to a lot more writes to the database and in the worst case lead to nasty bugs.</p>
<p>The solution is to instead compare the deserialized representation of the object, however Active Record<br>
can't assume the deserialized object has a working <code>==</code> method. Hence why this new functionality is opt-in.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="serialize :config, type: Hash, coder: JSON, comparable: true"><pre><span class="pl-en">serialize</span> <span class="pl-pds">:config</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-v">Hash</span><span class="pl-kos">,</span> <span class="pl-pds">coder</span>: <span class="pl-c1">JSON</span><span class="pl-kos">,</span> <span class="pl-pds">comparable</span>: <span class="pl-c1">true</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix MySQL default functions getting dropped when changing a column's nullability.</p>
<p><em>Bastian Bartmann</em></p>
</li>
<li>
<p>SQLite extensions can be configured in <code>config/database.yml</code>.</p>
<p>The database configuration option <code>extensions:</code> allows an application to load SQLite extensions<br>
when using <code>sqlite3</code> >= v2.4.0. The array members may be filesystem paths or the names of<br>
modules that respond to <code>.to_path</code>:</p>
<div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="development:
adapter: sqlite3
extensions:
- SQLean::UUID # module name responding to `.to_path`
- .sqlpkg/nalgeon/crypto/crypto.so # or a filesystem path
- <%= AppExtensions.location %> # or ruby code returning a path"><pre><span class="pl-ent">development</span>:
<span class="pl-ent">adapter</span>: <span class="pl-s">sqlite3</span>
<span class="pl-ent">extensions</span>:
- <span class="pl-s">SQLean::UUID </span><span class="pl-c"><span class="pl-c">#</span> module name responding to `.to_path`</span>
- <span class="pl-s">.sqlpkg/nalgeon/crypto/crypto.so </span><span class="pl-c"><span class="pl-c">#</span> or a filesystem path</span>
- <span class="pl-s"><%= AppExtensions.location %> </span><span class="pl-c"><span class="pl-c">#</span> or ruby code returning a path</span></pre></div>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p><code>ActiveRecord::Middleware::ShardSelector</code> supports granular database connection switching.</p>
<p>A new configuration option, <code>class_name:</code>, is introduced to<br>
<code>config.active_record.shard_selector</code> to allow an application to specify the abstract connection<br>
class to be switched by the shard selection middleware. The default class is<br>
<code>ActiveRecord::Base</code>.</p>
<p>For example, this configuration tells <code>ShardSelector</code> to switch shards using<br>
<code>AnimalsRecord.connected_to</code>:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.active_record.shard_selector = { class_name: "AnimalsRecord" }"><pre class="notranslate"><code>config.active_record.shard_selector = { class_name: "AnimalsRecord" }
</code></pre></div>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Reset relations after <code>insert_all</code>/<code>upsert_all</code>.</p>
<p>Bulk insert/upsert methods will now call <code>reset</code> if used on a relation, matching the behavior of <code>update_all</code>.</p>
<p><em>Milo Winningham</em></p>
</li>
<li>
<p>Use <code>_N</code> as a parallel tests databases suffixes</p>
<p>Peviously, <code>-N</code> was used as a suffix. This can cause problems for RDBMSes<br>
which do not support dashes in database names.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Remember when a database connection has recently been verified (for<br>
two seconds, by default), to avoid repeated reverifications during a<br>
single request.</p>
<p>This should recreate a similar rate of verification as in Rails 7.1,<br>
where connections are leased for the duration of a request, and thus<br>
only verified once.</p>
<p><em>Matthew Draper</em></p>
</li>
<li>
<p>Allow to reset cache counters for multiple records.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Aircraft.reset_counters([1, 2, 3], :wheels_count)"><pre class="notranslate"><code>Aircraft.reset_counters([1, 2, 3], :wheels_count)
</code></pre></div>
<p>It produces much fewer queries compared to the custom implementation using looping over ids.<br>
Previously: <code>O(ids.size * counters.size)</code> queries, now: <code>O(ids.size + counters.size)</code> queries.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Add <code>affected_rows</code> to <code>sql.active_record</code> Notification.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix <code>sum</code> when performing a grouped calculation.</p>
<p><code>User.group(:friendly).sum</code> no longer worked. This is fixed.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Add support for enabling or disabling transactional tests per database.</p>
<p>A test class can now override the default <code>use_transactional_tests</code> setting<br>
for individual databases, which can be useful if some databases need their<br>
current state to be accessible to an external process while tests are running.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class MostlyTransactionalTest < ActiveSupport::TestCase
self.use_transactional_tests = true
skip_transactional_tests_for_database :shared
end"><pre><span class="pl-k">class</span> <span class="pl-v">MostlyTransactionalTest</span> < <span class="pl-v">ActiveSupport</span>::<span class="pl-v">TestCase</span>
<span class="pl-smi">self</span><span class="pl-kos">.</span><span class="pl-en">use_transactional_tests</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span>
<span class="pl-en">skip_transactional_tests_for_database</span> <span class="pl-pds">:shared</span>
<span class="pl-k">end</span></pre></div>
<p><em>Matthew Cheetham</em>, <em>Morgan Mareve</em></p>
</li>
<li>
<p>Cast <code>query_cache</code> value when using URL configuration.</p>
<p><em>zzak</em></p>
</li>
<li>
<p>NULLS NOT DISTINCT works with UNIQUE CONSTRAINT as well as UNIQUE INDEX.</p>
<p><em>Ryuta Kamizono</em></p>
</li>
<li>
<p><code>PG::UnableToSend: no connection to the server</code> is now retryable as a connection-related exception</p>
<p><em>Kazuma Watanabe</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>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.</p>
<p>Before:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --><p>This is grand!</p>"><pre class="notranslate"><code><!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --><p>This is grand!</p>
</code></pre></div>
<p>After:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb
--><p>This is grand!</p>"><pre class="notranslate"><code><!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb
--><p>This is grand!</p>
</code></pre></div>
<p><em>Emmanuel Hayford</em></p>
</li>
<li>
<p>Add structured events for Action View:</p>
<ul>
<li><code>action_view.render_template</code></li>
<li><code>action_view.render_partial</code></li>
<li><code>action_view.render_layout</code></li>
<li><code>action_view.render_collection</code></li>
<li><code>action_view.render_start</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Fix label with <code>for</code> option not getting prefixed by form <code>namespace</code> value</p>
<p><em>Abeid Ahmed</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Add <code>fetchpriority</code> to Link headers to match HTML generated by <code>preload_link_tag</code>.</p>
<p><em>Guillermo Iguaran</em></p>
</li>
<li>
<p>Add CSP <code>nonce</code> to Link headers generated by <code>preload_link_tag</code>.</p>
<p><em>Alexander Gitter</em></p>
</li>
<li>
<p>Allow <code>current_page?</code> to match against specific HTTP method(s) with a <code>method:</code> option.</p>
<p><em>Ben Sheldon</em></p>
</li>
<li>
<p>Remove <code>autocomplete="off"</code> on hidden inputs generated by the following<br>
tags:</p>
<ul>
<li><code>form_tag</code>, <code>token_tag</code>, <code>method_tag</code></li>
</ul>
<p>As well as the hidden parameter fields included in <code>button_to</code>,<br>
<code>check_box</code>, <code>select</code> (with <code>multiple</code>) and <code>file_field</code> forms.</p>
<p><em>nkulway</em></p>
</li>
<li>
<p>Enable configuring the strategy for tracking dependencies between Action<br>
View templates.</p>
<p>The existing <code>:regex</code> strategy is kept as the default, but with<br>
<code>load_defaults 8.1</code> the strategy will be <code>:ruby</code> (using a real Ruby parser).</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Introduce <code>relative_time_in_words</code> helper</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="relative_time_in_words(3.minutes.from_now) # => "in 3 minutes"
relative_time_in_words(3.minutes.ago) # => "3 minutes ago"
relative_time_in_words(10.seconds.ago, include_seconds: true) # => "less than 10 seconds ago""><pre><span class="pl-en">relative_time_in_words</span><span class="pl-kos">(</span><span class="pl-c1">3</span><span class="pl-kos">.</span><span class="pl-en">minutes</span><span class="pl-kos">.</span><span class="pl-en">from_now</span><span class="pl-kos">)</span> <span class="pl-c"># => "in 3 minutes"</span>
<span class="pl-en">relative_time_in_words</span><span class="pl-kos">(</span><span class="pl-c1">3</span><span class="pl-kos">.</span><span class="pl-en">minutes</span><span class="pl-kos">.</span><span class="pl-en">ago</span><span class="pl-kos">)</span> <span class="pl-c"># => "3 minutes ago"</span>
<span class="pl-en">relative_time_in_words</span><span class="pl-kos">(</span><span class="pl-c1">10</span><span class="pl-kos">.</span><span class="pl-en">seconds</span><span class="pl-kos">.</span><span class="pl-en">ago</span><span class="pl-kos">,</span> <span class="pl-pds">include_seconds</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span> <span class="pl-c"># => "less than 10 seconds ago"</span></pre></div>
<p><em>Matheus Richard</em></p>
</li>
<li>
<p>Make <code>nonce: false</code> remove the nonce attribute from <code>javascript_tag</code>, <code>javascript_include_tag</code>, and <code>stylesheet_link_tag</code>.</p>
<p><em>francktrouillez</em></p>
</li>
<li>
<p>Add <code>dom_target</code> helper to create <code>dom_id</code>-like strings from an unlimited<br>
number of objects.</p>
<p><em>Ben Sheldon</em></p>
</li>
<li>
<p>Respect <code>html_options[:form]</code> when <code>collection_checkboxes</code> generates the<br>
hidden <code><input></code>.</p>
<p><em>Riccardo Odone</em></p>
</li>
<li>
<p>Layouts have access to local variables passed to <code>render</code>.</p>
<p>This fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="287923807" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/31680" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/31680/hovercard" href="https://github.com/rails/rails/issues/31680">#31680</a> which was a regression in Rails 5.1.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Argument errors related to strict locals in templates now raise an<br>
<code>ActionView::StrictLocalsError</code>, and all other argument errors are reraised as-is.</p>
<p>Previously, any <code>ArgumentError</code> raised during template rendering was swallowed during strict<br>
local error handling, so that an <code>ArgumentError</code> unrelated to strict locals (e.g., a helper<br>
method invoked with incorrect arguments) would be replaced by a similar <code>ArgumentError</code> with an<br>
unrelated backtrace, making it difficult to debug templates.</p>
<p>Now, any <code>ArgumentError</code> unrelated to strict locals is reraised, preserving the original<br>
backtrace for developers.</p>
<p>Also note that <code>ActionView::StrictLocalsError</code> is a subclass of <code>ArgumentError</code>, so any existing<br>
code that rescues <code>ArgumentError</code> will continue to work.</p>
<p>Fixes <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2378084863" data-permission-text="Title is private" data-url="https://github.com/rails/rails/issues/52227" data-hovercard-type="issue" data-hovercard-url="/rails/rails/issues/52227/hovercard" href="https://github.com/rails/rails/issues/52227">#52227</a>.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Improve error highlighting of multi-line methods in ERB templates or<br>
templates where the error occurs within a do-end block.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Fix a crash in ERB template error highlighting when the error occurs on a<br>
line in the compiled template that is past the end of the source template.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Improve reliability of ERB template error highlighting.<br>
Fix infinite loops and crashes in highlighting and<br>
improve tolerance for alternate ERB handlers.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Allow <code>hidden_field</code> and <code>hidden_field_tag</code> to accept a custom autocomplete value.</p>
<p><em>brendon</em></p>
</li>
<li>
<p>Add a new configuration <code>content_security_policy_nonce_auto</code> for automatically adding a nonce to the tags affected by the directives specified by the <code>content_security_policy_nonce_directives</code> configuration option.</p>
<p><em>francktrouillez</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Submit test requests using <code>as: :html</code> with <code>Content-Type: x-www-form-urlencoded</code></p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Add link-local IP ranges to <code>ActionDispatch::RemoteIp</code> default proxies.</p>
<p>Link-local addresses (<code>169.254.0.0/16</code> for IPv4 and <code>fe80::/10</code> for IPv6)<br>
are now included in the default trusted proxy list, similar to private IP ranges.</p>
<p><em>Adam Daniels</em></p>
</li>
<li>
<p><code>remote_ip</code> will no longer ignore IPs in X-Forwarded-For headers if they<br>
are accompanied by port information.</p>
<p><em>Duncan Brown</em>, <em>Prevenios Marinos</em>, <em>Masafumi Koba</em>, <em>Adam Daniels</em></p>
</li>
<li>
<p>Add <code>action_dispatch.verbose_redirect_logs</code> setting that logs where redirects were called from.</p>
<p>Similar to <code>active_record.verbose_query_logs</code> and <code>active_job.verbose_enqueue_logs</code>, this adds a line in your logs that shows where a redirect was called from.</p>
<p>Example:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Redirected to http://localhost:3000/posts/1
↳ app/controllers/posts_controller.rb:32:in `block (2 levels) in create'"><pre class="notranslate"><code>Redirected to http://localhost:3000/posts/1
↳ app/controllers/posts_controller.rb:32:in `block (2 levels) in create'
</code></pre></div>
<p><em>Dennis Paagman</em></p>
</li>
<li>
<p>Add engine route filtering and better formatting in <code>bin/rails routes</code>.</p>
<p>Allow engine routes to be filterable in the routing inspector, and<br>
improve formatting of engine routing output.</p>
<p>Before:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="> bin/rails routes -e engine_only
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."><pre class="notranslate"><code>> bin/rails routes -e engine_only
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
</code></pre></div>
<p>After:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="> bin/rails routes -e engine_only
Routes for application:
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
Routes for Test::Engine:
Prefix Verb URI Pattern Controller#Action
engine GET /engine_only(.:format) a#b"><pre class="notranslate"><code>> bin/rails routes -e engine_only
Routes for application:
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
Routes for Test::Engine:
Prefix Verb URI Pattern Controller#Action
engine GET /engine_only(.:format) a#b
</code></pre></div>
<p><em>Dennis Paagman</em>, <em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add structured events for Action Pack and Action Dispatch:</p>
<ul>
<li><code>action_dispatch.redirect</code></li>
<li><code>action_controller.request_started</code></li>
<li><code>action_controller.request_completed</code></li>
<li><code>action_controller.callback_halted</code></li>
<li><code>action_controller.rescue_from_handled</code></li>
<li><code>action_controller.file_sent</code></li>
<li><code>action_controller.redirected</code></li>
<li><code>action_controller.data_sent</code></li>
<li><code>action_controller.unpermitted_parameters</code></li>
<li><code>action_controller.fragment_cache</code></li>
</ul>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p>URL helpers for engines mounted at the application root handle <code>SCRIPT_NAME</code> correctly.</p>
<p>Fixed an issue where <code>SCRIPT_NAME</code> is not applied to paths generated for routes in an engine<br>
mounted at "/".</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Update <code>ActionController::Metal::RateLimiting</code> to support passing method names to <code>:by</code> and <code>:with</code></p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class SignupsController < ApplicationController
rate_limit to: 10, within: 1.minute, with: :redirect_with_flash
private
def redirect_with_flash
redirect_to root_url, alert: "Too many requests!"
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">SignupsController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-en">rate_limit</span> <span class="pl-pds">to</span>: <span class="pl-c1">10</span><span class="pl-kos">,</span> <span class="pl-pds">within</span>: <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">minute</span><span class="pl-kos">,</span> <span class="pl-pds">with</span>: <span class="pl-pds">:redirect_with_flash</span>
<span class="pl-k">private</span>
<span class="pl-k">def</span> <span class="pl-en">redirect_with_flash</span>
<span class="pl-en">redirect_to</span> <span class="pl-en">root_url</span><span class="pl-kos">,</span> <span class="pl-pds">alert</span>: <span class="pl-s">"Too many requests!"</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Optimize <code>ActionDispatch::Http::URL.build_host_url</code> when protocol is included in host.</p>
<p>When using URL helpers with a host that includes the protocol (e.g., <code>{ host: "https://example.com" }</code>),<br>
skip unnecessary protocol normalization and string duplication since the extracted protocol is already<br>
in the correct format. This eliminates 2 string allocations per URL generation and provides a ~10%<br>
performance improvement for this case.</p>
<p><em>Joshua Young</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Allow <code>action_controller.logger</code> to be disabled by setting it to <code>nil</code> or <code>false</code> instead of always defaulting to <code>Rails.logger</code>.</p>
<p><em>Roberto Miranda</em></p>
</li>
<li>
<p>Remove deprecated support to a route to multiple paths.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated support for using semicolons as a query string separator.</p>
<p>Before:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActionDispatch::QueryParser.each_pair("foo=bar;baz=quux").to_a
# => [["foo", "bar"], ["baz", "quux"]]"><pre><span class="pl-v">ActionDispatch</span>::<span class="pl-v">QueryParser</span><span class="pl-kos">.</span><span class="pl-en">each_pair</span><span class="pl-kos">(</span><span class="pl-s">"foo=bar;baz=quux"</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to_a</span>
<span class="pl-c"># => [["foo", "bar"], ["baz", "quux"]]</span></pre></div>
<p>After:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActionDispatch::QueryParser.each_pair("foo=bar;baz=quux").to_a
# => [["foo", "bar;baz=quux"]]"><pre><span class="pl-v">ActionDispatch</span>::<span class="pl-v">QueryParser</span><span class="pl-kos">.</span><span class="pl-en">each_pair</span><span class="pl-kos">(</span><span class="pl-s">"foo=bar;baz=quux"</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to_a</span>
<span class="pl-c"># => [["foo", "bar;baz=quux"]]</span></pre></div>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated support to skipping over leading brackets in parameter names in the parameter parser.</p>
<p>Before:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActionDispatch::ParamBuilder.from_query_string("[foo]=bar") # => { "foo" => "bar" }
ActionDispatch::ParamBuilder.from_query_string("[foo][bar]=baz") # => { "foo" => { "bar" => "baz" } }"><pre><span class="pl-v">ActionDispatch</span>::<span class="pl-v">ParamBuilder</span><span class="pl-kos">.</span><span class="pl-en">from_query_string</span><span class="pl-kos">(</span><span class="pl-s">"[foo]=bar"</span><span class="pl-kos">)</span> <span class="pl-c"># => { "foo" => "bar" }</span>
<span class="pl-v">ActionDispatch</span>::<span class="pl-v">ParamBuilder</span><span class="pl-kos">.</span><span class="pl-en">from_query_string</span><span class="pl-kos">(</span><span class="pl-s">"[foo][bar]=baz"</span><span class="pl-kos">)</span> <span class="pl-c"># => { "foo" => { "bar" => "baz" } }</span></pre></div>
<p>After:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActionDispatch::ParamBuilder.from_query_string("[foo]=bar") # => { "[foo]" => "bar" }
ActionDispatch::ParamBuilder.from_query_string("[foo][bar]=baz") # => { "[foo]" => { "bar" => "baz" } }"><pre><span class="pl-v">ActionDispatch</span>::<span class="pl-v">ParamBuilder</span><span class="pl-kos">.</span><span class="pl-en">from_query_string</span><span class="pl-kos">(</span><span class="pl-s">"[foo]=bar"</span><span class="pl-kos">)</span> <span class="pl-c"># => { "[foo]" => "bar" }</span>
<span class="pl-v">ActionDispatch</span>::<span class="pl-v">ParamBuilder</span><span class="pl-kos">.</span><span class="pl-en">from_query_string</span><span class="pl-kos">(</span><span class="pl-s">"[foo][bar]=baz"</span><span class="pl-kos">)</span> <span class="pl-c"># => { "[foo]" => { "bar" => "baz" } }</span></pre></div>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Deprecate <code>Rails.application.config.action_dispatch.ignore_leading_brackets</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Raise <code>ActionController::TooManyRequests</code> error from <code>ActionController::RateLimiting</code></p>
<p>Requests that exceed the rate limit raise an <code>ActionController::TooManyRequests</code> error.<br>
By default, Action Dispatch rescues the error and responds with a <code>429 Too Many Requests</code> status.</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Add .md/.markdown as Markdown extensions and add a default <code>markdown:</code> renderer:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class Page
def to_markdown
body
end
end
class PagesController < ActionController::Base
def show
@page = Page.find(params[:id])
respond_to do |format|
format.html
format.md { render markdown: @page }
end
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">Page</span>
<span class="pl-k">def</span> <span class="pl-en">to_markdown</span>
<span class="pl-en">body</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-k">class</span> <span class="pl-v">PagesController</span> < <span class="pl-v">ActionController</span>::<span class="pl-v">Base</span>
<span class="pl-k">def</span> <span class="pl-en">show</span>
<span class="pl-c1">@page</span> <span class="pl-c1">=</span> <span class="pl-v">Page</span><span class="pl-kos">.</span><span class="pl-en">find</span><span class="pl-kos">(</span><span class="pl-en">params</span><span class="pl-kos">[</span><span class="pl-pds">:id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-en">respond_to</span> <span class="pl-k">do</span> |<span class="pl-s1">format</span>|
<span class="pl-s1">format</span><span class="pl-kos">.</span><span class="pl-en">html</span>
<span class="pl-s1">format</span><span class="pl-kos">.</span><span class="pl-en">md</span> <span class="pl-kos">{</span> <span class="pl-en">render</span> <span class="pl-pds">markdown</span>: <span class="pl-c1">@page</span> <span class="pl-kos">}</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>DHH</em></p>
</li>
<li>
<p>Add headers to engine routes inspection command</p>
<p><em>Petrik de Heus</em></p>
</li>
<li>
<p>Add "Copy as text" button to error pages</p>
<p><em>Mikkel Malmberg</em></p>
</li>
<li>
<p>Add <code>scope:</code> option to <code>rate_limit</code> method.</p>
<p>Previously, it was not possible to share a rate limit count between several controllers, since the count was by<br>
default separate for each controller.</p>
<p>Now, the <code>scope:</code> option solves this problem.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class APIController < ActionController::API
rate_limit to: 2, within: 2.seconds, scope: "api"
end
class API::PostsController < APIController
# ...
end
class API::UsersController < APIController
# ...
end"><pre><span class="pl-k">class</span> <span class="pl-v">APIController</span> < <span class="pl-v">ActionController</span>::<span class="pl-c1">API</span>
<span class="pl-en">rate_limit</span> <span class="pl-pds">to</span>: <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-pds">within</span>: <span class="pl-c1">2</span><span class="pl-kos">.</span><span class="pl-en">seconds</span><span class="pl-kos">,</span> <span class="pl-pds">scope</span>: <span class="pl-s">"api"</span>
<span class="pl-k">end</span>
<span class="pl-k">class</span> <span class="pl-c1">API</span>::<span class="pl-v">PostsController</span> < <span class="pl-v">APIController</span>
<span class="pl-c"># ...</span>
<span class="pl-k">end</span>
<span class="pl-k">class</span> <span class="pl-c1">API</span>::<span class="pl-v">UsersController</span> < <span class="pl-v">APIController</span>
<span class="pl-c"># ...</span>
<span class="pl-k">end</span></pre></div>
<p><em>ArthurPV</em>, <em>Kamil Hanus</em></p>
</li>
<li>
<p>Add support for <code>rack.response_finished</code> callbacks in ActionDispatch::Executor.</p>
<p>The executor middleware now supports deferring completion callbacks to later<br>
in the request lifecycle by utilizing Rack's <code>rack.response_finished</code> mechanism,<br>
when available. This enables applications to define <code>rack.response_finished</code> callbacks<br>
that may rely on state that would be cleaned up by the executor's completion callbacks.</p>
<p><em>Adrianna Chang</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Produce a log when <code>rescue_from</code> is invoked.</p>
<p><em>Steven Webb</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Allow hosts redirects from <code>hosts</code> Rails configuration</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.action_controller.allowed_redirect_hosts << "example.com""><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">action_controller</span><span class="pl-kos">.</span><span class="pl-en">allowed_redirect_hosts</span> << <span class="pl-s">"example.com"</span></pre></div>
<p><em>Kevin Robatel</em></p>
</li>
<li>
<p><code>rate_limit.action_controller</code> notification has additional payload</p>
<p>additional values: count, to, within, by, name, cache_key</p>
<p><em>Jonathan Rochkind</em></p>
</li>
<li>
<p>Add JSON support to the built-in health controller.</p>
<p>The health controller now responds to JSON requests with a structured response<br>
containing status and timestamp information. This makes it easier for monitoring<br>
tools and load balancers to consume health check data programmatically.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# /up.json
{
"status": "up",
"timestamp": "2025-09-19T12:00:00Z"
}"><pre><span class="pl-c"># /up.json</span>
<span class="pl-kos">{</span>
<span class="pl-s">"status"</span>: <span class="pl-s">"up"</span><span class="pl-kos">,</span>
<span class="pl-s">"timestamp"</span>: <span class="pl-s">"2025-09-19T12:00:00Z"</span>
<span class="pl-kos">}</span></pre></div>
<p><em>Francesco Loreti</em>, <em>Juan Vásquez</em></p>
</li>
<li>
<p>Allow to open source file with a crash from the browser.</p>
<p><em>Igor Kasyanchuk</em></p>
</li>
<li>
<p>Always check query string keys for valid encoding just like values are checked.</p>
<p><em>Casper Smits</em></p>
</li>
<li>
<p>Always return empty body for HEAD requests in <code>PublicExceptions</code> and<br>
<code>DebugExceptions</code>.</p>
<p>This is required by <code>Rack::Lint</code> (per RFC9110).</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Add comprehensive support for HTTP Cache-Control request directives according to RFC 9111.</p>
<p>Provides a <code>request.cache_control_directives</code> object that gives access to request cache directives:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Boolean directives
request.cache_control_directives.only_if_cached? # => true/false
request.cache_control_directives.no_cache? # => true/false
request.cache_control_directives.no_store? # => true/false
request.cache_control_directives.no_transform? # => true/false
# Value directives
request.cache_control_directives.max_age # => integer or nil
request.cache_control_directives.max_stale # => integer or nil (or true for valueless max-stale)
request.cache_control_directives.min_fresh # => integer or nil
request.cache_control_directives.stale_if_error # => integer or nil
# Special helpers for max-stale
request.cache_control_directives.max_stale? # => true if max-stale present (with or without value)
request.cache_control_directives.max_stale_unlimited? # => true only for valueless max-stale"><pre><span class="pl-c"># Boolean directives</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">only_if_cached?</span> <span class="pl-c"># => true/false</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">no_cache?</span> <span class="pl-c"># => true/false</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">no_store?</span> <span class="pl-c"># => true/false</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">no_transform?</span> <span class="pl-c"># => true/false</span>
<span class="pl-c"># Value directives</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">max_age</span> <span class="pl-c"># => integer or nil</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">max_stale</span> <span class="pl-c"># => integer or nil (or true for valueless max-stale)</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">min_fresh</span> <span class="pl-c"># => integer or nil</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">stale_if_error</span> <span class="pl-c"># => integer or nil</span>
<span class="pl-c"># Special helpers for max-stale</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">max_stale?</span> <span class="pl-c"># => true if max-stale present (with or without value)</span>
<span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">max_stale_unlimited?</span> <span class="pl-c"># => true only for valueless max-stale</span></pre></div>
<p>Example usage:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def show
if request.cache_control_directives.only_if_cached?
@article = Article.find_cached(params[:id])
return head(:gateway_timeout) if @article.nil?
else
@article = Article.find(params[:id])
end
render :show
end"><pre><span class="pl-k">def</span> <span class="pl-en">show</span>
<span class="pl-k">if</span> <span class="pl-en">request</span><span class="pl-kos">.</span><span class="pl-en">cache_control_directives</span><span class="pl-kos">.</span><span class="pl-en">only_if_cached?</span>
<span class="pl-c1">@article</span> <span class="pl-c1">=</span> <span class="pl-v">Article</span><span class="pl-kos">.</span><span class="pl-en">find_cached</span><span class="pl-kos">(</span><span class="pl-en">params</span><span class="pl-kos">[</span><span class="pl-pds">:id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-k">return</span> <span class="pl-en">head</span><span class="pl-kos">(</span><span class="pl-pds">:gateway_timeout</span><span class="pl-kos">)</span> <span class="pl-k">if</span> <span class="pl-c1">@article</span><span class="pl-kos">.</span><span class="pl-en">nil?</span>
<span class="pl-k">else</span>
<span class="pl-c1">@article</span> <span class="pl-c1">=</span> <span class="pl-v">Article</span><span class="pl-kos">.</span><span class="pl-en">find</span><span class="pl-kos">(</span><span class="pl-en">params</span><span class="pl-kos">[</span><span class="pl-pds">:id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-en">render</span> <span class="pl-pds">:show</span>
<span class="pl-k">end</span></pre></div>
<p><em>egg528</em></p>
</li>
<li>
<p>Add assert_in_body/assert_not_in_body as the simplest way to check if a piece of text is in the response body.</p>
<p><em>DHH</em></p>
</li>
<li>
<p>Include cookie name when calculating maximum allowed size.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Implement <code>must-understand</code> directive according to RFC 9111.</p>
<p>The <code>must-understand</code> directive indicates that a cache must understand the semantics of the response status code, or discard the response. This directive is enforced to be used only with <code>no-store</code> to ensure proper cache behavior.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
if @article.special_format?
must_understand
render status: 203 # Non-Authoritative Information
else
fresh_when @article
end
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">ArticlesController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-k">def</span> <span class="pl-en">show</span>
<span class="pl-c1">@article</span> <span class="pl-c1">=</span> <span class="pl-v">Article</span><span class="pl-kos">.</span><span class="pl-en">find</span><span class="pl-kos">(</span><span class="pl-en">params</span><span class="pl-kos">[</span><span class="pl-pds">:id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-k">if</span> <span class="pl-c1">@article</span><span class="pl-kos">.</span><span class="pl-en">special_format?</span>
<span class="pl-en">must_understand</span>
<span class="pl-en">render</span> <span class="pl-pds">status</span>: <span class="pl-c1">203</span> <span class="pl-c"># Non-Authoritative Information</span>
<span class="pl-k">else</span>
<span class="pl-en">fresh_when</span> <span class="pl-c1">@article</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>heka1024</em></p>
</li>
<li>
<p>The JSON renderer doesn't escape HTML entities or Unicode line separators anymore.</p>
<p>Using <code>render json:</code> will no longer escape <code><</code>, <code>></code>, <code>&</code>, <code>U+2028</code> and <code>U+2029</code> characters that can cause errors<br>
when the resulting JSON is embedded in JavaScript, or vulnerabilities when the resulting JSON is embedded in HTML.</p>
<p>Since the renderer is used to return a JSON document as <code>application/json</code>, it's typically not necessary to escape<br>
those characters, and it improves performance.</p>
<p>Escaping will still occur when the <code>:callback</code> option is set, since the JSON is used as JavaScript code in this<br>
situation (JSONP).</p>
<p>You can use the <code>:escape</code> option or set <code>config.action_controller.escape_json_responses</code> to <code>true</code> to restore the<br>
escaping behavior.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class PostsController < ApplicationController
def index
render json: Post.last(30), escape: true
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">PostsController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-k">def</span> <span class="pl-en">index</span>
<span class="pl-en">render</span> <span class="pl-pds">json</span>: <span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">last</span><span class="pl-kos">(</span><span class="pl-c1">30</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-pds">escape</span>: <span class="pl-c1">true</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Étienne Barrié</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Load lazy route sets before inserting test routes</p>
<p>Without loading lazy route sets early, we miss <code>after_routes_loaded</code> callbacks, or risk<br>
invoking them with the test routes instead of the real ones if another load is triggered by an engine.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Raise <code>AbstractController::DoubleRenderError</code> if <code>head</code> is called after rendering.</p>
<p>After this change, invoking <code>head</code> will lead to an error if response body is already set:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class PostController < ApplicationController
def index
render locals: {}
head :ok
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">PostController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-k">def</span> <span class="pl-en">index</span>
<span class="pl-en">render</span> <span class="pl-pds">locals</span>: <span class="pl-kos">{</span><span class="pl-kos">}</span>
<span class="pl-en">head</span> <span class="pl-pds">:ok</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Iaroslav Kurbatov</em></p>
</li>
<li>
<p>The Cookie Serializer can now serialize an Active Support SafeBuffer when using message pack.</p>
<p>Such code would previously produce an error if an application was using messagepack as its cookie serializer.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class PostController < ApplicationController
def index
flash.notice = t(:hello_html) # This would try to serialize a SafeBuffer, which was not possible.
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">PostController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-k">def</span> <span class="pl-en">index</span>
<span class="pl-en">flash</span><span class="pl-kos">.</span><span class="pl-en">notice</span> <span class="pl-c1">=</span> <span class="pl-en">t</span><span class="pl-kos">(</span><span class="pl-pds">:hello_html</span><span class="pl-kos">)</span> <span class="pl-c"># This would try to serialize a SafeBuffer, which was not possible.</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Fix <code>Rails.application.reload_routes!</code> from clearing almost all routes.</p>
<p>When calling <code>Rails.application.reload_routes!</code> inside a middleware of<br>
a Rake task, it was possible under certain conditions that all routes would be cleared.<br>
If ran inside a middleware, this would result in getting a 404 on most page you visit.<br>
This issue was only happening in development.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Add resource name to the <code>ArgumentError</code> that's raised when invalid <code>:only</code> or <code>:except</code> options are given to <code>#resource</code> or <code>#resources</code></p>
<p>This makes it easier to locate the source of the problem, especially for routes drawn by gems.</p>
<p>Before:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=":only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar]"><pre class="notranslate"><code>:only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar]
</code></pre></div>
<p>After:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Route `resources :products` - :only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar]"><pre class="notranslate"><code>Route `resources :products` - :only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar]
</code></pre></div>
<p><em>Jeremy Green</em></p>
</li>
<li>
<p>A route pointing to a non-existing controller now returns a 500 instead of a 404.</p>
<p>A controller not existing isn't a routing error that should result<br>
in a 404, but a programming error that should result in a 500 and<br>
be reported.</p>
<p>Until recently, this was hard to untangle because of the support<br>
for dynamic <code>:controller</code> segment in routes, but since this is<br>
deprecated and will be removed in Rails 8.1, we can now easily<br>
not consider missing controllers as routing errors.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Add <code>check_collisions</code> option to <code>ActionDispatch::Session::CacheStore</code>.</p>
<p>Newly generated session ids use 128 bits of randomness, which is more than<br>
enough to ensure collisions can't happen, but if you need to harden sessions<br>
even more, you can enable this option to check in the session store that the id<br>
is indeed free you can enable that option. This however incurs an extra write<br>
on session creation.</p>
<p><em>Shia</em></p>
</li>
<li>
<p>In ExceptionWrapper, match backtrace lines with built templates more often,<br>
allowing improved highlighting of errors within do-end blocks in templates.<br>
Fix for Ruby 3.4 to match new method labels in backtrace.</p>
<p><em>Martin Emde</em></p>
</li>
<li>
<p>Allow setting content type with a symbol of the Mime type.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Before
response.content_type = "text/html"
# After
response.content_type = :html"><pre><span class="pl-c"># Before</span>
<span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">content_type</span> <span class="pl-c1">=</span> <span class="pl-s">"text/html"</span>
<span class="pl-c"># After</span>
<span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">content_type</span> <span class="pl-c1">=</span> <span class="pl-pds">:html</span></pre></div>
<p><em>Petrik de Heus</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Add structured events for Active Job:</p>
<ul>
<li><code>active_job.enqueued</code></li>
<li><code>active_job.bulk_enqueued</code></li>
<li><code>active_job.started</code></li>
<li><code>active_job.completed</code></li>
<li><code>active_job.retry_scheduled</code></li>
<li><code>active_job.retry_stopped</code></li>
<li><code>active_job.discarded</code></li>
<li><code>active_job.interrupt</code></li>
<li><code>active_job.resume</code></li>
<li><code>active_job.step_skipped</code></li>
<li><code>active_job.step_started</code></li>
<li><code>active_job.step</code></li>
</ul>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p>Deprecate built-in <code>sidekiq</code> adapter.</p>
<p>If you're using this adapter, upgrade to <code>sidekiq</code> 7.3.3 or later to use the <code>sidekiq</code> gem's adapter.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Remove deprecated internal <code>SuckerPunch</code> adapter in favor of the adapter included with the <code>sucker_punch</code> gem.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove support to set <code>ActiveJob::Base.enqueue_after_transaction_commit</code> to <code>:never</code>, <code>:always</code> and <code>:default</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>Rails.application.config.active_job.enqueue_after_transaction_commit</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p><code>ActiveJob::Serializers::ObjectSerializers#klass</code> method is now public.</p>
<p>Custom Active Job serializers must have a public <code>#klass</code> method too.<br>
The returned class will be index allowing for faster serialization.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Allow jobs to the interrupted and resumed with Continuations</p>
<p>A job can use Continuations by including the <code>ActiveJob::Continuable</code><br>
concern. Continuations split jobs into steps. When the queuing system<br>
is shutting down jobs can be interrupted and their progress saved.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class ProcessImportJob
include ActiveJob::Continuable
def perform(import_id)
@import = Import.find(import_id)
# block format
step :initialize do
@import.initialize
end
# step with cursor, the cursor is saved when the job is interrupted
step :process do |step|
@import.records.find_each(start: step.cursor) do |record|
record.process
step.advance! from: record.id
end
end
# method format
step :finalize
private
def finalize
@import.finalize
end
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">ProcessImportJob</span>
<span class="pl-en">include</span> <span class="pl-v">ActiveJob</span>::<span class="pl-v">Continuable</span>
<span class="pl-k">def</span> <span class="pl-en">perform</span><span class="pl-kos">(</span><span class="pl-s1">import_id</span><span class="pl-kos">)</span>
<span class="pl-c1">@import</span> <span class="pl-c1">=</span> <span class="pl-v">Import</span><span class="pl-kos">.</span><span class="pl-en">find</span><span class="pl-kos">(</span><span class="pl-s1">import_id</span><span class="pl-kos">)</span>
<span class="pl-c"># block format</span>
<span class="pl-en">step</span> <span class="pl-pds">:initialize</span> <span class="pl-k">do</span>
<span class="pl-c1">@import</span><span class="pl-kos">.</span><span class="pl-en">initialize</span>
<span class="pl-k">end</span>
<span class="pl-c"># step with cursor, the cursor is saved when the job is interrupted</span>
<span class="pl-en">step</span> <span class="pl-pds">:process</span> <span class="pl-k">do</span> |<span class="pl-s1">step</span>|
<span class="pl-c1">@import</span><span class="pl-kos">.</span><span class="pl-en">records</span><span class="pl-kos">.</span><span class="pl-en">find_each</span><span class="pl-kos">(</span><span class="pl-pds">start</span>: <span class="pl-s1">step</span><span class="pl-kos">.</span><span class="pl-en">cursor</span><span class="pl-kos">)</span> <span class="pl-k">do</span> |<span class="pl-s1">record</span>|
<span class="pl-s1">record</span><span class="pl-kos">.</span><span class="pl-en">process</span>
<span class="pl-s1">step</span><span class="pl-kos">.</span><span class="pl-en">advance!</span> <span class="pl-pds">from</span>: <span class="pl-s1">record</span><span class="pl-kos">.</span><span class="pl-en">id</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-c"># method format</span>
<span class="pl-en">step</span> <span class="pl-pds">:finalize</span>
<span class="pl-k">private</span>
<span class="pl-k">def</span> <span class="pl-en">finalize</span>
<span class="pl-c1">@import</span><span class="pl-kos">.</span><span class="pl-en">finalize</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Donal McBreen</em></p>
</li>
<li>
<p>Defer invocation of ActiveJob enqueue callbacks until after commit when<br>
<code>enqueue_after_transaction_commit</code> is enabled.</p>
<p><em>Will Roever</em></p>
</li>
<li>
<p>Add <code>report:</code> option to <code>ActiveJob::Base#retry_on</code> and <code>#discard_on</code></p>
<p>When the <code>report:</code> option is passed, errors will be reported to the error reporter<br>
before being retried / discarded.</p>
<p><em>Andrew Novoselac</em></p>
</li>
<li>
<p>Accept a block for <code>ActiveJob::ConfiguredJob#perform_later</code>.</p>
<p>This was inconsistent with a regular <code>ActiveJob::Base#perform_later</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Raise a more specific error during deserialization when a previously serialized job class is now unknown.</p>
<p><code>ActiveJob::UnknownJobClassError</code> will be raised instead of a more generic<br>
<code>NameError</code> to make it easily possible for adapters to tell if the <code>NameError</code><br>
was raised during job execution or deserialization.</p>
<p><em>Earlopain</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>
<p>Add structured events for Action Mailer:</p>
<ul>
<li><code>action_mailer.delivered</code></li>
<li><code>action_mailer.processed</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add <code>deliver_all_later</code> to enqueue multiple emails at once.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="user_emails = User.all.map { |user| Notifier.welcome(user) }
ActionMailer.deliver_all_later(user_emails)
# use a custom queue
ActionMailer.deliver_all_later(user_emails, queue: :my_queue)"><pre><span class="pl-s1">user_emails</span> <span class="pl-c1">=</span> <span class="pl-v">User</span><span class="pl-kos">.</span><span class="pl-en">all</span><span class="pl-kos">.</span><span class="pl-en">map</span> <span class="pl-kos">{</span> |<span class="pl-s1">user</span>| <span class="pl-v">Notifier</span><span class="pl-kos">.</span><span class="pl-en">welcome</span><span class="pl-kos">(</span><span class="pl-s1">user</span><span class="pl-kos">)</span> <span class="pl-kos">}</span>
<span class="pl-v">ActionMailer</span><span class="pl-kos">.</span><span class="pl-en">deliver_all_later</span><span class="pl-kos">(</span><span class="pl-s1">user_emails</span><span class="pl-kos">)</span>
<span class="pl-c"># use a custom queue</span>
<span class="pl-v">ActionMailer</span><span class="pl-kos">.</span><span class="pl-en">deliver_all_later</span><span class="pl-kos">(</span><span class="pl-s1">user_emails</span><span class="pl-kos">,</span> <span class="pl-pds">queue</span>: <span class="pl-pds">:my_queue</span><span class="pl-kos">)</span></pre></div>
<p>This can greatly reduce the number of round-trips to the queue datastore.<br>
For queue adapters that do not implement the <code>enqueue_all</code> method, we<br>
fall back to enqueuing email jobs indvidually.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>
<p>Allow passing composite channels to <code>ActionCable::Channel#stream_for</code> – e.g. <code>stream_for [ group, group.owner ]</code></p>
<p><em>hey-leon</em></p>
</li>
<li>
<p>Allow setting nil as subscription connection identifier for Redis.</p>
<p><em>Nguyen Nguyen</em></p>
</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Add structured events for Active Storage:</p>
<ul>
<li><code>active_storage.service_upload</code></li>
<li><code>active_storage.service_download</code></li>
<li><code>active_storage.service_streaming_download</code></li>
<li><code>active_storage.preview</code></li>
<li><code>active_storage.service_delete</code></li>
<li><code>active_storage.service_delete_prefixed</code></li>
<li><code>active_storage.service_exist</code></li>
<li><code>active_storage.service_url</code></li>
<li><code>active_storage.service_mirror</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Allow analyzers and variant transformer to be fully configurable</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# ActiveStorage.analyzers can be set to an empty array:
config.active_storage.analyzers = []
# => ActiveStorage.analyzers = []
# or use custom analyzer:
config.active_storage.analyzers = [ CustomAnalyzer ]
# => ActiveStorage.analyzers = [ CustomAnalyzer ]"><pre><span class="pl-c"># ActiveStorage.analyzers can be set to an empty array:</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">analyzers</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>
<span class="pl-c"># => ActiveStorage.analyzers = []</span>
<span class="pl-c"># or use custom analyzer:</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">analyzers</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span> <span class="pl-v">CustomAnalyzer</span> <span class="pl-kos">]</span>
<span class="pl-c"># => ActiveStorage.analyzers = [ CustomAnalyzer ]</span></pre></div>
<p>If no configuration is provided, it will use the default analyzers.</p>
<p>You can also disable variant processor to remove warnings on startup about missing gems.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.active_storage.variant_processor = :disabled"><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">variant_processor</span> <span class="pl-c1">=</span> <span class="pl-pds">:disabled</span></pre></div>
<p><em>zzak</em>, <em>Alexandre Ruban</em></p>
</li>
<li>
<p>Remove deprecated <code>:azure</code> storage service.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove unnecessary calls to the GCP metadata server.</p>
<p>Calling Google::Auth.get_application_default triggers an explicit call to<br>
the metadata server - given it was being called for significant number of<br>
file operations, it can lead to considerable tail latencies and even metadata<br>
server overloads. Instead, it's preferable (and significantly more efficient)<br>
that applications use:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Google::Apis::RequestOptions.default.authorization = Google::Auth.get_application_default(...)"><pre><span class="pl-v">Google</span>::<span class="pl-v">Apis</span>::<span class="pl-v">RequestOptions</span><span class="pl-kos">.</span><span class="pl-en">default</span><span class="pl-kos">.</span><span class="pl-en">authorization</span> <span class="pl-c1">=</span> <span class="pl-v">Google</span>::<span class="pl-v">Auth</span><span class="pl-kos">.</span><span class="pl-en">get_application_default</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span></pre></div>
<p>In the cases applications do not set that, the GCP libraries automatically determine credentials.</p>
<p>This also enables using credentials other than those of the associated GCP<br>
service account like when using impersonation.</p>
<p><em>Alex Coomans</em></p>
</li>
<li>
<p>Direct upload progress accounts for server processing time.</p>
<p><em>Jeremy Daer</em></p>
</li>
<li>
<p>Delegate <code>ActiveStorage::Filename#to_str</code> to <code>#to_s</code></p>
<p>Supports checking String equality:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="filename = ActiveStorage::Filename.new("file.txt")
filename == "file.txt" # => true
filename in "file.txt" # => true
"file.txt" == filename # => true"><pre><span class="pl-s1">filename</span> <span class="pl-c1">=</span> <span class="pl-v">ActiveStorage</span>::<span class="pl-v">Filename</span><span class="pl-kos">.</span><span class="pl-en">new</span><span class="pl-kos">(</span><span class="pl-s">"file.txt"</span><span class="pl-kos">)</span>
<span class="pl-s1">filename</span> == <span class="pl-s">"file.txt"</span> <span class="pl-c"># => true</span>
<span class="pl-s1">filename</span> <span class="pl-k">in</span> <span class="pl-s">"file.txt"</span> <span class="pl-c"># => true</span>
<span class="pl-s">"file.txt"</span> == <span class="pl-s1">filename</span> <span class="pl-c"># => true</span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>A Blob will no longer autosave associated Attachment.</p>
<p>This fixes an issue where a record with an attachment would have<br>
its dirty attributes reset, preventing your <code>after commit</code> callbacks<br>
on that record to behave as expected.</p>
<p>Note that this change doesn't require any changes on your application<br>
and is supposed to be internal. Active Storage Attachment will continue<br>
to be autosaved (through a different relation).</p>
<p><em>Edouard-chin</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>
<p>Add <code>reply_to_address</code> extension method on <code>Mail::Message</code>.</p>
<p><em>Mr0grog</em></p>
</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>
<p>De-couple <code>@rails/actiontext/attachment_upload.js</code> from <code>Trix.Attachment</code></p>
<p>Implement <code>@rails/actiontext/index.js</code> with a <code>direct-upload:progress</code> event<br>
listeners and <code>Promise</code> resolution.</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Capture block content for form helper methods</p>
<div class="highlight highlight-text-html-erb notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<%= rich_textarea_tag :content, nil do %>
<h1>hello world</h1>
<% end %>
<!-- <input type="hidden" name="content" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->
<%= rich_textarea :message, :content, input: "trix_input_1" do %>
<h1>hello world</h1>
<% end %>
<!-- <input type="hidden" name="message[content]" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->
<%= form_with model: Message.new do |form| %>
<%= form.rich_textarea :content do %>
<h1>hello world</h1>
<% end %>
<% end %>
<!-- <form action="/messages" accept-charset="UTF-8" method="post"><input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->"><pre><span class="pl-k"><%=</span> rich_textarea_tag :content, nil do <span class="pl-k">%></span>
<span class="pl-kos"><span class="pl-en"></span><span class="pl-pds"></span><span class="pl-kos"></span><span class="pl-c1"></span><span class="pl-k"></span><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <input type="hidden" name="content" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span>
<span class="pl-k"><%=</span> <span class="pl-en">rich_textarea</span> <span class="pl-pds">:message</span><span class="pl-kos">,</span> <span class="pl-pds">:content</span><span class="pl-kos">,</span> <span class="pl-pds">input</span>: <span class="pl-s">"trix_input_1"</span> <span class="pl-k">do</span> <span class="pl-k">%></span>
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <input type="hidden" name="message[content]" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span>
<span class="pl-k"><%=</span> <span class="pl-en">form_with</span> <span class="pl-pds">model</span>: <span class="pl-v">Message</span><span class="pl-kos">.</span><span class="pl-en">new</span> <span class="pl-k">do</span> |<span class="pl-s1">form</span>| <span class="pl-k">%></span>
<span class="pl-k"><%=</span> <span class="pl-s1">form</span><span class="pl-kos">.</span><span class="pl-en">rich_textarea</span> <span class="pl-pds">:content</span> <span class="pl-k">do</span> <span class="pl-k">%></span>
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <form action="/messages" accept-charset="UTF-8" method="post"><input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Generalize <code>:rich_text_area</code> Capybara selector</p>
<p>Prepare for more Action Text-capable WYSIWYG editors by making<br>
<code>:rich_text_area</code> rely on the presence of <code>[role="textbox"]</code> and<br>
<code>[contenteditable]</code> HTML attributes rather than a <code><trix-editor></code> element.</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Forward <code>fill_in_rich_text_area</code> options to Capybara</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="fill_in_rich_textarea "Rich text editor", id: "trix_editor_1", with: "Hello world!""><pre><span class="pl-en">fill_in_rich_textarea</span> <span class="pl-s">"Rich text editor"</span><span class="pl-kos">,</span> <span class="pl-pds">id</span>: <span class="pl-s">"trix_editor_1"</span><span class="pl-kos">,</span> <span class="pl-pds">with</span>: <span class="pl-s">"Hello world!"</span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Attachment upload progress accounts for server processing time.</p>
<p><em>Jeremy Daer</em></p>
</li>
<li>
<p>The Trix dependency is now satisfied by a gem, <code>action_text-trix</code>, rather than vendored<br>
files. This allows applications to bump Trix versions independently of Rails<br>
releases. Effectively this also upgrades Trix to <code>>= 2.1.15</code>.</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Change <code>ActionText::RichText#embeds</code> assignment from <code>before_save</code> to <code>before_validation</code></p>
<p><em>Sean Doyle</em></p>
</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Suggest <code>bin/rails action_text:install</code> from Action Dispatch error page</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Remove deprecated <code>STATS_DIRECTORIES</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>bin/rake stats</code> command.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>rails/console/methods.rb</code> file.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Don't generate system tests by default.</p>
<p>Rails scaffold generator will no longer generate system tests by default. To enable this pass <code>--system-tests=true</code> or generate them with <code>bin/rails generate system_test name_of_test</code>.</p>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Optionally skip bundler-audit.</p>
<p>Skips adding the <code>bin/bundler-audit</code> & <code>config/bundler-audit.yml</code> if the gem is not installed when <code>bin/rails app:update</code> runs.</p>
<p>Passes an option to <code>--skip-bundler-audit</code> when new apps are generated & adds that same option to the <code>--minimal</code> generator flag.</p>
<p><em>Jill Klang</em></p>
</li>
<li>
<p>Show engine routes in <code>/rails/info/routes</code> as well.</p>
<p><em>Petrik de Heus</em></p>
</li>
<li>
<p>Exclude <code>asset_path</code> configuration from Kamal <code>deploy.yml</code> for API applications.</p>
<p>API applications don't serve assets, so the <code>asset_path</code> configuration in <code>deploy.yml</code><br>
is not needed and can cause 404 errors on in-flight requests. The asset_path is now<br>
only included for regular Rails applications that serve assets.</p>
<p><em>Saiqul Haq</em></p>
</li>
<li>
<p>Reverted the incorrect default <code>config.public_file_server.headers</code> config.</p>
<p>If you created a new application using Rails <code>8.1.0.beta1</code>, make sure to regenerate<br>
<code>config/environments/production.rb</code>, or to manually edit the <code>config.public_file_server.headers</code><br>
configuration to just be:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Cache assets for far-future expiry since they are all digest stamped.
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }"><pre><span class="pl-c"># Cache assets for far-future expiry since they are all digest stamped.</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">public_file_server</span><span class="pl-kos">.</span><span class="pl-en">headers</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span> <span class="pl-s">"cache-control"</span> <span class="pl-c1">=></span> <span class="pl-s">"public, max-age=<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">year</span><span class="pl-kos">.</span><span class="pl-en">to_i</span><span class="pl-kos">}</span></span>"</span> <span class="pl-kos">}</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Add command <code>rails credentials:fetch PATH</code> to get the value of a credential from the credentials file.</p>
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ bin/rails credentials:fetch kamal_registry.password"><pre>$ bin/rails credentials:fetch kamal_registry.password</pre></div>
<p><em>Matthew Nguyen</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Generate static BCrypt password digests in fixtures instead of dynamic ERB expressions.</p>
<p>Previously, fixtures with password digest attributes used <code><%= BCrypt::Password.create("secret") %></code>,<br>
which regenerated the hash on each test run. Now generates a static hash with a comment<br>
showing how to recreate it.</p>
<p><em>Nate Smith</em>, <em>Cassia Scheffer</em></p>
</li>
<li>
<p>Broaden the <code>.gitignore</code> entry when adding a credentials key to ignore all key files.</p>
<p><em>Greg Molnar</em></p>
</li>
<li>
<p>Remove unnecessary <code>ruby-version</code> input from <code>ruby/setup-ruby</code></p>
<p><em>TangRufus</em></p>
</li>
<li>
<p>Add --reset option to bin/setup which will call db:reset as part of the setup.</p>
<p><em>DHH</em></p>
</li>
<li>
<p>Add RuboCop cache restoration to RuboCop job in GitHub Actions workflow templates.</p>
<p><em>Lovro Bikić</em></p>
</li>
<li>
<p>Skip generating mailer-related files in authentication generator if the application does<br>
not use ActionMailer</p>
<p><em>Rami Massoud</em></p>
</li>
<li>
<p>Introduce <code>bin/ci</code> for running your tests, style checks, and security audits locally or in the cloud.</p>
<p>The specific steps are defined by a new DSL in <code>config/ci.rb</code>.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ActiveSupport::ContinuousIntegration.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Gem audit", "bin/bundler-audit"
step "Tests: Rails", "bin/rails test test:system"
end"><pre><span class="pl-v">ActiveSupport</span>::<span class="pl-v">ContinuousIntegration</span><span class="pl-kos">.</span><span class="pl-en">run</span> <span class="pl-k">do</span>
<span class="pl-en">step</span> <span class="pl-s">"Setup"</span><span class="pl-kos">,</span> <span class="pl-s">"bin/setup --skip-server"</span>
<span class="pl-en">step</span> <span class="pl-s">"Style: Ruby"</span><span class="pl-kos">,</span> <span class="pl-s">"bin/rubocop"</span>
<span class="pl-en">step</span> <span class="pl-s">"Security: Gem audit"</span><span class="pl-kos">,</span> <span class="pl-s">"bin/bundler-audit"</span>
<span class="pl-en">step</span> <span class="pl-s">"Tests: Rails"</span><span class="pl-kos">,</span> <span class="pl-s">"bin/rails test test:system"</span>
<span class="pl-k">end</span></pre></div>
<p>Optionally use <a href="https://github.com/basecamp/gh-signoff">gh-signoff</a> to<br>
set a green PR status - ready for merge.</p>
<p><em>Jeremy Daer</em>, <em>DHH</em></p>
</li>
<li>
<p>Generate session controller tests when running the authentication generator.</p>
<p><em>Jerome Dalbert</em></p>
</li>
<li>
<p>Add bin/bundler-audit and config/bundler-audit.yml for discovering and managing known security problems with app gems.</p>
<p><em>DHH</em></p>
</li>
<li>
<p>Rails no longer generates a <code>bin/bundle</code> binstub when creating new applications.</p>
<p>The <code>bin/bundle</code> binstub used to help activate the right version of bundler.<br>
This is no longer necessary as this mechanism is now part of Rubygem itself.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Add a <code>SessionTestHelper</code> module with <code>sign_in_as(user)</code> and <code>sign_out</code> test helpers when<br>
running <code>rails g authentication</code>. Simplifies authentication in integration tests.</p>
<p><em>Bijan Rahnema</em></p>
</li>
<li>
<p>Rate limit password resets in authentication generator</p>
<p>This helps mitigate abuse from attackers spamming the password reset form.</p>
<p><em>Chris Oliver</em></p>
</li>
<li>
<p>Update <code>rails new --minimal</code> option</p>
<p>Extend the <code>--minimal</code> flag to exclude recently added features:<br>
<code>skip_brakeman</code>, <code>skip_ci</code>, <code>skip_docker</code>, <code>skip_kamal</code>, <code>skip_rubocop</code>, <code>skip_solid</code> and <code>skip_thruster</code>.</p>
<p><em>eelcoj</em></p>
</li>
<li>
<p>Add <code>application-name</code> metadata to application layout</p>
<p>The following metatag will be added to <code>app/views/layouts/application.html.erb</code></p>
<div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<meta name="application-name" content="Name of Rails Application">"><pre><span class="pl-kos"><</span><span class="pl-ent">meta</span> <span class="pl-c1">name</span>="<span class="pl-s">application-name</span>" <span class="pl-c1">content</span>="<span class="pl-s">Name of Rails Application</span>"<span class="pl-kos">></span></pre></div>
<p><em>Steve Polito</em></p>
</li>
<li>
<p>Use <code>secret_key_base</code> from ENV or credentials when present locally.</p>
<p>When ENV["SECRET_KEY_BASE"] or<br>
<code>Rails.application.credentials.secret_key_base</code> is set for test or<br>
development, it is used for the <code>Rails.config.secret_key_base</code>,<br>
instead of generating a <code>tmp/local_secret.txt</code> file.</p>
<p><em>Petrik de Heus</em></p>
</li>
<li>
<p>Introduce <code>RAILS_MASTER_KEY</code> placeholder in generated ci.yml files</p>
<p><em>Steve Polito</em></p>
</li>
<li>
<p>Colorize the Rails console prompt even on non standard environments.</p>
<p><em>Lorenzo Zabot</em></p>
</li>
<li>
<p>Don't enable YJIT in development and test environments</p>
<p>Development and test environments tend to reload code and redefine methods (e.g. mocking),<br>
hence YJIT isn't generally faster in these environments.</p>
<p><em>Ali Ismayilov</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Only include PermissionsPolicy::Middleware if policy is configured.</p>
<p><em>Petrik de Heus</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>
<p>In the Active Job bug report template set the queue adapter to the<br>
test adapter so that <code>assert_enqueued_with</code> can pass.</p>
<p><em>Andrew White</em></p>
</li>
<li>
<p>Ensure all bug report templates set <code>config.secret_key_base</code> to avoid<br>
generation of <code>tmp/local_secret.txt</code> files when running the report template.</p>
<p><em>Andrew White</em></p>
</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v8.1.0.rc12025-10-15T00:50:43Z8.1.0.rc1<h2>Active Support</h2>
<ul>
<li>
<p>Remove deprecated passing a Time object to <code>Time#since</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>Benchmark.ms</code> method. It is now defined in the <code>benchmark</code> gem.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated addition for <code>Time</code> instances with <code>ActiveSupport::TimeWithZone</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated support for <code>to_time</code> to preserve the system local time. It will now always preserve the receiver<br>
timezone.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Deprecate <code>config.active_support.to_time_preserves_timezone</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Standardize event name formatting in <code>assert_event_reported</code> error messages.</p>
<p>The event name in failure messages now uses <code>.inspect</code> (e.g., <code>name: "user.created"</code>)<br>
to match <code>assert_events_reported</code> and provide type clarity between strings and symbols.<br>
This only affects tests that assert on the failure message format itself.</p>
<p><em>George Ma</em></p>
</li>
<li>
<p>Fix <code>Enumerable#sole</code> to return the full tuple instead of just the first element of the tuple.</p>
<p><em>Olivier Bellone</em></p>
</li>
<li>
<p>Fix parallel tests hanging when worker processes die abruptly.</p>
<p>Previously, if a worker process was killed (e.g., OOM killed, <code>kill -9</code>) during parallel<br>
test execution, the test suite would hang forever waiting for the dead worker.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Add <code>config.active_support.escape_js_separators_in_json</code>.</p>
<p>Introduce a new framework default to skip escaping LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029) in JSON.</p>
<p>Historically these characters were not valid inside JavaScript literal strings but that changed in ECMAScript 2019.<br>
As such it's no longer a concern in modern browsers: <a href="https://caniuse.com/mdn-javascript_builtins_json_json_superset" rel="nofollow">https://caniuse.com/mdn-javascript_builtins_json_json_superset</a>.</p>
<p><em>Étienne Barrié</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>NameError</code> when <code>class_attribute</code> is defined on instance singleton classes.</p>
<p>Previously, calling <code>class_attribute</code> on an instance's singleton class would raise<br>
a <code>NameError</code> when accessing the attribute through the instance.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="object = MyClass.new
object.singleton_class.class_attribute :foo, default: "bar"
object.foo # previously raised NameError, now returns "bar""><pre><span class="pl-s1">object</span> <span class="pl-c1">=</span> <span class="pl-v">MyClass</span><span class="pl-kos">.</span><span class="pl-en">new</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">singleton_class</span><span class="pl-kos">.</span><span class="pl-en">class_attribute</span> <span class="pl-pds">:foo</span><span class="pl-kos">,</span> <span class="pl-pds">default</span>: <span class="pl-s">"bar"</span>
<span class="pl-s1">object</span><span class="pl-kos">.</span><span class="pl-en">foo</span> <span class="pl-c"># previously raised NameError, now returns "bar"</span></pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Introduce <code>ActiveSupport::Testing::EventReporterAssertions#with_debug_event_reporting</code><br>
to enable event reporter debug mode in tests.</p>
<p>The previous way to enable debug mode is by using <code>#with_debug</code> on the<br>
event reporter itself, which is too verbose. This new helper will help<br>
clear up any confusion on how to test debug events.</p>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add <code>ActiveSupport::StructuredEventSubscriber</code> for consuming notifications and<br>
emitting structured event logs. Events may be emitted with the <code>#emit_event</code><br>
or <code>#emit_debug_event</code> methods.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class MyStructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber
def notification(event)
emit_event("my.notification", data: 1)
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">MyStructuredEventSubscriber</span> < <span class="pl-v">ActiveSupport</span>::<span class="pl-v">StructuredEventSubscriber</span>
<span class="pl-k">def</span> <span class="pl-en">notification</span><span class="pl-kos">(</span><span class="pl-s1">event</span><span class="pl-kos">)</span>
<span class="pl-en">emit_event</span><span class="pl-kos">(</span><span class="pl-s">"my.notification"</span><span class="pl-kos">,</span> <span class="pl-pds">data</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p><code>ActiveSupport::FileUpdateChecker</code> does not depend on <code>Time.now</code> to prevent unecessary reloads with time travel test helpers</p>
<p><em>Jan Grodowski</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>
<p>Add <code>reset_token: { expires_in: ... }</code> option to <code>has_secure_password</code>.</p>
<p>Allows configuring the expiry duration of password reset tokens (default remains 15 minutes for backwards compatibility).</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="has_secure_password reset_token: { expires_in: 1.hour }"><pre><span class="pl-en">has_secure_password</span> <span class="pl-pds">reset_token</span>: <span class="pl-kos">{</span> <span class="pl-pds">expires_in</span>: <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">hour</span> <span class="pl-kos">}</span></pre></div>
<p><em>Jevin Sew</em>, <em>Abeid Ahmed</em></p>
</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Add replicas to test database parallelization setup.</p>
<p>Setup and configuration of databases for parallel testing now includes replicas.</p>
<p>This fixes an issue when using a replica database, database selector middleware,<br>
and non-transactional tests, where integration tests running in parallel would select<br>
the base test database, i.e. <code>db_test</code>, instead of the numbered parallel worker database,<br>
i.e. <code>db_test_{n}</code>.</p>
<p><em>Adam Maas</em></p>
</li>
<li>
<p>Support virtual (not persisted) generated columns on PostgreSQL 18+</p>
<p>PostgreSQL 18 introduces virtual (not persisted) generated columns,<br>
which are now the default unless the <code>stored: true</code> option is explicitly specified on PostgreSQL 18+.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="create_table :users do |t|
t.string :name
t.virtual :lower_name, type: :string, as: "LOWER(name)", stored: false
t.virtual :name_length, type: :integer, as: "LENGTH(name)"
end"><pre><span class="pl-en">create_table</span> <span class="pl-pds">:users</span> <span class="pl-k">do</span> |<span class="pl-s1">t</span>|
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">string</span> <span class="pl-pds">:name</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">virtual</span> <span class="pl-pds">:lower_name</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:string</span><span class="pl-kos">,</span> <span class="pl-pds">as</span>: <span class="pl-s">"LOWER(name)"</span><span class="pl-kos">,</span> <span class="pl-pds">stored</span>: <span class="pl-c1">false</span>
<span class="pl-s1">t</span><span class="pl-kos">.</span><span class="pl-en">virtual</span> <span class="pl-pds">:name_length</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:integer</span><span class="pl-kos">,</span> <span class="pl-pds">as</span>: <span class="pl-s">"LENGTH(name)"</span>
<span class="pl-k">end</span></pre></div>
<p><em>Yasuo Honda</em></p>
</li>
<li>
<p>Optimize schema dumping to prevent duplicate file generation.</p>
<p><code>ActiveRecord::Tasks::DatabaseTasks.dump_all</code> now tracks which schema files<br>
have already been dumped and skips dumping the same file multiple times.<br>
This improves performance when multiple database configurations share the<br>
same schema dump path.</p>
<p><em>Mikey Gough</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Add structured events for Active Record:</p>
<ul>
<li><code>active_record.strict_loading_violation</code></li>
<li><code>active_record.sql</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add support for integer shard keys.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Now accepts symbols as shard keys.
ActiveRecord::Base.connects_to(shards: {
1: { writing: :primary_shard_one, reading: :primary_shard_one },
2: { writing: :primary_shard_two, reading: :primary_shard_two},
})
ActiveRecord::Base.connected_to(shard: 1) do
# ..
end"><pre><span class="pl-c"># Now accepts symbols as shard keys.</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">connects_to</span><span class="pl-kos">(</span><span class="pl-pds">shards</span>: <span class="pl-kos">{</span>
<span class="pl-c1">1</span>: <span class="pl-kos">{</span> <span class="pl-pds">writing</span>: <span class="pl-pds">:primary_shard_one</span><span class="pl-kos">,</span> <span class="pl-pds">reading</span>: <span class="pl-pds">:primary_shard_one</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">2</span>: <span class="pl-kos">{</span> <span class="pl-pds">writing</span>: <span class="pl-pds">:primary_shard_two</span><span class="pl-kos">,</span> <span class="pl-pds">reading</span>: <span class="pl-pds">:primary_shard_two</span><span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-v">ActiveRecord</span>::<span class="pl-v">Base</span><span class="pl-kos">.</span><span class="pl-en">connected_to</span><span class="pl-kos">(</span><span class="pl-pds">shard</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
<span class="pl-c"># ..</span>
<span class="pl-k">end</span></pre></div>
<p><em>Nony Dutton</em></p>
</li>
<li>
<p>Add <code>ActiveRecord::Base.only_columns</code></p>
<p>Similar in use case to <code>ignored_columns</code> but listing columns to consider rather than the ones<br>
to ignore.</p>
<p>Can be useful when working with a legacy or shared database schema, or to make safe schema change<br>
in two deploys rather than three.</p>
<p><em>Anton Kandratski</em></p>
</li>
<li>
<p>Use <code>PG::Connection#close_prepared</code> (protocol level Close) to deallocate<br>
prepared statements when available.</p>
<p>To enable its use, you must have pg >= 1.6.0, libpq >= 17, and a PostgreSQL<br>
database version >= 17.</p>
<p><em>Hartley McGuire</em>, <em>Andrew Jackson</em></p>
</li>
<li>
<p>Fix query cache for pinned connections in multi threaded transactional tests</p>
<p>When a pinned connection is used across separate threads, they now use a separate cache store<br>
for each thread.</p>
<p>This improve accuracy of system tests, and any test using multiple threads.</p>
<p><em>Heinrich Lee Yu</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix time attribute dirty tracking with timezone conversions.</p>
<p>Time-only attributes now maintain a fixed date of 2000-01-01 during timezone conversions,<br>
preventing them from being incorrectly marked as changed due to date shifts.</p>
<p>This fixes an issue where time attributes would be marked as changed when setting the same time value<br>
due to timezone conversion causing internal date shifts.</p>
<p><em>Prateek Choudhary</em></p>
</li>
<li>
<p>Skip calling <code>PG::Connection#cancel</code> in <code>cancel_any_running_query</code><br>
when using libpq >= 18 with pg < 1.6.0, due to incompatibility.<br>
Rollback still runs, but may take longer.</p>
<p><em>Yasuo Honda</em>, <em>Lars Kanis</em></p>
</li>
<li>
<p>Don't add <code>id_value</code> attribute alias when attribute/column with that name already exists.</p>
<p><em>Rob Lewis</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>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.</p>
<p>Before:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --><p>This is grand!</p>"><pre class="notranslate"><code><!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --><p>This is grand!</p>
</code></pre></div>
<p>After:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb
--><p>This is grand!</p>"><pre class="notranslate"><code><!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb
--><p>This is grand!</p>
</code></pre></div>
<p><em>Emmanuel Hayford</em></p>
</li>
<li>
<p>Add structured events for Action View:</p>
<ul>
<li><code>action_view.render_template</code></li>
<li><code>action_view.render_partial</code></li>
<li><code>action_view.render_layout</code></li>
<li><code>action_view.render_collection</code></li>
<li><code>action_view.render_start</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Fix label with <code>for</code> option not getting prefixed by form <code>namespace</code> value</p>
<p><em>Abeid Ahmed</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Add <code>fetchpriority</code> to Link headers to match HTML generated by <code>preload_link_tag</code>.</p>
<p><em>Guillermo Iguaran</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>Add link-local IP ranges to <code>ActionDispatch::RemoteIp</code> default proxies.</p>
<p>Link-local addresses (<code>169.254.0.0/16</code> for IPv4 and <code>fe80::/10</code> for IPv6)<br>
are now included in the default trusted proxy list, similar to private IP ranges.</p>
<p><em>Adam Daniels</em></p>
</li>
<li>
<p><code>remote_ip</code> will no longer ignore IPs in X-Forwarded-For headers if they<br>
are accompanied by port information.</p>
<p><em>Duncan Brown</em>, <em>Prevenios Marinos</em>, <em>Masafumi Koba</em>, <em>Adam Daniels</em></p>
</li>
<li>
<p>Add <code>action_dispatch.verbose_redirect_logs</code> setting that logs where redirects were called from.</p>
<p>Similar to <code>active_record.verbose_query_logs</code> and <code>active_job.verbose_enqueue_logs</code>, this adds a line in your logs that shows where a redirect was called from.</p>
<p>Example:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Redirected to http://localhost:3000/posts/1
↳ app/controllers/posts_controller.rb:32:in `block (2 levels) in create'"><pre class="notranslate"><code>Redirected to http://localhost:3000/posts/1
↳ app/controllers/posts_controller.rb:32:in `block (2 levels) in create'
</code></pre></div>
<p><em>Dennis Paagman</em></p>
</li>
<li>
<p>Add engine route filtering and better formatting in <code>bin/rails routes</code>.</p>
<p>Allow engine routes to be filterable in the routing inspector, and<br>
improve formatting of engine routing output.</p>
<p>Before:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="> bin/rails routes -e engine_only
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."><pre class="notranslate"><code>> bin/rails routes -e engine_only
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
</code></pre></div>
<p>After:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="> bin/rails routes -e engine_only
Routes for application:
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
Routes for Test::Engine:
Prefix Verb URI Pattern Controller#Action
engine GET /engine_only(.:format) a#b"><pre class="notranslate"><code>> bin/rails routes -e engine_only
Routes for application:
No routes were found for this grep pattern.
For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html.
Routes for Test::Engine:
Prefix Verb URI Pattern Controller#Action
engine GET /engine_only(.:format) a#b
</code></pre></div>
<p><em>Dennis Paagman</em>, <em>Gannon McGibbon</em></p>
</li>
<li>
<p>Add structured events for Action Pack and Action Dispatch:</p>
<ul>
<li><code>action_dispatch.redirect</code></li>
<li><code>action_controller.request_started</code></li>
<li><code>action_controller.request_completed</code></li>
<li><code>action_controller.callback_halted</code></li>
<li><code>action_controller.rescue_from_handled</code></li>
<li><code>action_controller.file_sent</code></li>
<li><code>action_controller.redirected</code></li>
<li><code>action_controller.data_sent</code></li>
<li><code>action_controller.unpermitted_parameters</code></li>
<li><code>action_controller.fragment_cache</code></li>
</ul>
<p><em>Adrianna Chang</em></p>
</li>
<li>
<p>URL helpers for engines mounted at the application root handle <code>SCRIPT_NAME</code> correctly.</p>
<p>Fixed an issue where <code>SCRIPT_NAME</code> is not applied to paths generated for routes in an engine<br>
mounted at "/".</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Update <code>ActionController::Metal::RateLimiting</code> to support passing method names to <code>:by</code> and <code>:with</code></p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class SignupsController < ApplicationController
rate_limit to: 10, within: 1.minute, with: :redirect_with_flash
private
def redirect_with_flash
redirect_to root_url, alert: "Too many requests!"
end
end"><pre><span class="pl-k">class</span> <span class="pl-v">SignupsController</span> < <span class="pl-v">ApplicationController</span>
<span class="pl-en">rate_limit</span> <span class="pl-pds">to</span>: <span class="pl-c1">10</span><span class="pl-kos">,</span> <span class="pl-pds">within</span>: <span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">minute</span><span class="pl-kos">,</span> <span class="pl-pds">with</span>: <span class="pl-pds">:redirect_with_flash</span>
<span class="pl-k">private</span>
<span class="pl-k">def</span> <span class="pl-en">redirect_with_flash</span>
<span class="pl-en">redirect_to</span> <span class="pl-en">root_url</span><span class="pl-kos">,</span> <span class="pl-pds">alert</span>: <span class="pl-s">"Too many requests!"</span>
<span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Optimize <code>ActionDispatch::Http::URL.build_host_url</code> when protocol is included in host.</p>
<p>When using URL helpers with a host that includes the protocol (e.g., <code>{ host: "https://example.com" }</code>),<br>
skip unnecessary protocol normalization and string duplication since the extracted protocol is already<br>
in the correct format. This eliminates 2 string allocations per URL generation and provides a ~10%<br>
performance improvement for this case.</p>
<p><em>Joshua Young</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Allow <code>action_controller.logger</code> to be disabled by setting it to <code>nil</code> or <code>false</code> instead of always defaulting to <code>Rails.logger</code>.</p>
<p><em>Roberto Miranda</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Add structured events for Active Job:</p>
<ul>
<li><code>active_job.enqueued</code></li>
<li><code>active_job.bulk_enqueued</code></li>
<li><code>active_job.started</code></li>
<li><code>active_job.completed</code></li>
<li><code>active_job.retry_scheduled</code></li>
<li><code>active_job.retry_stopped</code></li>
<li><code>active_job.discarded</code></li>
<li><code>active_job.interrupt</code></li>
<li><code>active_job.resume</code></li>
<li><code>active_job.step_skipped</code></li>
<li><code>active_job.step_started</code></li>
<li><code>active_job.step</code></li>
</ul>
<p><em>Adrianna Chang</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>
<p>Add structured events for Action Mailer:</p>
<ul>
<li><code>action_mailer.delivered</code></li>
<li><code>action_mailer.processed</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Add structured events for Active Storage:</p>
<ul>
<li><code>active_storage.service_upload</code></li>
<li><code>active_storage.service_download</code></li>
<li><code>active_storage.service_streaming_download</code></li>
<li><code>active_storage.preview</code></li>
<li><code>active_storage.service_delete</code></li>
<li><code>active_storage.service_delete_prefixed</code></li>
<li><code>active_storage.service_exist</code></li>
<li><code>active_storage.service_url</code></li>
<li><code>active_storage.service_mirror</code></li>
</ul>
<p><em>Gannon McGibbon</em></p>
</li>
<li>
<p>Allow analyzers and variant transformer to be fully configurable</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# ActiveStorage.analyzers can be set to an empty array:
config.active_storage.analyzers = []
# => ActiveStorage.analyzers = []
# or use custom analyzer:
config.active_storage.analyzers = [ CustomAnalyzer ]
# => ActiveStorage.analyzers = [ CustomAnalyzer ]"><pre><span class="pl-c"># ActiveStorage.analyzers can be set to an empty array:</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">analyzers</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>
<span class="pl-c"># => ActiveStorage.analyzers = []</span>
<span class="pl-c"># or use custom analyzer:</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">analyzers</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span> <span class="pl-v">CustomAnalyzer</span> <span class="pl-kos">]</span>
<span class="pl-c"># => ActiveStorage.analyzers = [ CustomAnalyzer ]</span></pre></div>
<p>If no configuration is provided, it will use the default analyzers.</p>
<p>You can also disable variant processor to remove warnings on startup about missing gems.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="config.active_storage.variant_processor = :disabled"><pre><span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">active_storage</span><span class="pl-kos">.</span><span class="pl-en">variant_processor</span> <span class="pl-c1">=</span> <span class="pl-pds">:disabled</span></pre></div>
<p><em>zzak</em>, <em>Alexandre Ruban</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>
<p>De-couple <code>@rails/actiontext/attachment_upload.js</code> from <code>Trix.Attachment</code></p>
<p>Implement <code>@rails/actiontext/index.js</code> with a <code>direct-upload:progress</code> event<br>
listeners and <code>Promise</code> resolution.</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Capture block content for form helper methods</p>
<div class="highlight highlight-text-html-erb notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="<%= rich_textarea_tag :content, nil do %>
<h1>hello world</h1>
<% end %>
<!-- <input type="hidden" name="content" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->
<%= rich_textarea :message, :content, input: "trix_input_1" do %>
<h1>hello world</h1>
<% end %>
<!-- <input type="hidden" name="message[content]" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->
<%= form_with model: Message.new do |form| %>
<%= form.rich_textarea :content do %>
<h1>hello world</h1>
<% end %>
<% end %>
<!-- <form action="/messages" accept-charset="UTF-8" method="post"><input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … -->"><pre><span class="pl-k"><%=</span> rich_textarea_tag :content, nil do <span class="pl-k">%></span>
<span class="pl-kos"><span class="pl-en"></span><span class="pl-pds"></span><span class="pl-kos"></span><span class="pl-c1"></span><span class="pl-k"></span><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <input type="hidden" name="content" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span>
<span class="pl-k"><%=</span> <span class="pl-en">rich_textarea</span> <span class="pl-pds">:message</span><span class="pl-kos">,</span> <span class="pl-pds">:content</span><span class="pl-kos">,</span> <span class="pl-pds">input</span>: <span class="pl-s">"trix_input_1"</span> <span class="pl-k">do</span> <span class="pl-k">%></span>
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <input type="hidden" name="message[content]" id="trix_input_1" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span>
<span class="pl-k"><%=</span> <span class="pl-en">form_with</span> <span class="pl-pds">model</span>: <span class="pl-v">Message</span><span class="pl-kos">.</span><span class="pl-en">new</span> <span class="pl-k">do</span> |<span class="pl-s1">form</span>| <span class="pl-k">%></span>
<span class="pl-k"><%=</span> <span class="pl-s1">form</span><span class="pl-kos">.</span><span class="pl-en">rich_textarea</span> <span class="pl-pds">:content</span> <span class="pl-k">do</span> <span class="pl-k">%></span>
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>hello world<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-k"><%</span> <span class="pl-k">end</span> <span class="pl-k">%></span>
<span class="pl-c"><!-- <form action="/messages" accept-charset="UTF-8" method="post"><input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&lt;h1&gt;hello world&lt;/h1&gt;"/><trix-editor … --></span></pre></div>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Generalize <code>:rich_text_area</code> Capybara selector</p>
<p>Prepare for more Action Text-capable WYSIWYG editors by making<br>
<code>:rich_text_area</code> rely on the presence of <code>[role="textbox"]</code> and<br>
<code>[contenteditable]</code> HTML attributes rather than a <code><trix-editor></code> element.</p>
<p><em>Sean Doyle</em></p>
</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Suggest <code>bin/rails action_text:install</code> from Action Dispatch error page</p>
<p><em>Sean Doyle</em></p>
</li>
<li>
<p>Remove deprecated <code>STATS_DIRECTORIES</code>.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>bin/rake stats</code> command.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Remove deprecated <code>rails/console/methods.rb</code> file.</p>
<p><em>Rafael Mendonça França</em></p>
</li>
<li>
<p>Don't generate system tests by default.</p>
<p>Rails scaffold generator will no longer generate system tests by default. To enable this pass <code>--system-tests=true</code> or generate them with <code>bin/rails generate system_test name_of_test</code>.</p>
<p><em>Eileen M. Uchitelle</em></p>
</li>
<li>
<p>Optionally skip bundler-audit.</p>
<p>Skips adding the <code>bin/bundler-audit</code> & <code>config/bundler-audit.yml</code> if the gem is not installed when <code>bin/rails app:update</code> runs.</p>
<p>Passes an option to <code>--skip-bundler-audit</code> when new apps are generated & adds that same option to the <code>--minimal</code> generator flag.</p>
<p><em>Jill Klang</em></p>
</li>
<li>
<p>Show engine routes in <code>/rails/info/routes</code> as well.</p>
<p><em>Petrik de Heus</em></p>
</li>
<li>
<p>Exclude <code>asset_path</code> configuration from Kamal <code>deploy.yml</code> for API applications.</p>
<p>API applications don't serve assets, so the <code>asset_path</code> configuration in <code>deploy.yml</code><br>
is not needed and can cause 404 errors on in-flight requests. The asset_path is now<br>
only included for regular Rails applications that serve assets.</p>
<p><em>Saiqul Haq</em></p>
</li>
<li>
<p>Reverted the incorrect default <code>config.public_file_server.headers</code> config.</p>
<p>If you created a new application using Rails <code>8.1.0.beta1</code>, make sure to regenerate<br>
<code>config/environments/production.rb</code>, or to manually edit the <code>config.public_file_server.headers</code><br>
configuration to just be:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Cache assets for far-future expiry since they are all digest stamped.
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }"><pre><span class="pl-c"># Cache assets for far-future expiry since they are all digest stamped.</span>
<span class="pl-en">config</span><span class="pl-kos">.</span><span class="pl-en">public_file_server</span><span class="pl-kos">.</span><span class="pl-en">headers</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span> <span class="pl-s">"cache-control"</span> <span class="pl-c1">=></span> <span class="pl-s">"public, max-age=<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-c1">1</span><span class="pl-kos">.</span><span class="pl-en">year</span><span class="pl-kos">.</span><span class="pl-en">to_i</span><span class="pl-kos">}</span></span>"</span> <span class="pl-kos">}</span></pre></div>
<p><em>Jean Boussier</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfrancatag:github.com,2008:Repository/8514/v8.0.32025-09-22T22:19:48Z8.0.3<h2>Active Support</h2>
<ul>
<li>
<p><code>ActiveSupport::FileUpdateChecker</code> does not depend on <code>Time.now</code> to prevent unnecessary reloads with time travel test helpers</p>
<p><em>Jan Grodowski</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::BroadcastLogger</code> from executing a block argument for each logger (tagged, info, etc.).</p>
<p><em>Jared Armstrong</em></p>
</li>
<li>
<p>Make <code>ActiveSupport::Logger</code> <code>#freeze</code>-friendly.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::HashWithIndifferentAccess#transform_keys!</code> removing defaults.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::HashWithIndifferentAccess#tranform_keys!</code> to handle collisions.</p>
<p>If the transformation would result in a key equal to another not yet transformed one,<br>
it would result in keys being lost.</p>
<p>Before:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=">> {a: 1, b: 2}.with_indifferent_access.transform_keys!(&:succ)
=> {"c" => 1}"><pre>>> <span class="pl-kos">{</span><span class="pl-pds">a</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-pds">b</span>: <span class="pl-c1">2</span><span class="pl-kos">}</span><span class="pl-kos">.</span><span class="pl-en">with_indifferent_access</span><span class="pl-kos">.</span><span class="pl-en">transform_keys!</span><span class="pl-kos">(</span>&<span class="pl-pds">:succ</span><span class="pl-kos">)</span>
<span class="pl-c1">=></span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=></span> <span class="pl-c1">1</span><span class="pl-kos">}</span></pre></div>
<p>After:</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content=">> {a: 1, b: 2}.with_indifferent_access.transform_keys!(&:succ)
=> {"c" => 1, "d" => 2}"><pre>>> <span class="pl-kos">{</span><span class="pl-pds">a</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-pds">b</span>: <span class="pl-c1">2</span><span class="pl-kos">}</span><span class="pl-kos">.</span><span class="pl-en">with_indifferent_access</span><span class="pl-kos">.</span><span class="pl-en">transform_keys!</span><span class="pl-kos">(</span>&<span class="pl-pds">:succ</span><span class="pl-kos">)</span>
<span class="pl-c1">=></span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=></span> <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-s">"d"</span> <span class="pl-c1">=></span> <span class="pl-c1">2</span><span class="pl-kos">}</span></pre></div>
<p><em>Jason T Johnson</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>ActiveSupport::Cache::MemCacheStore#read_multi</code> to handle network errors.</p>
<p>This method specifically wasn't handling network errors like other codepaths.</p>
<p><em>Alessandro Dal Grande</em></p>
</li>
<li>
<p>Fix configuring <code>RedisCacheStore</code> with <code>raw: true</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix <code>Enumerable#sole</code> for infinite collections.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Active Model</h2>
<ul>
<li>
<p>Fix <code>has_secure_password</code> to perform confirmation validation of the password even when blank.</p>
<p>The validation was incorrectly skipped when the password only contained whitespace characters.</p>
<p><em>Fabio Sangiovanni</em></p>
</li>
</ul>
<h2>Active Record</h2>
<ul>
<li>
<p>Fix query cache for pinned connections in multi threaded transactional tests</p>
<p>When a pinned connection is used across separate threads, they now use a separate cache store<br>
for each thread.</p>
<p>This improve accuracy of system tests, and any test using multiple threads.</p>
<p><em>Heinrich Lee Yu</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Don't add <code>id_value</code> attribute alias when attribute/column with that name already exists.</p>
<p><em>Rob Lewis</em></p>
</li>
<li>
<p>Fix false positive change detection involving STI and polymorphic has one relationships.</p>
<p>Polymorphic <code>has_one</code> relationships would always be considered changed when defined in a STI child<br>
class, causing nedless extra autosaves.</p>
<p><em>David Fritsch</em></p>
</li>
<li>
<p>Skip calling <code>PG::Connection#cancel</code> in <code>cancel_any_running_query</code><br>
when using libpq >= 18 with pg < 1.6.0, due to incompatibility.<br>
Rollback still runs, but may take longer.</p>
<p><em>Yasuo Honda</em>, <em>Lars Kanis</em></p>
</li>
<li>
<p>Fix stale association detection for polymorphic <code>belongs_to</code>.</p>
<p><em>Florent Beaurain</em>, <em>Thomas Crambert</em></p>
</li>
<li>
<p>Fix removal of PostgreSQL version comments in <code>structure.sql</code> for latest PostgreSQL versions which include <code>\restrict</code></p>
<p><em>Brendan Weibrecht</em></p>
</li>
<li>
<p>Allow setting <code>schema_format</code> in database configuration.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="primary:
schema_format: ruby"><pre class="notranslate"><code>primary:
schema_format: ruby
</code></pre></div>
<p>Useful in multi-database setups to have different formats per-database.</p>
<p><em>T S Vallender</em></p>
</li>
<li>
<p>Use ntuples to populate row_count instead of count for Postgres</p>
<p><em>Jonathan Calvert</em></p>
</li>
<li>
<p>Fix <code>#merge</code> with <code>#or</code> or <code>#and</code> and a mixture of attributes and SQL strings resulting in an incorrect query.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="base = Comment.joins(:post).where(user_id: 1).where("recent = 1")
puts base.merge(base.where(draft: true).or(Post.where(archived: true))).to_sql"><pre><span class="pl-s1">base</span> <span class="pl-c1">=</span> <span class="pl-v">Comment</span><span class="pl-kos">.</span><span class="pl-en">joins</span><span class="pl-kos">(</span><span class="pl-pds">:post</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">user_id</span>: <span class="pl-c1">1</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-s">"recent = 1"</span><span class="pl-kos">)</span>
<span class="pl-en">puts</span> <span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">merge</span><span class="pl-kos">(</span><span class="pl-s1">base</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">draft</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">or</span><span class="pl-kos">(</span><span class="pl-v">Post</span><span class="pl-kos">.</span><span class="pl-en">where</span><span class="pl-kos">(</span><span class="pl-pds">archived</span>: <span class="pl-c1">true</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">to_sql</span></pre></div>
<p>Before:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p>After:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="SELECT "comments".* FROM "comments"
INNER JOIN "posts" ON "posts"."id" = "comments"."post_id"
WHERE "comments"."user_id" = 1
AND (recent = 1)
AND (
"comments"."user_id" = 1
AND (recent = 1)
AND "comments"."draft" = 1
OR "posts"."archived" = 1
)"><pre><span class="pl-k">SELECT</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-k">*</span> <span class="pl-k">FROM</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>
<span class="pl-k">INNER JOIN</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span> <span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>post_id<span class="pl-pds">"</span></span>
<span class="pl-k">WHERE</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> (
<span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>user_id<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">AND</span> (recent <span class="pl-k">=</span> <span class="pl-c1">1</span>)
<span class="pl-k">AND</span> <span class="pl-s"><span class="pl-pds">"</span>comments<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>draft<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">OR</span> <span class="pl-s"><span class="pl-pds">"</span>posts<span class="pl-pds">"</span></span>.<span class="pl-s"><span class="pl-pds">"</span>archived<span class="pl-pds">"</span></span> <span class="pl-k">=</span> <span class="pl-c1">1</span>
)</pre></div>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix inline <code>has_and_belongs_to_many</code> fixtures for tables with composite primary keys.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix migration log message for down operations.</p>
<p><em>Bernardo Barreto</em></p>
</li>
<li>
<p>Prepend <code>extra_flags</code> in postgres' <code>structure_load</code></p>
<p>When specifying <code>structure_load_flags</code> with a postgres adapter, the flags<br>
were appended to the default flags, instead of prepended.<br>
This caused issues with flags not being taken into account by postgres.</p>
<p><em>Alice Loeser</em></p>
</li>
<li>
<p>Fix <code>annotate</code> comments to propagate to <code>update_all</code>/<code>delete_all</code>.</p>
<p><em>fatkodima</em></p>
</li>
<li>
<p>Fix checking whether an unpersisted record is <code>include?</code>d in a strictly<br>
loaded <code>has_and_belongs_to_many</code> association.</p>
<p><em>Hartley McGuire</em></p>
</li>
<li>
<p><code>create_or_find_by</code> will now correctly rollback a transaction.</p>
<p>When using <code>create_or_find_by</code>, raising a ActiveRecord::Rollback error<br>
in a <code>after_save</code> callback had no effect, the transaction was committed<br>
and a record created.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Gracefully handle <code>Timeout.timeout</code> firing during connection configuration.</p>
<p>Use of <code>Timeout.timeout</code> could result in improperly initialized database connection.</p>
<p>This could lead to a partially configured connection being used, resulting in various exceptions,<br>
the most common being with the PostgreSQLAdapter raising <code>undefined method 'key?' for nil</code><br>
or <code>TypeError: wrong argument type nil (expected PG::TypeMap)</code>.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix stale state for composite foreign keys in belongs_to associations.</p>
<p><em>Varun Sharma</em></p>
</li>
</ul>
<h2>Action View</h2>
<ul>
<li>
<p>Fix label with <code>for</code> option not getting prefixed by form <code>namespace</code> value</p>
<p><em>Abeid Ahmed</em>, <em>Hartley McGuire</em></p>
</li>
<li>
<p>Fix <code>javascript_include_tag</code> <code>type</code> option to accept either strings and symbols.</p>
<div class="highlight highlight-source-ruby notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="javascript_include_tag "application", type: :module
javascript_include_tag "application", type: "module""><pre><span class="pl-en">javascript_include_tag</span> <span class="pl-s">"application"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-pds">:module</span>
<span class="pl-en">javascript_include_tag</span> <span class="pl-s">"application"</span><span class="pl-kos">,</span> <span class="pl-pds">type</span>: <span class="pl-s">"module"</span></pre></div>
<p>Previously, only the string value was recognized.</p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fix <code>excerpt</code> helper with non-whitespace separator.</p>
<p><em>Jonathan Hefner</em></p>
</li>
</ul>
<h2>Action Pack</h2>
<ul>
<li>
<p>URL helpers for engines mounted at the application root handle <code>SCRIPT_NAME</code> correctly.</p>
<p>Fixed an issue where <code>SCRIPT_NAME</code> is not applied to paths generated for routes in an engine<br>
mounted at "/".</p>
<p><em>Mike Dalessio</em></p>
</li>
<li>
<p>Fix <code>Rails.application.reload_routes!</code> from clearing almost all routes.</p>
<p>When calling <code>Rails.application.reload_routes!</code> inside a middleware of<br>
a Rake task, it was possible under certain conditions that all routes would be cleared.<br>
If ran inside a middleware, this would result in getting a 404 on most page you visit.<br>
This issue was only happening in development.</p>
<p><em>Edouard Chin</em></p>
</li>
<li>
<p>Address <code>rack 3.2</code> deprecations warnings.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="warning: Status code :unprocessable_entity is deprecated and will be removed in a future version of Rack.
Please use :unprocessable_content instead."><pre class="notranslate"><code>warning: Status code :unprocessable_entity is deprecated and will be removed in a future version of Rack.
Please use :unprocessable_content instead.
</code></pre></div>
<p>Rails API will transparently convert one into the other for the foreseeable future.</p>
<p><em>Earlopain</em>, <em>Jean Boussier</em></p>
</li>
<li>
<p>Support hash-source in Content Security Policy.</p>
<p><em>madogiwa</em></p>
</li>
<li>
<p>Always return empty body for HEAD requests in <code>PublicExceptions</code> and<br>
<code>DebugExceptions</code>.</p>
<p>This is required by <code>Rack::Lint</code> (per RFC9110).</p>
<p><em>Hartley McGuire</em></p>
</li>
</ul>
<h2>Active Job</h2>
<ul>
<li>
<p>Include the actual Active Job locale when serializing rather than I18n locale.</p>
<p><em>Adrien S</em></p>
</li>
<li>
<p>Fix <code>retry_job</code> instrumentation when using <code>:test</code> adapter for Active Job.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Action Mailer</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Cable</h2>
<ul>
<li>
<p>Fixed compatibility with <code>redis</code> gem <code>5.4.1</code></p>
<p><em>Jean Boussier</em></p>
</li>
<li>
<p>Fixed a possible race condition in <code>stream_from</code>.</p>
<p><em>OuYangJinTing</em></p>
</li>
</ul>
<h2>Active Storage</h2>
<ul>
<li>
<p>Address deprecation of <code>Aws::S3::Object#upload_stream</code> in <code>ActiveStorage::Service::S3Service</code>.</p>
<p><em>Joshua Young</em></p>
</li>
<li>
<p>Fix <code>config.active_storage.touch_attachment_records</code> to work with eager loading.</p>
<p><em>fatkodima</em></p>
</li>
</ul>
<h2>Action Mailbox</h2>
<ul>
<li>No changes.</li>
</ul>
<h2>Action Text</h2>
<ul>
<li>
<p>Add rollup-plugin-terser as a dev dependency.</p>
<p><em>Édouard Chin</em></p>
</li>
</ul>
<h2>Railties</h2>
<ul>
<li>
<p>Fix <code>polymorphic_url</code> and <code>polymorphic_path</code> not working when routes are not loaded.</p>
<p><em>Édouard Chin</em></p>
</li>
<li>
<p>Fix Rails console to not override user defined IRB_NAME.</p>
<p>Only change the prompt name if it hasn't been customized in <code>.irbrc</code>.</p>
<p><em>Jarrett Lusso</em></p>
</li>
</ul>
<h2>Guides</h2>
<ul>
<li>No changes.</li>
</ul>rafaelfranca