Real Life Examples: Upgrade Rails 5.2 to Rails 6.0.
As it's been some time since Rails 6 was released (officially on 6th of August 2019), it's about time to update your application to this version of the framework. In this tutorial, I will show you how to easily do it, quickly solve potential problems along the way and take full advantage of some new features coming with Rails 6!
This post is about updating a real, existing application - tiomsu.com. Tiomsu is our pet-project, developed mostly after hours, which allows you to store notes and links in one place. And share them within your organization. And set permissions. And comment on them. And many other things. But enough of that, let's just get to it and get the job done.
New features in Rails 6.0
Rails 6.0 adds a few nice features, e.g. Action Mailbox, which I would like to add to Tiomsu, currently running Rails 5.2. So let's make the update first as without this, we won't be able to enjoy the benefits coming with the new Ruby on Rails.
We start with updating rails version in Gemfile:
gem 'rails', '~> 6.0.1'
I suggest defining the latest version and locking updates to patch version (this is what ~>
does - it allows all versions within 6.0.X
where X >= 1).
Having that done, run $ bundle update
Upgrade Rails 5.2 to 6.0: Gems
In our case this didn't just succeed, this is the output:
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies......
Bundler could not find compatible versions for gem "actionpack":
In Gemfile:
best_in_place (~> 3.0.1) was resolved to 3.0.3, which depends on
actionpack (>= 3.2)
haml-rails was resolved to 1.0.0, which depends on
actionpack (>= 4.0.1)
inherited_resources was resolved to 1.9.0, which depends on
has_scope (~> 0.6) was resolved to 0.7.2, which depends on
actionpack (>= 4.1)
inherited_resources was resolved to 1.9.0, which depends on
actionpack (>= 4.2, < 5.3)
omniauth-rails_csrf_protection was resolved to 0.1.2, which depends on
actionpack (>= 4.2)
rails (~> 6.0.1) was resolved to 6.0.1, which depends on
actionpack (= 6.0.1)
rails-controller-testing was resolved to 1.0.2, which depends on
actionpack (~> 5.x, >= 5.0.1)
rspec-rails was resolved to 3.8.2, which depends on
actionpack (>= 3.0)
simple_form (> 3.5) was resolved to 4.0.1, which depends on
actionpack (>= 5.0)
And there are a few things, that need to be sorted out. Bundler is informing us, that the update cannot be performed, because there are some dependency conflicts. Now all the fun is to identify gems, that are blocking us from the update. We can spot two occurrences:
inherited_resources was resolved to 1.9.0, which depends on
actionpack (>= 4.2, < 5.3)
so inherited_resoures
depends on actionpack < 5.3
. Also there is
rails-controller-testing was resolved to 1.0.2, which depends on
actionpack (~> 5.x, >= 5.0.1)
rails-controller-testing
depends on actionpack ~> 5.x
(and with Rails 6 we'll update actionpack
to 6.0.x)
Also best_in_place
is probably something, that could be removed, but let's just leave it for now and call bundle update again, this time with two additional gems, whose dependencies stopped us the first time
$ bundle update rails rails-controller-testing inherited_resources
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies.......
Bundler could not find compatible versions for gem "activerecord":
In Gemfile:
ancestry was resolved to 3.0.2, which depends on
activerecord (>= 3.2.0)
annotate (>= 2.6.0) was resolved to 2.7.4, which depends on
activerecord (>= 3.2, < 6.0)
apartment was resolved to 2.2.0, which depends on
activerecord (>= 3.1.2, < 6.0)
friendly_id (>= 5.0.0) was resolved to 5.2.4, which depends on
activerecord (>= 4.0.0)
kaminari was resolved to 1.1.1, which depends on
kaminari-activerecord (= 1.1.1) was resolved to 1.1.1, which depends on
activerecord
rails (~> 6.0.1) was resolved to 6.0.1, which depends on
activerecord (= 6.0.1)
ransack was resolved to 2.1.1, which depends on
activerecord (>= 5.0)
This time the bundler is communicating that it "could not find compatible versions for gem "activerecord". Here clearly annotate and apartment gems have dependency set to < 6.0. Let's call update again adding those two:
$ bundle update rails rails-controller-testing inherited_resources annotate apartment
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies........
Bundler could not find compatible versions for gem "railties":
In Gemfile:
best_in_place (~> 3.0.1) was resolved to 3.0.3, which depends on
railties (>= 3.2)
coffee-rails (~> 4.2.0) was resolved to 4.2.2, which depends on
railties (>= 4.0.0)
redactor2_rails was resolved to 0.1.4, which depends on
devise was resolved to 4.7.1, which depends on
railties (>= 4.1.0)
dotenv-rails was resolved to 2.5.0, which depends on
railties (>= 3.2, < 6.0)
factory_bot_rails was resolved to 4.8.2, which depends on
railties (>= 3.0.0)
haml-rails was resolved to 1.0.0, which depends on
railties (>= 4.0.1)
inherited_resources was resolved to 1.11.0, which depends on
railties (>= 5.0, < 6.1)
jquery-rails was resolved to 4.3.3, which depends on
railties (>= 4.2.0)
jquery-ui-rails was resolved to 6.0.1, which depends on
railties (>= 3.2.16)
rails (~> 6.0.1) was resolved to 6.0.1, which depends on
railties (= 6.0.1)
sass-rails (~> 5.0) was resolved to 5.0.7, which depends on
railties (>= 4.0.0, < 6)
This time conflicts with railties - dotenv-rails
and sass-rails
have conflicting dependencies, the latter is locked in our gemfile to ~> 5.0
. I think it's safe to remove it. After doing so, let's run update again:
$ bundle update rails rails-controller-testing inherited_resources annotate apartment sass-rails dotenv-rails
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies........
Using rake 12.3.3
...
Note: apartment version regressed from 2.2.0 to 0.24.3
Bundle updated!
Removing gems
Ok, this time it worked. But wait - take a look at second to last line - apartment gem regressed. This is not good - even if it got updated, it will probably cause problems later. This is the time, to check its repo. Fortunately, the last commits enable Rails 6.0 and added tests covering Rails 6. I think it will be safe to fetch this gem directly from the repo, but let's lock it to a specific commit:
gem 'apartment', git: 'https://github.com/influitive/apartment', ref: 'f266f73'
(ref: 'f266f73'
informs bundler to fetch from git repo specific commit, not latest master)
After taking a look at the above problems I've also noticed a few gems, that probably could be removed: redactor2_rails
and best_in_place
. It's worth doing so in order to leave the code in better shape than when we started.
Having that ready run rails app:update
that will try to automatically apply new changes:
$ rails app:update
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
>> Coffee::Rails::TemplateHandler.call(template)
To:
>> Coffee::Rails::TemplateHandler.call(template, source)
(called from <main> at /Users/mlitwiniuk/Sites/r5/tiomsu/config/application.rb:7)
identical config/boot.rb
exist config
conflict config/routes.rb
Overwrite /Users/mlitwiniuk/Sites/r5/tiomsu/config/routes.rb? (enter "h" for help) [Ynaqdhm]
In case of conflicts, it will always ask you what you'd like to do with files. Overwriting is not the best idea, let's go with the diff first (option d
):
- require 'constraints/subdomain_required'
+ Rails.application.routes.draw do
- require 'sidekiq/web'
+ # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
- Tiomsu::Application.routes.draw do
-
Clearly, the beginning of routes file differs. Let's ignore the Rails suggestion. At the same time, I'll change Tiomsu::Application.routes.draw
to Rails.application.routes.draw
in the editor.
I ignored all the suggested changes to config/application.rb
, config/environments/development.rb
, config/environments/test.rb
, config/initializers/assets.rb
, config/initializers/content_security_policy.rb
but accepted suggestions to config/puma.rb
, config/spring.rb
, bin/setup
and to config/environments/production.rb
(be super cautious about the last one).
Final issues: manifest.js, deprecation warning
At the very end, this command informed me about missing the manifest.js file:
Sprockets::Railtie::ManifestNeededError: Expected to find a manifest file in `app/assets/config/manifest.js`
But did not, please create this file and use it to link any assets that need to be rendered by your app:
Example:
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
and restart your server
so I created one with the following content (file app/assets/config/manifest.js
)
//= link_tree ../images
//= link_tree ../fonts
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
I also noticed a deprecation warning:
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
>> Coffee::Rails::TemplateHandler.call(template)
To:
>> Coffee::Rails::TemplateHandler.call(template, source)
Fortunately, all I needed to do was to update the coffee-rails
gem.
To check if everything is fine, I launched my development environment by running foreman start -f Procfile.dev
and visited prograils.tiomsu.test
(I'll explain tools I'm using in my dev environment in another post).
The application was launched successfully, but when visiting it, I was greeted with following error:
Blocked host: prograils.tiomsu.test
To allow requests to prograils.tiomsu.test, add the following to your environment configuration:
config.hosts << "prograils.tiomsu.test"
It's because Rails 6 introduced protection against DNS rebinding attacks by introducing a list of permitted hosts. You can read more about this in the PR description and on Wiki.
I've sorted this out by adding config.hosts << '.tiomsu.test'
to development.rb, config.hosts << '.tiomsu.com'
to production.rb and config.hosts << '.prograils.io'
to test.rb (prograils.io
is domain, that always resolves to IP 127.0.0.1 )
After doing that and visiting the page, I ran into yet another issue: wrong number of arguments (given 3, expected 4)
in line calling ransack
- updating ransack gem solved this (when using ransack and updating rails you can run into all sorts of strange problems, updating ransack in mosts cases solves those problems).
Visting page again raised the SassC::SyntaxError
exception:
Error: Undefined variable: "$purple".
on line 234 of app/assets/stylesheets/prograils.scss
It's because prograils.scss is in root and it's parsed by sprockets (thanks to the created app/assets/config/manifest.js
file). The issue here is that this file itself is imported and it's making use of variables imported from elsewhere. The solution is to move this file into a subdirectory and reference it properly (that is instead of @import "prograils";
do @import "custom/prograils";
).
After doing that, the app works fine again. But we're not quite there yet. Open the config/application.rb
and check the line with config.load_defaults
. It should be config.load_defaults 6.0
to make the update complete. Before changing anything, get familiar with the Rails 6 changelog and check config/initializers/new_framework_defaults_6_0.rb
.
Wrapping up
That should be it. I hope showing how a real, working application is updated to Rails 6.0 will make the entire process easier for you.
I also hope you won't run into bigger issues and the entire update process will be as painless for you, as it was for me (because it really was).
Until the next time!