Rails uses a “shifted” “semantic” “versioning” which pretty much comes down to the following. Major version: “we’ll most definitely break everything you ever depended on, half of them without warning.” Minor version: “we’ll probably break many stuff you depend on, some of them without warning.” Patch version: “we might accidentally some core APIs, but we promise it’s not intentional (or documented).” Knowing that, I still embarked on the grand endeavor of upgrading from Ruby on Rails 6.0.4.1 to 6.1.4.1. What could possibly go wrong, right?

Railway tracks are suspended above the washed out Tank Hill underpass of the Trans Canada Highway 1 after devastating rain storms caused flooding and landslides, northeast of Lytton, British Columbia, Canada November 17, 2021. B.C. Ministry of Transportation and Infrastructure/Handout via REUTERS
Rails in Canada and on Ruby share (un)surprising similarities (source)

No more autoloading during startup

Let’s start with an easy one. This one was at least documented with the introduction of the Zeitwerk code loader. It has a relatively small blast radius as well: previously autoloaded constants will stop being autoloaded during application boot and have to be manually required. The error message is clear and if you read the docs when switching to Zeitwerk (which you probably have, considering how many things that broke) it shouldn’t come as a surprise.

Before

config.middleware.use DevWebpackProxy, ssl_verify_none: true

After

require './lib/middleware/dev_webpack_proxy'
config.middleware.use DevWebpackProxy, ssl_verify_none: true

add_template_helper disappeared

If you started working with Rails with or before version 3, you might have some instances of add_template_helper in your code. Well, with Rails 6.1, no more. Someone considered it “private” and deleted it without deprecation warning. Luckily there is an equivalent replacement for it.

Before

add_template_helper(MyAwesomeHelper)

After

helper MyAwesomeHelper

fixture_file_upload broken

fixture_file_upload is often used in tests that focus on some uploading functionality. With Rails 6.1, it suddenly turns into a sea of NoMethodError: undefined method 'file_fixture_path' errors. Did they delete it? Turns out they did, but only from a default API. It’s still available, but it now takes an additional include to get it to work.

There is also a new deprecation warning about using file paths relative to file_fixture_path. “Passing a path to fixture_file_upload relative to fixture_path is deprecated. In Rails 6.2, the path needs to be relative to file_fixture_path.” Might as well fix it now while we’re at it though. The warning says how to fix it as well. How kind.

Before

fixture_file_upload('images/test_image.png', 'image/png')

After

include ActionDispatch::TestProcess::FixtureFile
fixture_file_upload('../images/test_image.png', 'image/png')

where.not changed from NOR to NAND

This is a big and potentially very painful change. If you had the policy not to test “obvious” code (because that’d be a test for the framework, right?), this might bite you one too late. Before, where.not would behave like NOR, eg if you gave it multiple conditions it’d only work if all the (negative) conditions were fulfilled (NOR(A, B) = AND(NOT(A), NOT(B))). This changes to NAND, meaning it works if any of the (negative) conditions are fulfilled (NAND(A, B) = OR(NOT(A), NOT(B))). The problem with this isn’t even the difficulty of the fix, but that without thorough test coverage you won’t even notice it (and having a where.not in a scope for example you won’t even see the deprecation warnings). For example if you wanted users who are not admins with godmode (= not admin and not godmode), you’ll need to change your code…

Before

where.not(role: ROLE::ADMIN, status: STATUS::GODMODE)

After

where.not(role: ROLE::ADMIN).where.not(status: STATUS::GODMODE)

has_many refuses source without through

I couldn’t find anything about this. I figure it wasn’t supposed to work in the first place, and now it’s actively erroring. Luckily for me the only usage in our source code was in a relatively simple place, so just removing the source parameter “fixed” the issue. I haven’t looked into how to work around this if the specified source had complex filtering conditions on it for example.

Before

has_many :active_items, -> { where status: STATUS::ACTIVE }, class_name: 'Item', source: :items

After

has_many :active_items, -> { where status: STATUS::ACTIVE }, class_name: 'Item'

re/order refuses raw SQL

There were supposed to be deprecation warnings for this along the lines of “Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s)”. If you were lucky enough to see the deprecation warning, it told you to wrap “known safe” values in Arel.sql. Hopefully you were only passing in “known safe” values anyway so that works.

Before

reorder('rand()')

After

reorder(Arel.sql('rand()'))

Bonus: don’t exit transaction block with break

Another new deprecation warning in Rails 6.1! “Using `return`, `break` or `throw` to exit a transaction block is deprecated without replacement.” Now I was already warmed up (and it only occurred once in the codebase) so I fixed it along the way.

Before

ActiveRecord::Base.transaction do
  break if commit_changes
  raise ActiveRecord::Rollback
end

After

ActiveRecord::Base.transaction do
  unless commit_changes
    raise ActiveRecord::Rollback
  end
end