Upgrade from Rails 6.1.7 to Rails 7.0
Published: 2023-01-31 23:44:39

Rails Upgrade

Upgrade from Rails 6.1.7 to Rails 7.0 in an API type application.

Before the upgrade the specs are:

- I have a Mac M1 MacOs ventura
- Rails 6.1.7
- Ruby 2.7.3
- PostgreSQL 13.0 client for the mac  
- Redis server 6.2.4

## Step by step upgrade:

- There is a recommendation regarding not upgrading from a very old version to a very recent one, for example, it is not recommended to upgrade from Rails 5 to Rails 7, in this case it does not apply, as we are moving from 6.1 to 7 which is fine.
- Copy and paste our Gemfile.lock into [RailsBumb](https://railsbump.org/) so we can check which of our gems are going to generate errors or problems.
- Use the gem "next_rails" to check which gems we need to update, when we have it installed we will just do a `bundle_report compatibility --rails-version=desired_version`, after this we can update the gems that appear in the report.
- Updating gems:
  - If the report asks us to update gems that are part of Rails such as `ActionMailer` or `ActionText` these will be updated with the version change to Rails 7. If the gems are installed by us, obviously we must do the update.
  - It is not advisable to have a fixed version of a gem in our `Gemfile` so it is a good time to remove that.
- After updating the gems we can now in our `Gemfile` update the Rails version and run `bundle update`.
- When the update is done the first thing to do is to change the `load_defaults` to Rails 7 in our `config/application.rb` file.
- In my case I use `Spring` this gem is on its 2.1.1 version for some reason `next-rails` didn't take it into account, but it should be updated to a `3X` version.
  - When I tried to update the gem it told me that I could not because it was locked, for this what I did was to delete my Gemfile.lock.
  - Then in `config/environments/test.rb` I left it set to `false`.
- Configure `Zeitwerk`.
  - Zeitwerk is the only way in Rails 7 to safely load files and avoid overuse of `require`.
  - As you have already made the switch to Rails 7 in the application in `config/application.rb` you should remove the `config.autoloader` line.
  - Check if everything is already running fine with `bin/rails runner 'p Rails.autoloaders.zeitwerk_enabled?'` should return `true` or the respective warnings.
- Encryption
  - At this point, when starting the server I was getting an `ActiveSupport::MessageEncryptor::InvalidMessage` error because of the encryption.
  - In the API the attributes are encrypted following this [tutorial](https://pawelurbanek.com/rails-secure-encrypt-decrypt), but when updating Rails the encryption changes, because Rails 7 uses SHA256 while in the previous versions SHA1 is used, but this protocol is obsolete, to solve this you must specify to continue encrypting with SHA1 while then the change is made to use the encrypted attributes of Rails 7, to solve this it worked for me to add the `hash_digest_class` like this:
````Ruby
class EncryptionService
  KEY = ActiveSupport::KeyGenerator.new(
    Rails.application.credentials[:secret_key_base],
    hash_digest_class: OpenSSL::Digest::SHA1
  ).generate_key(
    Rails.application.credentials[:ENCRYPTION_SERVICE_SALT],
    ActiveSupport::MessageEncryptor.key_len
  ).freeze
```

- When running the tests I found the error `RuntimeError: Foreign key violations found in your fixture data`, this is because in previous versions you could have `fixtures` with a `missing association`, this will no longer be possible in Rails 7.
  - One option is in `application.rb` add the line `config.active_record.verify_foreign_keys_for_fixtures = false`.
  - The other option is to fix the errors in the fixtures (which is recommended), to detect which tables we have wrong, first we reset and load the test database and then enter in the console like this:
```
RAILS_ENV=test bin/rails db:reset
RAILS_ENV=test bin/rails db:fixtures:load
RAILS_ENV=test bin/rails c 
```
  - Then we run this query to identify if there are tables that are missing an association so we can correct the ``fixtures`` and the error will be fixed:
```
ActiveRecord::Base.connection.execute(<<~SQL)
  do $$$
    declare r record;
  BEGIN
  FOR r IN (
    SELECT FORMAT(
      'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I''; ALTER TABLE %I VALIDATE CONSTRAINT %I;',
      constraint_name,
      table_name,
      constraint_name
    ) AS constraint_check
    FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
  )
    LOOP
      EXECUTE (r.constraint_check);
    END LOOP;
  END;
  $$;
SQL
```
- The following error to be corrected was `ActionDispatch::Request::Session::DisabledSessionError: Your application has sessions disabled. To write to the session you must first configure a session store`
  - In `config/application.rb`, I had the line `config.middleware.use ActionDispatch::Cookies` to solve this solution from this [thread of Github](https://github.com/waiting-for-dev/devise-jwt/issues/235) worked for me.

![Github thread](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vp4io2vzxb010h89yfnn.png)


This was the whole process in my case. The support guides and documentation for this update were:


- [Next Rails](https://github.com/fastruby/next_rails) 
- [Documentación FastRuby.io](https://www.fastruby.io/blog/rails/upgrades/upgrade-rails-6-1-to-7-0.html)
- [Guías de Rails](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-6-1-to-rails-7-0)
- [Principales cambios de Rails 7](https://edgeguides.rubyonrails.org/7_0_release_notes.html) 
- [Cambio a Zeitwerk](https://guides.rubyonrails.org/classic_to_zeitwerk_howto.html)
- [Más de Zeitwerk ](https://medium.com/cedarcode/understanding-zeitwerk-in-rails-6-f168a9f09a1f)