Skip to content

Rails upgrade notes

Jakob Skjerning edited this page Mar 18, 2024 · 61 revisions

This page contains a bunch of issues we've run into when upgrading Rails applications and how to fix them. For the most part each issue contain a findable error message or code snippet.

General process

  1. Upgrade rails gem to the most recent version in the current line. Ie if the project is currently on 4.1.x, lock it to 4.1.16. You can find the newest versions on https://rubygems.org/gems/rails/versions.
  2. Run the test suite and make sure there are no deprecation warnings. If there are any, they should be fixed, preferably in separate steps/pull requests.
  3. Upgrade rails gem to the most recent version in the next line. Ie if the project is currently on 4.1.16, lock it to 4.2.11.1. You can find the newest versions on https://rubygems.org/gems/rails/versions. If this is blocked by other incompatible gems, abort and go update each of those gems in a separate branch.
  4. If synvert has a snippet for your particular upgrade, run it now. Ie going from Rails 4.1 to 4.2: synvert-ruby --run rails/upgrade_4_1_to_4_2.
  5. Use rake rails:update/rails app:update making sure to not overwrite existing files. This will add new files and remove deprecated ones.
  6. Get a list of all changes to default files and new default files from RailsDiff for your particular upgrade. Go through them one by one, merging the changes into your existing files.
  7. Consult the official upgrade guides for your particular upgrade. Go through each change, making the applicable changes.
  8. Consult the known issues we've encountered for your particular upgrade below. Go through each change, making the applicable changes.
  9. Run the test suite again, everything should be green.
  10. Fix any deprecation warnings that might have appeared.
  11. Restart the application and go through the most important flows, veriyfing they still work.
  12. Done?

Useful tools

  • RailsDiff: Show changes in the default generated files, these changes will usually have to be manually merged.
  • Synvert: Automatically apply common syntax changes to an entire project.

6.1 → 7.0

undefined method 'assets' for #<Rails::Application::Configuration:0x000000013af16f18

If you get an error message on startup resembling

NoMethodError: undefined method 'assets' for #<Rails::Application::Configuration:0x000000013af16f18 ...>

you need to add sprockets-rails to the Gemfile.

See https://github.com/rails/rails/issues/43793 for details.

6.0 → 6.1

undefined method add_template_helper' for NotificationsMailer:Class`

#add_template_helper was deleted in Rails 6.1 (supposedly it was private), resulting in:

NoMethodError: undefined method `add_template_helper' for NotificationsMailer:Class

Replace

add_template_helper EmailHelpers

with

helper EmailHelpers

5.2 → 6.0

Sprockets::ArgumentError: link_directory argument must be a directory

This is supposedly a side effect of upgrading Sprockets to v4. Some manifest file, probably at app/assets/config/manifest.js is pointing at a non-existing folder.

If you're still using Sprockets for those kinds of assets, update the folder reference to the location of your assets, otherwise just remove the line.

ArgumentError when calling render :partial => "foo"

ArgumentError: wrong number of arguments (given 2, expected 1)
.../gems/actionview-6.0.1/lib/action_view/template.rb:315:in `compile'

Solution

Upgrade rspec-rails to at least 4.0, using the beta versions if necessary:

In Gemfile:

gem "rspec-rails", :github => "rspec/rspec-rails", :tag => "v4.0.0.beta3"

ActiveModel::UnknownAttributeError when using ActionMailbox

ActiveModel::UnknownAttributeError:
  unknown attribute 'message_checksum' for ActionMailbox::InboundEmail.

You probably have an old database schema action_mailbox_inbound_emails. The table should look like

  create_table "action_mailbox_inbound_emails", force: :cascade do |t|
    t.integer "status", default: 0, null: false
    t.string "message_id", null: false
    t.string "message_checksum", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["message_id", "message_checksum"], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
  end

Solution

Construct a new migration to bring your schema up to date. In the case where the current schema is

  create_table "action_mailbox_inbound_emails", force: :cascade do |t|
    t.integer "status", default: 0, null: false
    t.string "message_id"
    t.datetime "created_at", precision: 6
    t.datetime "updated_at", precision: 6
  end

the resulting migration should look something like

# frozen_string_literal: true

class UpdateActionMailboxInboundEmails < ActiveRecord::Migration[6.0]
  def change
    change_column_null(
      :action_mailbox_inbound_emails,
      :message_id, false
    )
    add_column(
      :action_mailbox_inbound_emails,
      :message_checksum,
      :string
    )
    add_index(
      :action_mailbox_inbound_emails,
      [:message_id, :message_checksum],
      :name => "index_action_mailbox_inbound_emails_uniqueness",
      :unique => true
    )
  end
end

5.1 → 5.2

Scope name already defined by ActiveRecord::Relation

ActiveRecord::Relation defines a bunch of methods on associations, among others an #index so that one can say

model.tickers.index(ticker)

to find the position of ticker in the list of index.tickers (ie 0 if ticker is the first record).

However, if you also define a scope with that name:

scope :index, -> { where(:kind => INDEX) }

to return all records where kind == "INDEX" that results in

index.tickers.index(ticker)

being ambigious, because it would no longer return the position but rather another list of records.

Prior to Rails 5.2 we've been able to clobber the ActiveRecord::Relation#index method, but starting with Rails 5.2, scope checks for name clashes:

> You tried to define a scope named "index" on the model "Ticker", but ActiveRecord::Relation
> already defined an instance method with the same name.

The list of instance methods that are defined like this by ActiveRecord::Relation is

delegate :to_xml, :encode_with, :length, :each, :uniq, :join,
         :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
         :to_sentence, :to_formatted_s, :as_json,
         :shuffle, :split, :slice, :index, :rindex, to: :records

(from https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/delegation.rb#L41).

Solution

Rename the scope to something that doesn't clash. In the above case we went for

scope :is_index, -> { where(:kind => INDEX) }

Delayed:Job deserialization

Delayed::DeserializationError: Job failed to load: undefined method `allocate' for "ActiveModel::Attribute::FromDatabase":String

Solution

Don't serialize "rich" objects (ie objects like full ActiveRecord::Base instances) - only pass simple, Ruby instances like String, Integer etc, which we can reliably deserialize.

5.0 → 5.1

Conditional validations passed as string

ActiveSupport::DeprecationException: DEPRECATION WARNING: Passing string to be evaluated in :if and :unless conditional options is deprecated and will be removed in Rails 5.2 without replacement. Pass a symbol for an instance method, or a lambda, proc or block, instead

Solution

Pass the conditional as a lambda or block instead

From: validates :some_attribute, :presence => true, :if => "person_identifier.nil?" To: validates :some_attribute, :presence => true, :if => lambda { person_identifier.nil? }

Optional associations fails in specs

ActiveRecord::RecordInvalid: Validation failed: Some association must exist

Solution

Optional belongs_to associations must be declared as such. Example: belongs_to :some_association, :optional => true

Xhr request failures in rspec

super: no superclass method `xhr' for #RSpec::ExampleGroups:SomeController::WhenNotLoggedIn::HandlingGETTerms:0x007f8766c19540

Solution

xhr :action in specs doesn't work anymore.

Change to, for example, get :action, :xhr => true

ActiveRecord::HasManyThroughOrderError

ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'Ticker#indices' which goes through 'Ticker#index_components' before the through association is defined.

Solution

has_many :through associations needs to be defined after their source association. So for all places where you have associations defined like

class Car < ApplicationRecord
  has_many :tires, :through => :wheels
  has_many :wheels
end

swap the associations around so they become

class Car < ApplicationRecord
  has_many :wheels
  has_many :tires, :through => :wheels
end

render :text is now render :plain

Doesn't work:

render :text => "OK"

The above likely causes ActionView::MissingTemplate as ActionView doesn't realise it has rendered and thus continues the rendering process and looks for a template.

Works:

render :plain => "OK"

4.2 → 5.0

params#to_hash

If you are sending to_hash to the params object anywhere, it'd likely need to be changed to to_unsafe_hash:

DEPRECATION WARNING: #to_hash unexpectedly ignores parameter filtering, and will change to enforce it in Rails 5.1. Enable raise_on_unfiltered_parameters to respect parameter filtering, which is the default in new applications. For the existing deprecated behaviour, call #to_unsafe_h instead.

This ensures that all parameters are included in the returned hash instead of just the permitted ones.

database default values of an attribute stops working after the upgrade

example BookingEvent

default value for spam is true

self def.default_scope { where(:spam => false) }

BookingEvent.new.spam? = false

Solution

Don't use default scopes. Else ensure that new instances are created with the proper values.

The default_scope is also applied while creating/building a record. It is not applied while updating a record.

References

Undefined method (for ActiveRecord_AssociationRelation)

undefined method `to_sentence' for #Index::ActiveRecord_AssociationRelation:0x007fbf5712a930

Looks like Associations no longer are Arrays, and/or to_sentence is no longer mixed into AssociationRelation. The simple solution is to simple convert the relation to an array:

virtual_portfolio.indices.display_order.to_a.to_sentence

Attempting to generate a URL from non-sanitized request parameters!

Attempting to generate a URL from non-sanitized request parameters! An attacker can inject malicious data into the generated URL, such as changing the host. Whitelist and sanitize passed parameters to be secure.

Solution

Dont pass params directly to a URL builder, for example in:

= simple_form_for :timeframe, :url => params do |form|

Instead, be explicit about the params you actually allow, for example:

= simple_form_for :timeframe, :url => params.permit(:controller, :action) do |form|

References

4.1 → 4.2

4.0 → 4.1

Undefined method shift (for ActiveRecord::Relation)

undefined method `shift' for #Movie::ActiveRecord_AssociationRelation:0x00000114e49e30

Solution

Dont use array mutating methods directly on ActiveRecord relations, as they are not allowed anymore. This also includes map!, delete_if, compact! and similar methods. If needed, convert them to arrays first (.to_a)

3.1 → 3.2

MySQL: Specified key was too long

When running rake db:schema:load:

ActiveRecord::StatementInvalid: Mysql2::Error: Specified key was too long; max key length is 767 bytes: CREATE UNIQUE INDEX `index_annoying_closings_on_ip_address` ON `annoying_closings` (`ip_address`)

Solution

Switch the schema dump to :sql:

config.active_record.schema_format = :sql

It seems the dump generated by rake db:schema:dump doesn't include the limit/length of the index, so rake db:schema:load cannot load the dump.

Undefined method: read_inheritable_attribute

.../active_record/dynamic_matchers.rb:55:in `method_missing': undefined method `read_inheritable_attribute' for #<Class:0x007fcde359d900> (NoMethodError)

Solution

Rewrite to use class_attribute

Undefined method call for :raise:Symbol

ActionView::Template::Error: undefined method `call' for :raise:Symbol

Solution

Change

config.active_support.deprecation = :raise

to

config.active_support.deprecation = :stderr

This isn't quite the same behavior, though.

References

Undefined method: update_details

NoMethodError: undefined method `update_details' for #ActionView::LookupContext:0x007ff0bea09dd8

Solution

Upgrade prototype-rails to ~> 3.2.1.

2.x → 3.x

  • sendmail, exim doesn't work: https://github.com/gitlabhq/gitlabhq/issues/4866

  • Rename all Mailer.deliver_foo to Mailer.foo.deliver

  • Rename mail templates with foo.erb to match their actual content type, i.e. foo.html.erb or foo.text.erb.

  • Change @body["foo"] = "bar" to @foo = "bar" in mailers

  • Remove all default_scopes with :order - they will override all order clauses

Clone this wiki locally