Errors I faced while upgrading Rails 6.1 to 7.0.4

These are the errors which were not mentioned in Rails upgrade guide.

Introduction

I recently attempted to upgrade our long running Rails application from 6.1 to 7.0.4. On the way, I found various errors and failing tests which I have documented down here as a blog post. Feel free to skip to the part you are interested in using the Table Of Contents.

1. ActiveStorage: Cannot generate URL

 Cannot generate URL for shiva_passport.jpg using Disk service, please set ActiveStorage::Current.url_options. : Cannot generate URL for shiva_passport.jpg using Disk service, please set ActiveStorage::Current.url_options.

Explanation

The error message suggests that you are trying to generate a URL for a file called "shiva_passport.jpg" using the Disk service, but Active Storage is unable to generate the URL because the URL options are not set.

Solution

# in test.rb and development.rb
# make sure to update host according to your need
Rails.application.routes.default_url_options = {protocol: "http", 
host: "example.com", port: 80}

If this ^ does not solve your issue then try the suggestions below

class ApplicationController < ActionController::Base
  # Fixes error
  # Cannot generate URL for filename.jpg using Disk service,
  # please set ActiveStorage::Current.url_options.
  # This mainly affects development environment.
  if Rails.env.development?
    before_action do
      ActiveStorage::Current.url_options = {protocol: request.protocol, host: request.host, port: request.port}
    end
  end

or simply add

class ApplicationController < ActionController::Base
  include ActiveStorage::SetCurrent
  ...

In Rspec, if the suggestions above do not work, try this:-

module ActiveStorageCurrentUrlHelper
  def set_url_options    
    # put this somewhere just before where you call methods to 
    #  generate blob/attachment URL
    ActiveStorage::Current.url_options = {host: 'www.example.com'} if Rails.env.test?
  end
end 
# example

  def attachment_url(upload_obj: upload, params: {})
    # see here
    ActiveStorageCurrentUrlHelper.set_url_options

    upload_obj.url(**params)
  end
# test.rb
# this does not seem to solve the issue, though its the way to set 
#  url_options for routes in Rails 7
config.default_url_options = { host: 'example.com', protocol: 'http' }

Benefit of ActiveStorage::Current.url_options

This is an API you can reap the benefit of when you need to decide Asset’s domain/URL based on request or User parameters.


2. Please use #media_type instead.

 Rails 7.1 will return Content-Type header without modification. If you want just the MIME type, please use `#media_type` instead. (called from preload at /Users/XX/Projects/XX/XX/app/modules/request_caching/lib/request_caching/request_cache_filter_helper.rb:8)

Solution

Use request.media_type instead of request.content_type if you are expecting "text/csv" as a value because now it also includes the charset value.

Also, make sure you are using request.media_type and expecting "text/csv" because now

request.content_type #=> "text/csv; header=present; charset=utf-16"
request.media_type   #=> "text/csv"

For more info please see this guide.

But, if you want to explicitly want to use content_type then you can silence the deprecation warning by updating config like

# application.rb
# if you wish previous behaviour
config.action_dispatch.return_only_request_media_type_on_content_type = true

3. undefined method ‘raw_filter’ For ActiveSupport::Callback


#application undefined method raw\_filter' for #[ActiveSupport::Callbacks::Callback:0x0000000111021ec8](ActiveSupport::Callbacks::Callback:0x0000000111021ec8) Did you mean? filter 
tagged allocation ticket error \*\*&gt; undefined method \`raw\_filter' for #[ActiveSupport::Callbacks::Callback:0x0000000111021ec8](ActiveSupport::Callbacks::Callback:0x0000000111021ec8) Did you mean? filter

Explanation

It’s possible that the raw_filter error is related to the activerecord-import gem, which is a gem that provides a fast way to bulk insert data into an ActiveRecord database.

The activerecord-import gem uses callbacks to perform bulk inserts, and these callbacks may be triggering the raw_filter method, which is not defined in the current version of Rails.

To resolve this issue, you can try updating the activerecord-import gem to the latest version, which may include a fix for this issue. Alternatively, you can try disabling any callbacks related to the activerecord-import gem and see if that resolves the issue.

To disable callbacks for a specific model, you can use the without_callback method. For example:

MyModel.without_callback(:save, :after, :bulk_insert) do
  MyModel.import(data)
end

This will disable the bulk_insert callback for the save action, allowing you to perform the import without triggering the raw_filter error.

Solution

# updated the gem
$ bundle update 'activerecord-import'

4. Rails couldn’t find a valid model

NameError: Rails couldn't find a valid model for Card association. Please provide the :class\_name option on the association declaration. If :class\_name is already provided, make sure it's an ActiveRecord::Base subclass.

--- Caused by: ---
      # NameError:
      #   uninitialized constant CreditCardPayment::Card
      #   ./app/models/credit_card_payment.rb:375:in `stored_card?'

Explanation:

For some reason, this was not raising any error in Rails 6 but, suddenly its an issue in Rails 7. This the related part of code in rails repo.

Solution


belongs_to :card, class_name: 'CreditCardPayment', foreign_key: 'payment_id'

5. ActiveSupport::TimeWithZone.name has been deprecated

ActiveSupport::DeprecationException:
        DEPRECATION WARNING: ActiveSupport::TimeWithZone.name has been deprecated and
        from Rails 7.1 will use the default Ruby implementation.
        You can set `config.active_support.remove_deprecated_time_with_zone_name = true`

Solution

# activate this in config/initializers/new_framework_defaults_7_0.rb
# Don't override ActiveSupport::TimeWithZone.name and use the default Ruby
# implementation.
Rails.application.config.active_support.remove_deprecated_time_with_zone_name = true

After the deployment of Rails 7, you will need to remove the file new_framework_defaults_7_0.rb and you will need to activate 7.0 default config in application.rb. And, note that, config.active_support.remove_deprecated_time_with_zone_name will be removed from Rails 7.2.


6. Undefined method `clear_association_cache’

Turns out, this private API is now removed in Rails 7


 - object.send(:clear_association_cache)

so you need to manually reset the instance variable

object.instance_variable_set :@association_cache, {} if object.persisted?

The reason for the removal of this method was due to improvements in the way that Rails handles caching of associations. With these improvements, the clear_association_cache method was no longer needed and could be safely removed.


7. Uninitialized constant DeprecationHandlingMessageHash

Looks like the classes ActiveModel::DeprecationHandlingMessageHash and ActiveModel::DeprecationHandlingMessageArray were deprecated from rails 6.1 but now in 7.0 it’s removed. So, it was raising this issue.

ActiveModel::Error was added to Rails 6.1 in #32313. Part of the change was the addition of DeprecationHandlingMessageArray which serves as a delegate for the [] method:

Link to the Rails Issue

Explanation

In our case, we were using Yaks gem to generate APIs responses. In order to generate response, the gem need to have data in primitive types like string , number, hash , etc that can be easily converted to xml or JSON . So, we need to explicitly tell Yaks how to get primitive data out of any complex data-type. Now, the constants are removed, so its raising exception.

Solution

- map_to_primitive ActiveModel::DeprecationHandlingMessageHash, &:to_h
- map_to_primitive ActiveModel::DeprecationHandlingMessageArray, &:to_a

+ map_to_primitive ActiveModel::Error, &:full_message

Need to convert instances of ActiveModel::Error to primitive using :full_message method to string. No other meaning full methods are there. If you want to try then can use :details, :message


8. Uninitialized Active record object after calling .reload

 arobject.reload.reload
=> #<ArModel:0x000000011acbcdc8 not initialized>

Also facing stale object issue. calling .reload is not making another SQL query to fetch the record from the DB.

Solution

This happened probably because we had overwritten preload method in some models. In Rails 7, reload method happens to call preload method internally on the active-record object.

I just renamed our preload method to something else like def our_preload and the problem seems to have gone.

8.1 TypeError: no implicit conversion of String into Integer

expect(setting.reload.value[0][:description]).to eq("Old charge")

I found that in Rails 6.1 the .reload method internally did not used to call any preload class method on the object. So it was working before.

Rails 6.1 : https://github.com/rails/rails/blob/v6.1.7.2/activerecord/lib/active_record/persistence.rb#L800-L814

In Rails 7.0, it called .preload class method which is causing problem.

Rails 7.0.4: https://github.com/rails/rails/blob/v7.0.4/activerecord/lib/active_record/persistence.rb#L943-L957

calls this method below, which calls preload method https://github.com/rails/rails/blob/v7.0.4/activerecord/lib/active_record/persistence.rb#L1018-L1023


9. image/jpg is not a valid content type

 DEPRECATION WARNING: image/jpg is not a valid content type, it should not be used when creating a blob, and support for it will be removed in Rails 7.1. If you want to keep supporting this content type past Rails 7.1, add it to `config.active_storage.variable_content_types`. Dismiss this warning by setting `config.active_storage.silence_invalid_content_types_warning = true`. (called from block (2 levels) in <top (required)> at /Users/username/Projects/app/spec/lib/active_storage/service/cloudfront_s3_service_spec.rb:23)

Solution

      - content_type: 'image/jpg'
      + content_type: 'image/jpeg'

10. Zlib::DataError: incorrect header check

Failure/Error: expect(Rails.cache.fetch('cache-key')).to eq('value to cache')

     Zlib::DataError:
       incorrect header check

Explanation

We were using the following API in Rails 6.1


      ::Rails.cache.fetch(
        opts[:cache_key],
        compressed: true, # <-- has no effect in Rails 6
        expires_in: opts[:expires_in],
        force:      opts[:force_cache_refresh]
      ) do
        cache_value_string
      end

It was working before because compressed: true had no meaning and the actual option was compress: true which was by default true and everything was fine, data was compressed in the cache. See Here

When I upgraded to Rails 7.0.4, suddenly the cache compression behaviour changed from the default true to false . See Here

And, the actual cause of the exception is a potential bug in Rails. When compressed: true option is used,

  • while storing the value, there is no issue. Compression is off

  • while fetching from the cache, it tries to decompress the value and raises error.

Solution

- compressed: true,
+ compress: true,

11. Massing failing of tests - Bullet N+1 Queries

After moving from bullet 6.0.0 to 7.0.7, we are seeing a lot of N+1 query exceptions. I also tried upgrading bullet to 7.0.7 in the master branch but see no such errors. upon digging I found that, bullet has multiple modules for different version of active-record. Like this is for AR 7.0

https://github.com/flyerhzm/bullet/blob/7.0.7/lib/bullet/active_record70.rb https://github.com/flyerhzm/bullet/blob/7.0.7/lib/bullet/active_record61.rb

  • https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md

  • https://github.com/flyerhzm/bullet/issues/642

Solution

Looking into this I came to the conclusion that these are false positives and need to be whitelisted

# helper method
def bullet_whitelist(whitelist)
  whitelist.each do |name, association, type = :n_plus_one_query|
    Bullet.add_safelist type: type, class_name: name, association: association
  end
  yield
  Bullet.clear_safelist
end

# Usage
# rails_helper.rb
config.around do |example|
  bullet_whitelist([
                       ["User", :account],
                       ["Comment", :article, :unused_eager_loading],
                     ]) do
    example.run
  end
end

12. Fewer SQL queries are fired

There are several potential reasons why we might be seeing fewer SQL queries fired in Rails 7 compared to Rails 6:

  1. Improved caching: Rails 7 includes several caching improvements that could reduce the need for SQL queries. For example, the new action caching feature caches entire controller actions, reducing the need to execute SQL queries on subsequent requests.

  2. Better database connection management: Rails 7 includes improvements to the way it manages database connections, which could reduce the number of unnecessary SQL queries. For example, the new connection pooling feature helps reduce the overhead of creating and managing database connections.

  3. Changes to the way ActiveRecord handles associations: In Rails 7, ActiveRecord has been optimized to handle associations more efficiently, which could reduce the number of SQL queries needed to load associated records.

  4. Other performance optimizations: Rails 7 includes several other performance optimizations that could reduce the number of SQL queries needed for certain operations. For example, improvements to the way ActiveRecord handles count queries could reduce the need for additional SQL queries.

Overall, it’s likely that a combination of these factors (and possibly others) could be responsible for the reduction in SQL queries you’re seeing in Rails 7.


13. Non-URL-safe CSRF tokens are deprecated. Use 6.1 defaults or above.

Failure/Error: require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)

ActiveSupport::DeprecationException:
  DEPRECATION WARNING: Non-URL-safe CSRF tokens are deprecated. Use 6.1 defaults or above. (called from <top (required)> at

Solution

  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.1 # less than 6.1 is not acceptable in Rails 7

14. ArgumentError: missing keywords: :records, :associations

ArgumentError: missing keywords: :records, :associations
from /Users/user/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/activerecord-7.0.4.2/lib/active_record/associations/preloader.rb:96:in `initialize'

Solution


  def self.eager_load(models, includes)
    # This is the old way to preload data and will have argument error in Rails 7
    # ActiveRecord::Associations::Preloader.new.preload(Array(models).flatten, includes)


    # The is the new way to preload data in Rails 7
    ActiveRecord::Associations::Preloader.new(
      records: Array(models).flatten,
      associations: includes
    ).call
  end

15. ExecJS::ProgramError: Unexpected token: name (FileChecksum)


$ bundle exec rake assets:precompile

ExecJS::ProgramError: Unexpected token: name (FileChecksum) (line: 421, col: 8, pos: 16550)

Solution

I got rid of this error by upgrading the uglifier gem.

bundle update uglifier

16. Uglifier::Error: Unexpected token: keyword (const)

Uglifier::Error: Unexpected token: keyword (const). To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).
--
 412       SparkMD5.ArrayBuffer.hash = function(arr, raw) {
 413         var hash = md51_array(new Uint8Array(arr)), ret = hex(hash);
 414         return raw ? hexToBinaryString(ret) : ret;
 415       };
 416       return SparkMD5;
 417     }));

Solution

add harmony: true to Uglifier.new


  # Compress JavaScripts and CSS
  config.assets.js_compressor = Uglifier.new({
    output: {
      beautify: true,
      preserve_line: true, # Add new lines
      indent_level: 0      # Don't add indentation
    },
+    harmony: true
  })

Now it supports both ES5 and ES6 syntax.


17. to_s(:format) deprecated in favor of to_fs(:format)

To allow Rails applications to use this optimization in the future Rails is deprecating all the reasons we override to_s in those core classes in favour of using to_formatted_s.

You need to make sure all the deprecated API usages are updated accordingly.

PR: https://github.com/rails/rails/pull/43772/