Upgrade Ruby on Rails Project 101

My strategy of upgrade Ruby 2.6.6 => 3.0.2 and Ruby 5.2 => 6.0.

William
WilliamDesk
6 min readOct 17, 2021

--

For my company needed, I started to upgrade my company’s project to the newest version:

Our Goal:

  • Ruby: 2.6.6 => 3.0.2
  • Rails: 5.2.6.4 => 6.0.4.1

Before starting to upgrade the project, it is a good way to read the official upgrading guide first.

I do not decide to upgrade to Rails 6.1, because Rails 6.1 doesn’t support the preload of some folders (e.g. settings folders) which we put the environment configuration by using the gem “settingslogic.

For more information, you can look at the link to the rails GitHub issue. If you have any solution for this, you can leave your comment. =)

Attention: You must prepare sufficient test coverage first for a safety upgrade!

2022–02–13 update

I finally upgraded to Rails 6.1.4.1, and here is my solution.

Add autoload_once_paths and require_relative for resolved problem.

Step 1: Create another new project by your target version.

  • Use rvm to install the ruby 3.0.2
  • Use gem install rails 6.0.4.1

Then, create a new folder to build a new RoR project.

If you don’t use JavaScript for view or the webpack, you can choose to skip the install webpack at first by using the command below. It will skip the huge config settings that you need to understand.

$ rails _6.0.4.1_ new {{my_project_name}} --skip-javascript

Step 2: Copy and compare the config file to your original project.

Here are my update files:

  1. Update .ruby_version to 3.0.2 (For rvm set the project version)
  2. Each config/environments/*.rb add the active_storage config setting.

3. Add config/initializers‎/content_security_policy.rb file

4. Update config/application.rb load_defaults to 6.0

5. Update the config/boot.rb

6. Update the config/spring.rb

7. Add the new file at config/storage.yml for active storage setting.

Step 3: Copy and compare the bin file to your original project.

  1. Overwrite the bin/bundle file.
  2. Add the begin..rescue condition section at the bin/rails and bin/rake files.

3. Update the bin/setup file for “APP_ROOT and “db:prepare section

4. Add the new bin/spring file.

5. Delete the bin/update file.

Step 4: Refactoring your gem file

The tips for renewing Gemfile:

1. Use the rails 6.0 project auto generating’s Gemfile for the base.

2. Delete your old Gemfile.lock file, because you may face some dependency error when try updating.

3. Remove gems version limitation settings, upgrade them at the same time.

4. Try bundle update them at your project.

5. If some deprecated hints showing when you update the gem, try to fix it and bundle update again.

For example:

The gem “sentry-raven” is deprecated, change it to the gem “sentry-ruby”.

The gem “pry-byebug” 3.8.0. It will be a warning like below, limit it to version 3.9.0.

warning: control_d_handler's arity of 2 parameters was deprecated (eval_string, pry_instance). Now it gets passed just 1 parameter (pry_instance)

The gem “awesome_rails_console” is not updated to resolve the Rails 6.0 warning. The warning is annoyed and not an essential gem, just remove it.

The gem “wicked_pdf”, is a PDF generation plugin. I upgraded this gem from 1.1.0 to 2.1.0, but it failed to access local files because the newest version needs to add another config to the set.

WickedPdf.config = {
exe_path: '/usr/local/bin/wkhtmltopdf',
enable_local_file_access: true # need to add this config
}

Note: If your project will build on different platforms, to avoid some error may happened, you can manually add some additional platform at your Gemfile.lock.

ex: arm64-darwin-20, java, ruby, x64-mingw32, x86-mingw32, x86-mswin32

Step 5: Try to start up your rails application

The important step of all, try to start the application to check is alive or dead by using the commands below:

If you saw failed to start up, some deprecated hint showing, using the browser access localhost:3000 failed (e.g. 500 error), figure out what happens by your .log file.

Step 6: Fix warnings and errors at the project

The most important change is positional arguments and keyword arguments will be separated in Ruby 3.0. Ruby 2.7+ will warn for behaviors but 3.0 will occur the error.

For more information:

Error 1: rspec-rails test error

For convenience, sometimes I wrote API test request code like:

These request test cases will face the error at Ruby 3.0+. For resolved it, we must separate the params and header to each hash key.

Also, if the request headers are declaring content-type as “application/json”, you must let the params to JSON format. For rails 6 and ruby 3, it is more strict.

Modify our test case code like:

Error 2: File typo error

For rails 5, my project has a controller called ProfilesController, we should be has a file called “profiles_controller.rb”.

But we don’t aware that this controller’s file name we typo to “profile_controller.rb”, but it still can work at Rails 5…

Rails 6 is better for detecting some minor errors than Rails 5.

Error 3: Our custom-made gem needs to update.

We use the gem we made called “service-caller”. The purpose is to let your project more convenient to handle some complex compute or series processing and not put them in the models.

The problem is the same for positional arguments and keyword arguments errors. so I fixed the method’s arguments, also updated the gem description.

Error 4: Overwrite to Rails 6 new methods.

At Rails 5, my project sometimes faced an error ActiveRecord::RecordNotUnique by using the “find_or_create_by” method.

According to the rails API guideline, it said:

Please note this method is not atomic, it runs first a SELECT, and if there are no results an INSERT is attempted. If there are other threads or processes there is a race condition between both calls and it could be the case that you end up with two similar records.

For resolved the error and followed the suggestion, we wrote a custom method “safe_find_or_create_by” method at the “application_record.rb”.

For Rails 6, we finally can remove the custom method and use the native “create_or_find_by method instead.

Attention: If using the “create_or_find_bymethod, make sure your params are unique keys at the table. Otherwise, it will create multi-record because the method “creates” first, and if RecordNotUnique it will trigger “find_by”.

If you are not searching by unique attributes, you can use our “safe_find_or_create_by” method, I modified it for suitable for Rails 6 and Ruby 3.

Error 5: ActiveRecord callback error

We use the ActiveRecord callback but it will occur error:

before_save :setup_options, on: :create

so I modify it this way instead:

before_create :setup_options

(2022–02–13) Error 6: URI.decode method is deprecate.

URI.decode method is deprecate.

Step 7: Update Dockerfile and .gitlab-ci.yml

For Dockerfile, I simply changed the base image ruby:2.6.6 to ruby:3.0.2 and it works perfectly!

For .gitlab-ci.yml, as same as Dockerfile, simply changed the rspec test stage image to ruby:3.0.2.

If you tried to use the newest bundler (e.g. 2.2.8), you may need to add some new script to your Dockerfile and .gitlab-ci.yml for installing the new bundler.

- gem install bundler:2.2.28# if your bundle install folder is using cache path at .gitlab-ci.yml. you may see the warning like:
## [DEPRECATED] The `--path` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions.
- bundle install --path vendor/bundle## use the new command instead
- bundle config set --local path 'vendor/bundle'
- bundle install

Conclusion

For actual use, I don’t feel Ruby 3.0 is faster than Ruby 2.6 for Rails Framework. Maybe can refer to some article for testing the performance.

For the official article the MJIT part. it clear said:

Although Ruby 3.0 significantly decreased the size of JIT-ed code, it is still not ready for optimizing workloads like Rails, which often spend time on so many methods and therefore suffer from i-cache misses exacerbated by JIT. Stay tuned for Ruby 3.1 for further improvements on this issue.

Although Ruby 3 seems not better than Ruby 2 for Rails now, upgrading to the new version is always a good choice because every version will face the EOL soon and must upgrade. Especially upgrade Rails 5 to Rails 6. It has a more strict writing style, detects minor errors, and new convenient native methods to develop your project.

Thanks for watching, hope this article helps you.

If you wanna to know how to upgrade your view for rails 6 and using webpack, now I updated at here: https://medium.com/williamdesk/upgrade-ruby-on-rails-project-101-phase-2-webpack-4d2e33fc49e7

--

--