{"id":14975,"date":"2026-10-05T17:30:00","date_gmt":"2026-10-05T12:00:00","guid":{"rendered":"https:\/\/www.allerin.com\/blog\/?p=14975"},"modified":"2026-10-03T20:20:11","modified_gmt":"2026-10-03T14:50:11","slug":"rails-upgrade-checks-2026-10-02","status":"publish","type":"post","link":"https:\/\/www.allerin.com\/blog\/rails-upgrade-checks-2026-10-02\/","title":{"rendered":"Rails upgrade checks for 2 October 2026"},"content":{"rendered":"<div class=\"allerin-rails-reading\" style=\"overflow-wrap: break-word;\">\n<p>An older Rails application can carry a working route that the development branch now rejects. It can also fail to recognize a validation error that was correctly added. The <a href=\"https:\/\/rubyonrails.org\/2026\/10\/2\/this-week-in-rails\" target=\"_blank\" rel=\"noopener\">2 October Rails reading<\/a> makes both worth checking before the next framework change.<\/p>\n<p>These checks use sources inspected on 2 October. Rails 8.1.4 is the released comparison; the development results use main commit <code style=\"overflow-wrap: anywhere;\">5afc71aa<\/code>. A merged change or stable-branch backport does not establish a released fix. The isolated checks were prepared and run with AI assistance; they do not establish production behavior.<\/p>\n<nav aria-label=\"Article sections\"><a href=\"#check-the-ruby-floor-for-the-framework-you-install\">Ruby requirement<\/a> \u00b7 <a href=\"#replace-catch-all-routes-with-the-paths-you-intend-to-serve\">Routes<\/a> \u00b7 <a href=\"#test-how-callers-recognize-validation-errors\">Validation errors<\/a> \u00b7 <a href=\"#what-to-do-this-week\">Next steps<\/a><\/nav>\n<h2 id=\"check-the-ruby-floor-for-the-framework-you-install\" style=\"scroll-margin-top: 125px;\">Check the Ruby floor for the framework you install<\/h2>\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58908\" target=\"_blank\" rel=\"noopener\">PR #58908<\/a> raises Rails main&#8217;s declared Ruby minimum from 3.3.1 to 3.3.5. That is a requirement in the framework gemspecs, not an instruction to deploy that old patch release. Choose a currently maintained Ruby patch after checking your dependencies and application.<\/p>\n<p>The distinction matters when an upgrade plan mixes released documentation with development instructions. The Rails 8.1.4 gemspec still declares Ruby 3.2.0 or later. Moving to Rails 8.1 does not acquire main&#8217;s new floor merely because both appear in this week&#8217;s research.<\/p>\n<p>Ruby 3.3 introduced <code style=\"overflow-wrap: anywhere;\">ObjectSpace::WeakKeyMap<\/code> in 2023. Rails added a workaround in 2024 to avoid garbage-collection crashes, and the new floor permits retiring that compatibility layer. A subsequent main change removes it, but neither establishes a speedup for your application. Keep the Ruby change and framework change separately testable. The <a href=\"https:\/\/www.allerin.com\/blog\/ruby-3-3-to-3-4-upgrade\/\">Ruby 3.3-to-3.4 article<\/a> explains the separate runtime checks; its dated experiment is not a test of today&#8217;s main branch.<\/p>\n<h2 id=\"replace-catch-all-routes-with-the-paths-you-intend-to-serve\" style=\"scroll-margin-top: 125px;\">Replace catch-all routes with the paths you intend to serve<\/h2>\n<p><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58893\" target=\"_blank\" rel=\"noopener\">PR #58893<\/a> removes dynamic controller and action segments on main. Such a route chooses the controller or action from the incoming path. Rails 5.0 already deprecated this mechanism in 2016, but warning about it did not remove it from an application&#8217;s route file.<\/p>\n<p>In an isolated Ruby 3.4.10 check, the default-style route still drew successfully with warnings against the Rails 8.1.4 source. The pinned main source raised <code style=\"overflow-wrap: anywhere;\">ArgumentError<\/code>. Explicit routes passed recognition checks on both:<\/p>\n<pre style=\"max-width: 100%; overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere;\"><code class=\"language-ruby\">routes.draw do\r\n  get \"photos\", to: \"photos#index\"\r\n  get \"photos\/:id\", to: \"photos#show\"\r\nend\r\n<\/code><\/pre>\n<p>This is not a mechanically equivalent replacement. Our explicit example no longer recognizes <code style=\"overflow-wrap: anywhere;\">\/photos\/show\/7<\/code>; it recognizes <code style=\"overflow-wrap: anywhere;\">\/photos\/7<\/code>. Before removing a catch-all in a 5.2, 6.1 or 7.x application, identify paths clients actually use, preserve required paths explicitly, and test their HTTP methods and destinations. Check test-only route sets too. The upstream patch changes many of its own tests because they relied on the same shortcut.<\/p>\n<p>The rejection is development behavior, not a claim that an 8.1.4 patch upgrade breaks these routes. A small routing probe also cannot verify your authorization rules or production traffic.<\/p>\n<h2 id=\"test-how-callers-recognize-validation-errors\" style=\"scroll-margin-top: 125px;\">Test how callers recognize validation errors<\/h2>\n<p>Rails 8.0 introduced <code style=\"overflow-wrap: anywhere;\">except_on<\/code> in 2024 to skip a validation in specified contexts. <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58876\" target=\"_blank\" rel=\"noopener\">PR #58876<\/a> addresses a different concern: treating that callback option consistently when comparing errors and building their details.<\/p>\n<p>Our synthetic object required a reference except in a draft context. Against Rails 8.1.4, normal validation failed and added the blank error, yet <code style=\"overflow-wrap: anywhere;\">errors.added?(:reference, :blank)<\/code> returned false. Against pinned main, the same lookup returned true. Draft-context validation succeeded with no errors in both runs. The fix changes error matching; the experiment does not show that the original validation was skipped incorrectly.<\/p>\n<p>A consumer that adds an error only when that lookup is false produced a duplicate on the released source and one error on main. The six-case check recorded three expected regression failures against 8.1.4 and passed all six cases against main. No database or customer application was involved.<\/p>\n<p>The fix is also present on the 8.1 stable branch, but absent from the released 8.1.4 tag checked here. Applications on 5.2, 6.1 or 7.x need no change for an option they do not have. When adopting it during an upgrade, test the error-handling consumer as well as the validation decision.<\/p>\n<h2 id=\"what-to-do-this-week\" style=\"scroll-margin-top: 125px;\">What to do this week<\/h2>\n<ul>\n<li>Record the Ruby requirement from the exact Rails version being installed.<\/li>\n<li>Replace dynamic route segments while preserving required paths and methods.<\/li>\n<li>For validations using <code style=\"overflow-wrap: anywhere;\">except_on<\/code>, assert error lookup, details and duplicate prevention.<\/li>\n<li>Keep released-tag results separate from main and unreleased backports.<\/li>\n<\/ul>\n<p>The official policy currently lists Rails 8.1 bug fixes through 10 October 2026 and security fixes through 10 October 2027. Check those separate windows and the policy&#8217;s release-extension condition when planning, rather than treating the bug-fix date as the end of all support.<\/p>\n<p>Use the <a href=\"https:\/\/www.allerin.com\/services\/rails-upgrades\">Rails upgrades guide<\/a> to place these checks within a staged upgrade.<\/p>\n<h2 id=\"sources\" style=\"scroll-margin-top: 125px;\">Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/rubyonrails.org\/2026\/10\/2\/this-week-in-rails\" target=\"_blank\" rel=\"noopener\">Official weekly reading, 2 October 2026<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58908\" target=\"_blank\" rel=\"noopener\">Ruby minimum change #58908<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58918\" target=\"_blank\" rel=\"noopener\">compatibility-layer removal #58918<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/ruby\/ruby\/blob\/v3_3_0\/NEWS.md\" target=\"_blank\" rel=\"noopener\">Ruby 3.3 changes<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/commit\/99808b1\" target=\"_blank\" rel=\"noopener\">Rails weak-map workaround<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.1.4\/rails.gemspec\" target=\"_blank\" rel=\"noopener\">Rails 8.1.4 gemspec<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/rails\/rails\/pull\/58893\" target=\"_blank\" rel=\"noopener\">Route removal #58893<\/a>, <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v5.0.0\/actionpack\/lib\/action_dispatch\/routing\/route_set.rb\" target=\"_blank\" rel=\"noopener\">Rails 5.0 route warnings<\/a> and <a href=\"https:\/\/rubyonrails.org\/2016\/6\/30\/Rails-5-0-final\" target=\"_blank\" rel=\"noopener\">2016 release<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/rails\/rails\/pull\/43495\" target=\"_blank\" rel=\"noopener\">Validation option #43495<\/a>, <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58876\" target=\"_blank\" rel=\"noopener\">error matching fix #58876<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/commit\/a471124b39b3b5c3ccfbb56f7f52d66eac34147f\" target=\"_blank\" rel=\"noopener\">8.1 backport<\/a><\/li>\n<li><a href=\"https:\/\/rubyonrails.org\/maintenance\" target=\"_blank\" rel=\"noopener\">Rails maintenance policy<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Check the Ruby requirement, replace dynamic route segments carefully, and test validation error consumers against the exact Rails source you plan to use.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[2037],"tags":[2086,2038,2085,16,2039],"class_list":["post-14975","post","type-post","status-publish","format-standard","hentry","category-ruby-on-rails","tag-active-model","tag-rails-upgrades","tag-routing","tag-ruby","tag-this-week-in-rails"],"_links":{"self":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14975","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=14975"}],"version-history":[{"count":2,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14975\/revisions"}],"predecessor-version":[{"id":14977,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14975\/revisions\/14977"}],"wp:attachment":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/media?parent=14975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/categories?post=14975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/tags?post=14975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}