paint-brush
How We Upgraded to Ruby on Rails 7.1: A Smooth Transitionby@aleksandrov
2,746 reads
2,746 reads

How We Upgraded to Ruby on Rails 7.1: A Smooth Transition

by Igor AlexandrovOctober 10th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Rails 7.1 was released on October 5th. One of our projects, OneTribe, was upgraded to the new major release within one day of my holidays. In this guide I covered the upgrade process.
featured image - How We Upgraded to Ruby on Rails 7.1: A Smooth Transition
Igor Alexandrov HackerNoon profile picture

On October 5th, Rails 7.1 has been released. In this article, I will show you how I upgraded one of our projects, OneTribe (https://onetribe.team/), to the new major release within one day of my holidays.

Prerequisites

OneTribe runs Ruby 3.2.2 and Rails 7.0.7. We host code on GitHub and deploy with GitHub Actions and Kamal.

Dependencies Update

I started with upgrading rails in the application Gemfile.

# Gemfile
gem 'pg_party'

# ...

gem 'rails', '~> 7.1.0'
gem 'rails-i18n'

After this, you can try to run bundle update rails and depending on your other dependencies, it will either succeed or fail. In my case, it failed because pg_party does not support Rails 7.1 yet.


The good news is that Ryan (author and maintainer of PgParty), with help from me and one more developer, managed to add support for AR 7.1 within one evening, but didn’t release the new version of the gem yet, so I am going to take the code from the main branch.

# Gemfile
gem 'pg_party', github: 'rkrage/pg_party'

After solving the issue with the PgParty bundle, it succeeded. I was almost sure that the application was ready for the next phase of the update, but I remembered that Rails 7.1 introduced composite primary keys for ActiveRecord support out of the box (you can find a full list of new features and improvements here: https://rubyonrails.org/2023/10/5/Rails-7-1-0-has-been-released).


In OneTribe, we’ve used the gem called composite_primary_keys (https://github.com/composite-primary-keys/composite_primary_keys). It was rather easy; I replaced self.primary_keys = <array> call with self.primary_key = <array> and removed composite_primary_keys from the application bundle.

class TimeTracking::Entry < TimeTrackingRecord
	# with composite_primary_keys
	# self.primary_keys = :id, :date

	# with ActiveRecord 7.1
  self.primary_key = [:id, :date]

  range_partition_by :date
	
	# ...
end


After this, I updated Sidekiq to 6.5.11, which added support to Rails 7.1, and tried to start the application. However, I got an error.

ActionText in Rails 7.1 introduced a new HTML 5 sanitizer, which is now default and falls back to HTML 4. As a result of this change, ActionText::ContentHelper.allowed_tags and .allowed_attributes are applied at runtime and return nil during application load.

In our case, I don’t need additional tags to be added to ActionText allowed tags configuration, and I removed the initializer.


I ran the application test suit, and all specs passed successfully, meaning I can start rails app:update.

Application Configuration Update

Rails has a special task rails app:update that can help you to update application configuration in an interactive mode. I used VS Code for development and wanted to use its merge tool, so I specified the THOR_MERGE constant before running the command THOR_MERGE="code --wait" ./bin/rails app:update and used the merge tool to track changes over files.


One notable change in the 7.1 release is that config.cache_classes option has been replaced with config.enable_reloading that has an inverted meaning. Both options will still work for backward compatibility, but I suggest to replace config.cache_classes = false with config.enable_reloading = true in environments configuration files.


Another new configuration option of Rails 7.1 is config.action_controller.raise_on_missing_callback_actions . I always try to reduce the number of callbacks used in controllers since they can be extremely hard to maintain. However, there may be situations when controller callbacks fit well (e.g., authorization check). Conditional callbacks are an even bigger hell. Before Rails 7.1, if you defined a condition with only or except option for an action that does not exist, Rails would say nothing. Now, you can set config.action_controller.raise_on_missing_callback_actions=true for test and development environments, and Rails will raise an exception. You can read more info in the railties changelog: https://github.com/rails/rails/blob/7-1-stable/railties/CHANGELOG.md.


In my Kamal guide, I showed how to create an initializer, which will deal with a load balancer that terminates SSL (e.g., AWS ALB). Rails 7.1 introduced config.assume_ssl=true option. This means that in environments that work behind a load balancer (usually production, staging, etc.), you have to enable it and delete the initializer that you used in Rails 7.0.

# config/environments/production.rb

# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
config.assume_ssl = true

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false

After you merge all your configuration files and the interactive tool is finished, some tweaks can still be done.


First of all, you may notice that you will have a new file in your code base called new_framework_defaults_7_1.rb. It contains all Rails 7.1 default params commented, so you can enable them one by one. Another option is to change config.load_defaults in config/application.rb file to have 7.1 value and delete new_framework_defaults_7_1.rb , this will enable all options at once.


Last but not least is config.autoload_lib option. Before 7.1, you probably had something similar to the code below in your application that uses Zeitwerk.

# config/application.rb

module OneTribe
  class Application < Rails::Application
    config.eager_load_paths << config.root.join("lib")

    Rails.autoloaders.main.ignore(
      config.root.join("lib").join("assets"),
      config.root.join("lib").join("tasks"),
      config.root.join("lib").join("middleware"),
    )

    # ...
  end
end

The code above added lib folder to both eager load and autoload paths and excluded from autoload paths from lib that didn’t contain Ruby code or should not be reloaded or eager-loaded.

With Rails 7.1 and config.autoload_lib , everything becomes easier. You just tell Rails to autoload everything in lib and provide a list of folders that should not be reloaded and eager-loaded.

# config/application.rb

module OneTribe
  class Application < Rails::Application

    # Please, add to the `ignore` list any other `lib` subdirectories that do
    # not contain `.rb` files, or that should not be reloaded or eager loaded.
    # Common ones are `templates`, `generators`, or `middleware`, for example.
    config.autoload_lib(ignore: %w[assets tasks middleware])

		# ...
  end
end

Thats all. I reran my specs to ensure I didn’t break anything. Of course, I ran Rubocop to ensure all the changes fit the code style, created PR, and successfully upgraded OneTribe to the new Rails version.

Conclusions

Rails 7.1 gives you many new features and abilities (https://rubyonrails.org/2023/10/5/Rails-7-1-0-has-been-released), but as usual, the upgrading process is smooth and straightforward.

Thanks to all the contributors, the core team, and those who tested the release candidate and beta builds!


Also published here.