---
title: "How we upgraded from Rails 6 to Rails 7"
description: "How we upgraded from Rails 6 to Rails 7"
canonical_url: "https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7"
markdown_url: "https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7.md"
---

# How we upgraded from Rails 6 to Rails 7

How we upgraded from Rails 6 to Rails 7

- Author: Abhishek T
- Published: September 20, 2022
- Categories: Rails, Rails 7

Recently, we upgraded all [neeto](https://www.neeto.com/) products to Rails 7
using
[Rails upgrade guide](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html).

Here are the issues we faced during the upgrade.

### Migrating to Active Record Encryption

This was the biggest challenge we faced during the upgrade. For encrypting
columns, we had used the
[attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) gem. However
Rails 7 came with
[Active Record Encryption](https://guides.rubyonrails.org/active_record_encryption.html).
So we needed to decrypt the records in the production database and encrypt them
using Active Record Encryption. We found that "attr_encrypted" gem was
incompatible with Rails 7. So the only option was to remove the "attr_encrypted"
gem and decrypt the records using a script. We used the following method to
decrypt the records.

```rb
def decrypted_attribute(attribute_name, record)
  value = record.send(attribute_name)
  return if value.blank?

  value = Base64.decode64(value)

  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  cipher.decrypt

  cipher.key = Rails.application.secrets.attr_encrypted[:encryption_key]
  cipher.iv = Base64.decode64(record.send(:"#{attribute_name}_iv"))

  cipher.auth_tag = value[-16..]
  cipher.auth_data = ""

  cipher.update(value[0..-17]) + cipher.final
end
```

### Broken images in Active Storage

After the upgrade, we started getting broken images in some places. This
happened for Active Storage links embedded in Rich Text. After some debugging,
we found that we were getting incorrect Active Storage links because of a change
in the key generation algorithm. The following configuration was loading images
using the old algorithm.

```rb
config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
```

Since the new algorithm provides more security, we decided to migrate the links
instead of using the old algorithm. We used the following code to migrate the
old link to the new valid link.

```rb
# Usage:
# text_with_new_links = ActiveStorageKeyConverter.new(text_with_old_links).process
# If no links are there to replace, the original text will be returned as it is.

class ActiveStorageKeyConverter
  def initialize(text)
    @text = text
  end

  def process
    replace(@text)
  end

  private
    def convert_key(id)
      verifier_name = "ActiveStorage"
      key_generator =  ActiveSupport::KeyGenerator.new(Rails.application.secrets.
      secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1)
      key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
      secret = key_generator.generate_key(verifier_name.to_s)
      verifier = ActiveSupport::MessageVerifier.new(secret)

      ActiveStorage::Blob.find_by_id(verifier.verify(id, purpose: :blob_id))
      .try(:signed_id) rescue nil
    end

    def replace(text)
      keys = text.scan(URI.regexp).flatten.select{|x|
      x.to_s.include? ("rails/active_storage")}.map{|x| x.split("/")[-2]}
      keys.each do |key|
        new_key = convert_key(key)
        text = text.gsub(key, new_key) if new_key
      end
      text
    end
end
```

Following one time rake task was used to update the Active Storage links in the
`content` column of `Task` model:

```rb
desc "Update active storage links embedded in rich text to support in rails 7"
task migrate_old_activestorage_links: :environment do

  table_colum_map = {
    "Task" => "content",
  }
  match_term = "%rails/active_storage%"

  table_colum_map.each do |model_name, column_name|
    model_name.to_s.constantize.where("#{column_name} ILIKE ?", match_term).find_each do|row|
      row.update_column(column_name, ActiveStorageKeyConverter.new(row[column_name]).process)
    end
  end
end
```

### Test failures with the mailer jobs

After upgrading to Rails 7, tests related to mailers started to fail. This was
because the mailer jobs were enqueued in the `default` queue instead of
`mailers`. We fixed this by adding the following configuration.

```rb
config.action_mailer.deliver_later_queue_name = :mailers
```

### Autoloading during initialization failed

After the upgrade, if we startthe Rails sever then we were getting the following
error.

```
$ rails s
=> Booting Puma
=> Rails 7.0.3.1 application starting in development
=> Run `bin/rails server --help` for more startup options
Exiting
/Users/BB/Neeto/neeto_commons/lib/neeto_commons/initializers/session_store.rb:13:in
`session_store': uninitialized constant #<Class:NeetoCommons::Initializers>::ServerSideSession     (NameError)

    ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
                                                                ^^^^^^^^^^^^^^^^^
   from /Users/BB/Neeto/neeto-planner-web/config/initializers/common.rb:10:in `<main>'
```

That error was coming from our internal `neeto-commons` initializer called
`session_store.rb`. The code looked like this.

```rb
#session_store.rb

module NeetoCommons
  module Initializers
    class << self
      def session_store
        Rails.application.config.session_store :active_record_store,
          key: Rails.application.secrets.session_cookie_name, expire_after: 10.years.to_i

        ActiveRecord::SessionStore::Session.table_name = "server_side_sessions"
        ActiveRecord::SessionStore::Session.primary_key = "session_id"
        ActiveRecord::SessionStore::Session.serializer = :json
        ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
      end
    end
  end
end
```

In order to fix the issue we had to put the last statement in a block like shown
below.

```rb
Rails.application.config.after_initialize do
 ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
end
```

### Missing template error with pdf render

After the Rails 7 upgrade the following test started failing.

```rb
def test_get_task_pdf_download_success
  get api_v1_project_section_tasks_download_path(@project.id, @section, @task, format: :pdf)

  assert_response :ok

  assert response.body.starts_with? "%PDF-1.4"
  assert response.body.ends_with? "%EOF\n"
end
```

The actual error is `Missing template api/v1/projects/tasks/show.html.erb`.

In order to fix it we renamed the file name from `/tasks/show.html.erb` to
`/tasks/show.pdf.erb`. Similarly we changed the layout from
`/layouts/pdf.html.erb` to `/layouts/pdf.pdf.erb`.

Initially the controller code looked like this.

```rb
format.pdf do
  render \
    template: "api/v1/projects/tasks/show.html.erb"
    pdf: pdf_file_name,
    layout: "pdf.html.erb"
end
```

After the change the code looked like this.

```rb
format.pdf do
  render \
    template: "api/v1/projects/tasks/show",
    pdf: pdf_file_name,
    layout: "pdf"
end
```

### Open Redirect protection

After the Rails 7 upgrade the following test started failing.

```rb
def test_that_users_are_redirected_to_error_url_when_invalid_subdomain_is_entered
  invalid_subdomain = "invalid-subdomain"

  auth_subdomain_url = URI(app_secrets.auth_app[:url].gsub(
   app_secrets.app_subdomain, invalid_subdomain))

  auth_app_url = app_secrets.auth_app[:url]

  host! test_domain(invalid_subdomain)
  get "/"

  assert_redirected_to auth_app_url
end
```

In the test we are expecting the application to redirect to `auth_app_url` but
we are getting `UnsafeRedirectError` error for open redirections. In Rails 7 the
new Rails defaults
[protects](https://api.rubyonrails.org/v7.0.3.1/classes/ActionController/Redirecting.html#method-i-redirect_to-label-Open+Redirect+protection)
applications against the
[Open Redirect Vulnerability](https://cwe.mitre.org/data/definitions/601.html).

To allow any external redirects we can pass `allow_other_host: true`.

```rb
redirect_to <External URL>, allow_other_host: true
```

Since we use open redirection in many places we disabled this protection
globally.

```rb
config.action_controller.raise_on_open_redirects = false
```

### Argument Error for Mailgun signing key

After the upgrade, we started getting the following error in production.

```
>> ArgumentError: Missing required Mailgun Signing key. Set action_mailbox.mailgun_signing_key
in your application's encrypted credentials or provide the MAILGUN_INGRESS_SIGNING_KEY
environment variable.

```

Before Rails 7, we used the `MAILGUN_INGRESS_API_KEY` environment variable to
set up the
[Mailgun signing key](https://guides.rubyonrails.org/action_mailbox_basics.html#mailgun)
. In Rails 7, that is changed to `MAILGUN_INGRESS_SIGNING_KEY`. So we renamed
the environment variable to fix the problem.

## Links

- [Human page](https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7)
