tag:github.com,2008:https://github.com/rails/rails/releases Release notes from rails 2026-01-08T20:16:56Z tag:github.com,2008:Repository/8514/v8.1.2 2026-01-08T20:17:17Z 8.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(&quot;áÉÍÓÚ&quot;) # =&gt; &quot;Áéíóú&quot; ActiveSupport::Inflector.humanize(&quot;аБВГДЕ&quot;) # =&gt; &quot;Абвгде&quot;"><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"># =&gt; "Áéíóú"</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"># =&gt; "Абвгде"</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> rafaelfranca tag:github.com,2008:Repository/8514/v8.1.1 2025-10-28T23:46:29Z 8.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> rafaelfranca tag:github.com,2008:Repository/8514/v8.0.4 2025-10-28T23:43:23Z 8.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: &quot;bar&quot; object.foo # previously raised NameError, now returns &quot;bar&quot;"><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> rafaelfranca tag:github.com,2008:Repository/8514/v7.2.3 2025-10-28T23:24:18Z 7.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="&gt;&gt; {a: 1, b: 2}.with_indifferent_access.transform_keys!(&amp;:succ) =&gt; {&quot;c&quot; =&gt; 1}"><pre>&gt;&gt; <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>&amp;<span class="pl-pds">:succ</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=&gt;</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="&gt;&gt; {a: 1, b: 2}.with_indifferent_access.transform_keys!(&amp;:succ) =&gt; {&quot;c&quot; =&gt; 1, &quot;d&quot; =&gt; 2}"><pre>&gt;&gt; <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>&amp;<span class="pl-pds">:succ</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=&gt;</span> <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-s">"d"</span> <span class="pl-c1">=&gt;</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(&quot;key&quot;, expires_in: 1.hour, race_condition_ttl: 5.second) do &quot;something&quot; 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 # =&gt; &quot;Object&quot; MyModule::Something = mod mod.module_parent_name # =&gt; &quot;MyModule&quot;"><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"># =&gt; "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"># =&gt; "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(&quot;recent = 1&quot;) 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &lt; ActiveRecord::Base has_many :instructor_roles, -&gt; { active } end class InstructorRole &lt; ActiveRecord::Base scope :active, -&gt; { joins(&quot;JOIN students ON instructor_roles.student_id = students.id&quot;) .where(students { status: 1 }) } end Instructor.joins(:instructor_roles).first"><pre><span class="pl-k">class</span> <span class="pl-v">Instructor</span> &lt; <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">-&gt;</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> &lt; <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">-&gt;</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 &quot;application&quot;, type: :module javascript_include_tag &quot;application&quot;, type: &quot;module&quot;"><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>&lt;input&gt;</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> rafaelfranca tag:github.com,2008:Repository/8514/v7.1.6 2025-10-28T23:19:19Z 7.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> rafaelfranca tag:github.com,2008:Repository/8514/v7.0.10 2025-10-28T23:12:55Z 7.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> rafaelfranca tag:github.com,2008:Repository/8514/v7.0.9 2025-10-28T23:02:29Z 7.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 &lt; 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> rafaelfranca tag:github.com,2008:Repository/8514/v8.1.0 2025-10-22T00:35:05Z 8.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: &quot;bar&quot; object.foo # previously raised NameError, now returns &quot;bar&quot;"><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 &lt; ActiveSupport::StructuredEventSubscriber def notification(event) emit_event(&quot;my.notification&quot;, data: 1) end end"><pre><span class="pl-k">class</span> <span class="pl-v">MyStructuredEventSubscriber</span> &lt; <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: &quot;user.created&quot;, payload: { id: 123 } }, { name: &quot;email.sent&quot;, payload: { to: &quot;[email protected]&quot; } } ]) 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 # =&gt; '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"># =&gt; '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(&quot;user.signup&quot;, user_id: 123, email: &quot;[email protected]&quot;)"><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(&quot;graphql&quot;) do # Event includes tags: { graphql: true } Rails.event.notify(&quot;user.signup&quot;, user_id: 123, email: &quot;[email protected]&quot;) 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: &quot;abc123&quot;, shop_id: 456} Rails.event.set_context(request_id: &quot;abc123&quot;, 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| &quot;#{key}=#{value}&quot; }.join(&quot; &quot;) source_location = event[:source_location] log = &quot;[#{event[:name]}] #{payload} at #{source_location[:filepath]}:#{source_location[:lineno]}&quot; 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 &quot;#index works&quot; 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(&quot;foo&quot;, 1) Rails.cache.read_counter(&quot;foo&quot;) # =&gt; 1 Rails.cache.increment(&quot;foo&quot;) Rails.cache.read_counter(&quot;foo&quot;) # =&gt; 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"># =&gt; 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"># =&gt; 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(&quot;Oops&quot;)) Rails.error.report(IOError.new(&quot;Oh no&quot;)) Rails.error.report(StandardError.new) end assert_equal 2, reports.size assert_equal &quot;Oops&quot;, reports.first.error.message assert_equal &quot;Oh no&quot;, 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 # =&gt; Sun, 09 Jul 2024 15:34:49 EST -05:00 freeze_time Time.current + 1.day sleep 1 Time.current # =&gt; 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"># =&gt; 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"># =&gt; 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('{&quot;key&quot;: &quot;value&quot;}', symbolize_names: true) # =&gt; { key: &quot;value&quot; }"><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"># =&gt; { 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(&quot;post.submitted&quot;, title: &quot;Cool Post&quot;) do ActiveSupport::Notifications.instrument(&quot;post.submitted&quot;, title: &quot;Cool Post&quot;, body: &quot;Cool Body&quot;) 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(&quot;post.submitted&quot;, title: &quot;Cool Post&quot;) do ActiveSupport::Notifications.instrument(&quot;post.submitted&quot;, title: &quot;Cool Post&quot;, body: Body.new(&quot;Cool Body&quot;)) 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(&quot;172.16.0.0/24&quot;)) # =&gt; &quot;\&quot;172.16.0.0\&quot;&quot;"><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"># =&gt; "\"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(&quot;172.16.0.0/24&quot;)) # =&gt; &quot;\&quot;172.16.0.0/24\&quot;&quot;"><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"># =&gt; "\"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: -&gt; email { email.strip.downcase } end user = User.new user.email = &quot; [email protected]\n&quot; user.email # =&gt; &quot;[email protected]&quot;"><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">-&gt;</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"># =&gt; "[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: &quot;LOWER(name)&quot;, stored: false t.virtual :name_length, type: :integer, as: &quot;LENGTH(name)&quot; 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 &gt;= 1.6.0, libpq &gt;= 17, and a PostgreSQL<br> database version &gt;= 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 &gt;= 18 with pg &lt; 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 # =&gt; nil # Returns explicitly set isolation level User.transaction(isolation: :serializable) do User.connection.current_transaction.isolation # =&gt; :serializable end # Returns nil when isolation not explicitly set User.transaction do User.connection.current_transaction.isolation # =&gt; nil end # Nested transactions inherit parent's isolation User.transaction(isolation: :read_committed) do User.transaction do User.connection.current_transaction.isolation # =&gt; :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"># =&gt; 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"># =&gt; :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"># =&gt; 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"># =&gt; :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(&quot;recent = 1&quot;) 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &lt; 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> &lt; <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 &lt; 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> &lt; <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: &quot;SHA256&quot;, 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 = &quot;my secret&quot; # AFTER Post.signed_id_verifier = ActiveSupport::MessageVerifier.new(&quot;my secret&quot;, digest: &quot;SHA256&quot;, 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 # =&gt; false # AFTER ActiveRecord::Base.signed_id_verifier = ActiveSupport::MessageVerifier.new(...) Post.signed_id_verifier == ActiveRecord::Base.signed_id_verifier # =&gt; true Post.signed_id_verifier_secret = &quot;my secret&quot; # =&gt; deprecation warning Post.signed_id_verifier == ActiveRecord::Base.signed_id_verifier # =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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(&quot;post secret&quot;, ...) # 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(&quot;post secret&quot;, ...)"><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(&quot;title = posts.title&quot;)"><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(-&gt;(*) { &quot;Will never reach here&quot; }) 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">-&gt;</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> &gt;= 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 - &lt;%= AppExtensions.location %&gt; # 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">&lt;%= AppExtensions.location %&gt; </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: &quot;AnimalsRecord&quot; }"><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 &lt; 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> &lt; <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="&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt;"><pre class="notranslate"><code>&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt; </code></pre></div> <p>After:</p> <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt;"><pre class="notranslate"><code>&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt; </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) # =&gt; &quot;in 3 minutes&quot; relative_time_in_words(3.minutes.ago) # =&gt; &quot;3 minutes ago&quot; relative_time_in_words(10.seconds.ago, include_seconds: true) # =&gt; &quot;less than 10 seconds ago&quot;"><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"># =&gt; "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"># =&gt; "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"># =&gt; "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>&lt;input&gt;</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="&gt; 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>&gt; 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="&gt; 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>&gt; 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 &lt; ApplicationController rate_limit to: 10, within: 1.minute, with: :redirect_with_flash private def redirect_with_flash redirect_to root_url, alert: &quot;Too many requests!&quot; end end"><pre><span class="pl-k">class</span> <span class="pl-v">SignupsController</span> &lt; <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(&quot;foo=bar;baz=quux&quot;).to_a # =&gt; [[&quot;foo&quot;, &quot;bar&quot;], [&quot;baz&quot;, &quot;quux&quot;]]"><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"># =&gt; [["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(&quot;foo=bar;baz=quux&quot;).to_a # =&gt; [[&quot;foo&quot;, &quot;bar;baz=quux&quot;]]"><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"># =&gt; [["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(&quot;[foo]=bar&quot;) # =&gt; { &quot;foo&quot; =&gt; &quot;bar&quot; } ActionDispatch::ParamBuilder.from_query_string(&quot;[foo][bar]=baz&quot;) # =&gt; { &quot;foo&quot; =&gt; { &quot;bar&quot; =&gt; &quot;baz&quot; } }"><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"># =&gt; { "foo" =&gt; "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"># =&gt; { "foo" =&gt; { "bar" =&gt; "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(&quot;[foo]=bar&quot;) # =&gt; { &quot;[foo]&quot; =&gt; &quot;bar&quot; } ActionDispatch::ParamBuilder.from_query_string(&quot;[foo][bar]=baz&quot;) # =&gt; { &quot;[foo]&quot; =&gt; { &quot;bar&quot; =&gt; &quot;baz&quot; } }"><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"># =&gt; { "[foo]" =&gt; "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"># =&gt; { "[foo]" =&gt; { "bar" =&gt; "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 &lt; 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> &lt; <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 &lt; ActionController::API rate_limit to: 2, within: 2.seconds, scope: &quot;api&quot; end class API::PostsController &lt; APIController # ... end class API::UsersController &lt; APIController # ... end"><pre><span class="pl-k">class</span> <span class="pl-v">APIController</span> &lt; <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> &lt; <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> &lt; <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 &lt;&lt; &quot;example.com&quot;"><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> &lt;&lt; <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 { &quot;status&quot;: &quot;up&quot;, &quot;timestamp&quot;: &quot;2025-09-19T12:00:00Z&quot; }"><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? # =&gt; true/false request.cache_control_directives.no_cache? # =&gt; true/false request.cache_control_directives.no_store? # =&gt; true/false request.cache_control_directives.no_transform? # =&gt; true/false # Value directives request.cache_control_directives.max_age # =&gt; integer or nil request.cache_control_directives.max_stale # =&gt; integer or nil (or true for valueless max-stale) request.cache_control_directives.min_fresh # =&gt; integer or nil request.cache_control_directives.stale_if_error # =&gt; integer or nil # Special helpers for max-stale request.cache_control_directives.max_stale? # =&gt; true if max-stale present (with or without value) request.cache_control_directives.max_stale_unlimited? # =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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"># =&gt; 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 &lt; 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> &lt; <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>&lt;</code>, <code>&gt;</code>, <code>&amp;</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 &lt; 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> &lt; <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 &lt; ApplicationController def index render locals: {} head :ok end end"><pre><span class="pl-k">class</span> <span class="pl-v">PostController</span> &lt; <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 &lt; 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> &lt; <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 = &quot;text/html&quot; # 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 = [] # =&gt; ActiveStorage.analyzers = [] # or use custom analyzer: config.active_storage.analyzers = [ CustomAnalyzer ] # =&gt; 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"># =&gt; 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"># =&gt; 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(&quot;file.txt&quot;) filename == &quot;file.txt&quot; # =&gt; true filename in &quot;file.txt&quot; # =&gt; true &quot;file.txt&quot; == filename # =&gt; 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"># =&gt; 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"># =&gt; true</span> <span class="pl-s">"file.txt"</span> == <span class="pl-s1">filename</span> <span class="pl-c"># =&gt; 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="&lt;%= rich_textarea_tag :content, nil do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;!-- &lt;input type=&quot;hidden&quot; name=&quot;content&quot; id=&quot;trix_input_1&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt; &lt;%= rich_textarea :message, :content, input: &quot;trix_input_1&quot; do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;!-- &lt;input type=&quot;hidden&quot; name=&quot;message[content]&quot; id=&quot;trix_input_1&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt; &lt;%= form_with model: Message.new do |form| %&gt; &lt;%= form.rich_textarea :content do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;% end %&gt; &lt;!-- &lt;form action=&quot;/messages&quot; accept-charset=&quot;UTF-8&quot; method=&quot;post&quot;&gt;&lt;input type=&quot;hidden&quot; name=&quot;message[content]&quot; id=&quot;message_content_trix_input_message&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt;"><pre><span class="pl-k">&lt;%=</span> rich_textarea_tag :content, nil do <span class="pl-k">%&gt;</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>&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;input type="hidden" name="content" id="trix_input_1" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;input type="hidden" name="message[content]" id="trix_input_1" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;form action="/messages" accept-charset="UTF-8" method="post"&gt;&lt;input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</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>&lt;trix-editor&gt;</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 &quot;Rich text editor&quot;, id: &quot;trix_editor_1&quot;, with: &quot;Hello world!&quot;"><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>&gt;= 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> &amp; <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 &amp; 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 = { &quot;cache-control&quot; =&gt; &quot;public, max-age=#{1.year.to_i}&quot; }"><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">=&gt;</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>&lt;%= BCrypt::Password.create("secret") %&gt;</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 &quot;Setup&quot;, &quot;bin/setup --skip-server&quot; step &quot;Style: Ruby&quot;, &quot;bin/rubocop&quot; step &quot;Security: Gem audit&quot;, &quot;bin/bundler-audit&quot; step &quot;Tests: Rails&quot;, &quot;bin/rails test test:system&quot; 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="&lt;meta name=&quot;application-name&quot; content=&quot;Name of Rails Application&quot;&gt;"><pre><span class="pl-kos">&lt;</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">&gt;</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> rafaelfranca tag:github.com,2008:Repository/8514/v8.1.0.rc1 2025-10-15T00:50:43Z 8.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: &quot;bar&quot; object.foo # previously raised NameError, now returns &quot;bar&quot;"><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 &lt; ActiveSupport::StructuredEventSubscriber def notification(event) emit_event(&quot;my.notification&quot;, data: 1) end end"><pre><span class="pl-k">class</span> <span class="pl-v">MyStructuredEventSubscriber</span> &lt; <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: &quot;LOWER(name)&quot;, stored: false t.virtual :name_length, type: :integer, as: &quot;LENGTH(name)&quot; 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 &gt;= 1.6.0, libpq &gt;= 17, and a PostgreSQL<br> database version &gt;= 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 &gt;= 18 with pg &lt; 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="&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt;"><pre class="notranslate"><code>&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt; </code></pre></div> <p>After:</p> <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt;"><pre class="notranslate"><code>&lt;!-- BEGIN /Users/siaw23/Desktop/rails/actionview/test/fixtures/actionpack/test/greeting.html.erb --&gt;&lt;p&gt;This is grand!&lt;/p&gt; </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="&gt; 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>&gt; 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="&gt; 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>&gt; 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 &lt; ApplicationController rate_limit to: 10, within: 1.minute, with: :redirect_with_flash private def redirect_with_flash redirect_to root_url, alert: &quot;Too many requests!&quot; end end"><pre><span class="pl-k">class</span> <span class="pl-v">SignupsController</span> &lt; <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 = [] # =&gt; ActiveStorage.analyzers = [] # or use custom analyzer: config.active_storage.analyzers = [ CustomAnalyzer ] # =&gt; 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"># =&gt; 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"># =&gt; 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="&lt;%= rich_textarea_tag :content, nil do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;!-- &lt;input type=&quot;hidden&quot; name=&quot;content&quot; id=&quot;trix_input_1&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt; &lt;%= rich_textarea :message, :content, input: &quot;trix_input_1&quot; do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;!-- &lt;input type=&quot;hidden&quot; name=&quot;message[content]&quot; id=&quot;trix_input_1&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt; &lt;%= form_with model: Message.new do |form| %&gt; &lt;%= form.rich_textarea :content do %&gt; &lt;h1&gt;hello world&lt;/h1&gt; &lt;% end %&gt; &lt;% end %&gt; &lt;!-- &lt;form action=&quot;/messages&quot; accept-charset=&quot;UTF-8&quot; method=&quot;post&quot;&gt;&lt;input type=&quot;hidden&quot; name=&quot;message[content]&quot; id=&quot;message_content_trix_input_message&quot; value=&quot;&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;&quot;/&gt;&lt;trix-editor … --&gt;"><pre><span class="pl-k">&lt;%=</span> rich_textarea_tag :content, nil do <span class="pl-k">%&gt;</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>&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;input type="hidden" name="content" id="trix_input_1" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;input type="hidden" name="message[content]" id="trix_input_1" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-k">&lt;%=</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">%&gt;</span> <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>hello world<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-k">&lt;%</span> <span class="pl-k">end</span> <span class="pl-k">%&gt;</span> <span class="pl-c">&lt;!-- &lt;form action="/messages" accept-charset="UTF-8" method="post"&gt;&lt;input type="hidden" name="message[content]" id="message_content_trix_input_message" value="&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;"/&gt;&lt;trix-editor … --&gt;</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>&lt;trix-editor&gt;</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> &amp; <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 &amp; 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 = { &quot;cache-control&quot; =&gt; &quot;public, max-age=#{1.year.to_i}&quot; }"><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">=&gt;</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> rafaelfranca tag:github.com,2008:Repository/8514/v8.0.3 2025-09-22T22:19:48Z 8.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="&gt;&gt; {a: 1, b: 2}.with_indifferent_access.transform_keys!(&amp;:succ) =&gt; {&quot;c&quot; =&gt; 1}"><pre>&gt;&gt; <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>&amp;<span class="pl-pds">:succ</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=&gt;</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="&gt;&gt; {a: 1, b: 2}.with_indifferent_access.transform_keys!(&amp;:succ) =&gt; {&quot;c&quot; =&gt; 1, &quot;d&quot; =&gt; 2}"><pre>&gt;&gt; <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>&amp;<span class="pl-pds">:succ</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span><span class="pl-s">"c"</span> <span class="pl-c1">=&gt;</span> <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-s">"d"</span> <span class="pl-c1">=&gt;</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 &gt;= 18 with pg &lt; 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(&quot;recent = 1&quot;) 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &quot;comments&quot;.* FROM &quot;comments&quot; INNER JOIN &quot;posts&quot; ON &quot;posts&quot;.&quot;id&quot; = &quot;comments&quot;.&quot;post_id&quot; WHERE &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND ( &quot;comments&quot;.&quot;user_id&quot; = 1 AND (recent = 1) AND &quot;comments&quot;.&quot;draft&quot; = 1 OR &quot;posts&quot;.&quot;archived&quot; = 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 &quot;application&quot;, type: :module javascript_include_tag &quot;application&quot;, type: &quot;module&quot;"><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