Let’s upgrade to Rails 7

Ina Karatkova
4 min readJan 29, 2022

Or maybe you should give it a second thought.

I’m a conservative kind of person and I always have been. When a new version of a tool I use is released, I give it some time to sink in. And honestly, I just don’t like things breaking on my watch — and who does?

So when Rails 7 was released, I was both excited and terrified. Excited — because I’ve watched DHH’s demo and I instantly felt like I just have to try out this myself. Terrified — because I knew that in the team of mine we had that competition of being the first in everything. Spoilers here: we were actually the second ;)

Personally I would have waited much longer than a month from release date, but the task is out there and you have to complete it. If this is your case, I hope you find any of my tips helpful.

I will start with the most obvious thing: you might be just unable to upgrade.

Check your dependencies

First thing to do is just trying to perform a “dummy” upgrade.

gem 'rails', '~> 7.0'

Then check your dependencies:

bundle update
uh-oh, you know what red color means

Everything is red, what should I do? If you see something like this, this is not time to worry yet. Spot-check your dependencies for manual upgrades. If there are none, consider yourself lucky — you can stop reading from there :)

For those unlucky ones like me, there is an official upgrade guide that I’m going to be referring to. And here are some issues I faced in my journey.

Autoloading during initialization is deprecated

This is an easy one and is decently documented.

For an initializer like this in config/initializers/mode.rb:

Example.configuration.mode = ENV['MODE']

You’re not allowed to use classes/modules defined in your autoloaded paths like app/servicesanymore. Instead, to_prepare block is the new standard way to do it:

Rails.application.config.to_prepare do
Example.configuration.mode = ENV['MODE']
end

If you’re looking for other solutions, Rails introduced a new configuration option config.autoload_once_paths. It will allow you to use code from once autoloader in initializers. This one is not particularly useful for paths like app/services that you’re going to handle with main autoloader.

Adding a path to both of them creates a conflict and might result in an error like this:

lib/zeitwerk/loader.rb:475:in `block (3 levels) in raise_if_conflicting_directory': loader (Zeitwerk::Error)#<Zeitwerk::Loader:0x00007fa8ddccdd18 @tag="rails.main"> wants to manage directory /Users/fayris/rails-7-example/app/events, which is already managed by #<Zeitwerk::Loader:0x00007fa8ddccca58 @tag="rails.once">

Webpacker 🌅 or Modern web apps without Javascript

… bundling or transpiling :)

(please tell me I’m not the only one to misread the title)

Webpacker has reached its final chapter. It’s official now. Rails gives you a plenty of upgrade options, as always, the best one strongly depends on your application. Ours is relatively small and not very heavy on Javascript(and everyone could not care less about its handling).

Option 1: You can continue using Webpacker 5.x as is — so I am told — though you won’t be receiving upgrades, except for security issues.

But wiping it out is so much more fun, right?

Option 2: Start using jsbundling-rails with webpack.

I’ve never really liked configuring webpack in the first place. Normally you would do this via copy-pasting webpack.config.js file. But it is not the case for Webpacker. In a Rails-way it is handling configs with webpacker.yml file(and environments in config/webpack/folder), which I’ve always found very confusing.

Switching to jsbundling-rails is mentioned as a first option to proceed for existing apps. In addition, I went for adding cssbundling-rails for handling CSS.

# gem 'sass-rails'
# gem 'webpacker'
gem 'jsbundling-rails'
gem 'cssbundling-rails'
gem 'sprockets-rails'

Be sure to require sprockets in application.rb if you don’t do it yet:

require 'sprockets/railtie'

Following upgrade guides from jsbundling-rails and cssbundling-rails will result in removing ~5000 lines from your codebase. Sounds wonderful, doesn’t it?

Option 3: Start using Import Maps

This will be the new standard way for future releases and you can read this wonderful piece of documentation on how to proceed with them.

Docker alpine incompatibility issues

If you’re not using Docker for production releases, you can skip this part.

By the time your tests are all green and app runs locally, this is might not be over yet.

I faced an issue trying to build a docker image for my app:

#21 80.59 NoMethodError: undefined method `full_name' for nil:NilClass
#21 80.59 /usr/local/lib/ruby/3.0.0/bundler/installer/parallel_installer.rb:124:in `block (2 levels) in check_for_unmet_dependencies'
#21 80.59 /usr/local/lib/ruby/3.0.0/bundler/installer/parallel_installer.rb:123:in `each'
...#21 80.59 /usr/local/lib/ruby/3.0.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'
#21 80.59 /usr/local/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/libexec/bundle:37:in `<top (required)>'
#21 80.59 /usr/local/bin/bundle:23:in `load'
#21 80.59 /usr/local/bin/bundle:23:in `<main>'

This one is my personal favorite 🔫. And mainly is the reason why this article exists.

After doing some hours of googling, the problem was located in bundler 2.2.32 release. I managed to fix this by upgrading bundler directly in Dockerfile:

ENV BUNDLER_VERSION 2.3.6
RUN gem install bundler -v $BUNDLER_VERSION

Alternatively, you can upgrade Ruby version to 3.1, which is shipped with bundler 2.3+.

As always, I hope that reading this article was of any use to you. Stay safe and updated!

--

--