{"id":14847,"date":"2026-09-07T17:30:00","date_gmt":"2026-09-07T12:00:00","guid":{"rendered":"https:\/\/www.allerin.com\/blog\/?p=14847"},"modified":"2026-09-06T21:55:42","modified_gmt":"2026-09-06T16:25:42","slug":"this-week-in-rails-2026-09-04","status":"publish","type":"post","link":"https:\/\/www.allerin.com\/blog\/this-week-in-rails-2026-09-04\/","title":{"rendered":"This Week in Rails: 4 Upgrade Checks"},"content":{"rendered":"<p><a href=\"https:\/\/rubyonrails.org\/2026\/9\/4\/this-week-in-rails\" target=\"_blank\" rel=\"noopener\">This Week in Rails<\/a> highlights changes that can expose assumptions in an existing application, even when its tests pass. Four changes from the 4 September 2026 post deserve attention before the <a href=\"https:\/\/www.allerin.com\/services\/ruby-on-rails\/support-calendar\">Rails 8.0 security deadline of 7 November 2026<\/a>: state ownership, row ordering, MIME labels and JSON options.<\/p>\r\n<p>Release status checked on 6 September: Rails 8.1.3.1 remains the latest release. The features below are development-branch work; a merged pull request does not put them in your installed bundle.<\/p>\r\n\r\n<div id=\"rank-math-toc\" class=\"wp-block-rank-math-toc-block\">\r\n<p><strong>On this page<\/strong><\/p>\r\n<nav aria-label=\"On this page\">\r\n<ul>\r\n<li><a href=\"#this-week-in-rails-four-application-checks\">This Week in Rails: four application checks<\/a><\/li>\r\n<li><a href=\"#thread-safety-did-not-settle-state-ownership\">Thread safety did not settle state ownership<\/a><\/li>\r\n<li><a href=\"#a-stable-row-order-can-conceal-a-missing-contract\">A stable row order can conceal a missing contract<\/a><\/li>\r\n<li><a href=\"#mime-labels-are-part-of-an-application-s-data-contract\">MIME labels are part of an application&#8217;s data contract<\/a><\/li>\r\n<li><a href=\"#json-options-now-need-separate-destinations\">JSON options now need separate destinations<\/a><\/li>\r\n<li><a href=\"#what-to-do-this-week\">What to do this week<\/a><\/li>\r\n<\/ul>\r\n<\/nav><\/div>\r\n\r\n<h2 id=\"this-week-in-rails-four-application-checks\">This Week in Rails: four application checks<\/h2>\r\n<figure class=\"wp-caption alignnone\" style=\"max-width: 100%;\"><a href=\"https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-14853\" style=\"max-width: 100%; height: auto;\" src=\"https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks-1024x832.png\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks-1024x832.png 1024w, https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks-300x244.png 300w, https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks-768x624.png 768w, https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks-1536x1248.png 1536w, https:\/\/www.allerin.com\/blog\/wp-content\/uploads\/2026\/09\/this-week-in-rails-2026-09-04-application-checks.png 1600w\" alt=\"This Week in Rails decision map: checks for Ractor state ownership, unordered queries, Marcel 2 MIME types and JSON coder options.\" width=\"1024\" height=\"832\" \/><\/a>\r\n<figcaption class=\"wp-caption-text\">This Week in Rails: four application contracts to check. All four changes were unreleased on 6 September 2026; the JSON fixes were also on 8-1-stable beyond its latest released tag.<\/figcaption>\r\n<\/figure>\r\n<h2 id=\"thread-safety-did-not-settle-state-ownership\">Thread safety did not settle state ownership<\/h2>\r\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58647\" target=\"_blank\" rel=\"noopener\">PR #58647<\/a> makes controller configuration shareable when an application is prepared for Ractors. <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58620\" target=\"_blank\" rel=\"noopener\">#58620<\/a> changes Action View settings for the same purpose. Both are on main.<\/p>\r\n<p>Rails 2.2 introduced thread-safe dispatch in 2008, allowing concurrent requests in threads within one application process; Rails 4.0 made thread safety the default in 2013. Ruby 3.0 introduced Ractors in December 2020 to permit parallel Ruby execution with restricted object sharing. Configuration accessible to several threads is not necessarily shareable between Ractors.<\/p>\r\n<p>There is nothing to enable on a released Rails application today because of these PRs. For a separate experiment against main, inventory configuration mutated after boot and callbacks capturing non-shareable objects. An initializer assignment is not inherently a defect: the question is who owns the resulting object when another Ractor accesses it.<\/p>\r\n<h2 id=\"a-stable-row-order-can-conceal-a-missing-contract\">A stable row order can conceal a missing contract<\/h2>\r\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58548\" target=\"_blank\" rel=\"noopener\">PR #58548<\/a>, on main, adds an optional test\/development setting that shuffles eligible unordered query results. It is best effort: raw SQL and several statement-cache paths escape it, and shuffling cannot change which row the database already selected with LIMIT 1.<\/p>\r\n<p>Rails 4.0 addressed this boundary in 2013: its first finder gained default ordering for consistent results across database engines, while take retained the previous unspecified-order behavior. That did not give every query a business ordering.<\/p>\r\n<p>Add explicit ordering, including a unique tie-breaker, where order is part of the result&#8217;s meaning; compare sets where it is not. Evaluate shuffling only in a separate main-based test branch. This read-only check, run with Ruby 3.4.10 and Active Record 8.1.3.1, confirms that the new setting is absent from that release:<\/p>\r\n<pre><code class=\"language-ruby\">gem \"activerecord\", \"8.1.3.1\"\r\nrequire \"active_record\"\r\nputs ActiveRecord.version\r\nputs ActiveRecord.respond_to?(:shuffle_unordered_selects=)\r\n<\/code><\/pre>\r\n<p>Actual output:<\/p>\r\n<pre><code class=\"language-text\">8.1.3.1\r\nfalse\r\n<\/code><\/pre>\r\n<h2 id=\"mime-labels-are-part-of-an-application-s-data-contract\">MIME labels are part of an application&#8217;s data contract<\/h2>\r\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58549\" target=\"_blank\" rel=\"noopener\">PR #58549<\/a> requires Marcel 2 on main. Newly identified content can receive canonical MIME names where Marcel 1 returned aliases, including application\/yaml instead of text\/x-yaml. Existing blobs retain their stored content types.<\/p>\r\n<p>Active Storage arrived in Rails 5.2 in 2018 to manage file attachments and already depended on Marcel. The March 2021 releases upgraded that dependency to Marcel 1.0, replacing indirectly licensed MIME data with compatible data. That was a change within an existing dependency, not Marcel&#8217;s introduction.<\/p>\r\n<p>Before adopting the new dependency, test representative uploads through the proposed bundle. Inspect exact-string validations, download handling and gems that constrain Marcel. Compare new uploads with stored blobs; changing a dependency does not rewrite persisted metadata.<\/p>\r\n<h2 id=\"json-options-now-need-separate-destinations\">JSON options now need separate destinations<\/h2>\r\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58601\" target=\"_blank\" rel=\"noopener\">PR #58601<\/a> passes parsing options as keywords and separates an Active Record coder&#8217;s encoding and decoding options. <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58612\" target=\"_blank\" rel=\"noopener\">#58612<\/a> fixes the follow-up custom-encoding regression. The fixes are on main and the 8-1-stable branch, beyond the released tag.<\/p>\r\n<p>Ruby 3.0 separated positional and keyword arguments in 2020 to remove ambiguous conversions. JSON 3.0.0.rc1, released in August 2026, tightens option handling; it remains a release candidate. An option meaningful to an encoder is not automatically valid for its parser.<\/p>\r\n<p>Before changing JSON versions, add round-trip tests for custom coders and their options. Check the exact Rails patch or commit with the chosen JSON version. These PRs do not establish that every application-specific JSON override is broken or repaired.<\/p>\r\n<h2 id=\"what-to-do-this-week\">What to do this week<\/h2>\r\n<ul>\r\n<li>Record the installed Rails and JSON versions before applying advice about main.<\/li>\r\n<li>Test business-critical ordering, including single-row choices that shuffling cannot expose.<\/li>\r\n<li>Assemble uploads that exercise your MIME validations and compare stored versus newly identified types.<\/li>\r\n<li>Cover both encoding and decoding options in serialization tests.<\/li>\r\n<\/ul>\r\n<p>The <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58617\" target=\"_blank\" rel=\"noopener\">Hotwire guide<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58618\" target=\"_blank\" rel=\"noopener\">Securing Rails rewrite<\/a> are also open for review. Read the existing discussions before proposing feedback.<\/p>\r\n<p>The weekly post credits <a href=\"https:\/\/contributors.rubyonrails.org\/contributors\/in-time-window\/20260828-20260904\" target=\"_blank\" rel=\"noopener\">25 contributors<\/a>.<\/p>\r\n<p>For the application-specific sequence, start with <a href=\"https:\/\/www.allerin.com\/services\/rails-upgrades\">Rails upgrades at Allerin<\/a>.<\/p>\r\n<h2 id=\"sources\">Sources<\/h2>\r\n<ul>\r\n<li><a href=\"https:\/\/rubyonrails.org\/2026\/9\/4\/this-week-in-rails\" target=\"_blank\" rel=\"noopener\">Weekly post and linked PRs<\/a>; <a href=\"https:\/\/github.com\/rails\/rails\/releases\/tag\/v8.1.3.1\" target=\"_blank\" rel=\"noopener\">latest Rails release<\/a>; <a href=\"https:\/\/rubyonrails.org\/maintenance\" target=\"_blank\" rel=\"noopener\">maintenance policy<\/a>.<\/li>\r\n<li><a href=\"https:\/\/guides.rubyonrails.org\/v2.3\/2_2_release_notes.html\" target=\"_blank\" rel=\"noopener\">Rails 2.2 notes<\/a>; <a href=\"https:\/\/guides.rubyonrails.org\/4_0_release_notes.html\" target=\"_blank\" rel=\"noopener\">Rails 4.0 notes<\/a>; <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v4.0.0\/activerecord\/CHANGELOG.md\" target=\"_blank\" rel=\"noopener\">4.0 ordering change<\/a>.<\/li>\r\n<li><a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2020\/12\/25\/ruby-3-0-0-released\/\" target=\"_blank\" rel=\"noopener\">Ruby 3.0 release<\/a>; <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2019\/12\/12\/separation-of-positional-and-keyword-arguments-in-ruby-3-0\/\" target=\"_blank\" rel=\"noopener\">Ruby keyword migration<\/a>; <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2026\/06\/30\/ruby-3-4-10-released\/\" target=\"_blank\" rel=\"noopener\">Ruby 3.4.10<\/a>.<\/li>\r\n<li><a href=\"https:\/\/guides.rubyonrails.org\/5_2_release_notes.html\" target=\"_blank\" rel=\"noopener\">Rails 5.2 notes<\/a>; <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v5.2.0\/activestorage\/activestorage.gemspec\" target=\"_blank\" rel=\"noopener\">original Marcel dependency<\/a>; <a href=\"https:\/\/rubyonrails.org\/2021\/3\/26\/marcel-upgrade-releases\" target=\"_blank\" rel=\"noopener\">March 2021 releases<\/a>.<\/li>\r\n<li><a href=\"https:\/\/github.com\/ruby\/json\/blob\/master\/CHANGES.md\" target=\"_blank\" rel=\"noopener\">JSON release notes<\/a>; <a href=\"https:\/\/rubygems.org\/gems\/json\/versions\" target=\"_blank\" rel=\"noopener\">JSON versions<\/a>.<\/li>\r\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>This Week in Rails, read through four application contracts: state ownership, row ordering, MIME types and JSON options. Check what is released before changing the bundle.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[2037],"tags":[2041,2042,2043,2040,2038,2039],"class_list":["post-14847","post","type-post","status-publish","format-standard","hentry","category-ruby-on-rails","tag-active-record","tag-active-storage","tag-json","tag-ractors","tag-rails-upgrades","tag-this-week-in-rails"],"_links":{"self":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14847","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/comments?post=14847"}],"version-history":[{"count":8,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14847\/revisions"}],"predecessor-version":[{"id":14863,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14847\/revisions\/14863"}],"wp:attachment":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/media?parent=14847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/categories?post=14847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/tags?post=14847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}