rails: New reference to old table fails on foreign key (MySQL bigint(20) v. int(11))

When creating a new table with a references column for an existing (pre-5.1) table, the attempt to create a foreign key constraint (in MySQL) fails with a huge backtrace having just a few cryptic clues as to what went wrong.

Steps to reproduce

Create a foreign key to a table created pre-5.1 fails (MySQL). (I know this isn’t an executable test case based on active_record_migrations_gem.rb, but I’m pretty sure this wouldn’t be an issue for SQLite3 anyway.)

In my (client’s) case, the Member model exists and the Role and Hat models are going to be created (for a role-based authorization implementation). Starting simply with:

$ rails g migration CreateRole name description
$ rails g migration CreateHat member:references role:references

Expected behavior

This will create two new tables: a simple roles table and a simple hats table that joins Member to Role

Actual behavior

$ rails db:migrate
== 20170927000118 CreateRole: migrating =======================================
-- create_table(:roles)
   -> 0.2327s
== 20170927000118 CreateRole: migrated (0.2331s) ==============================

== 20170927000202 CreateHat: migrating ========================================
-- create_table(:hats)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'sa_website4_dev.hats' doesn't exist: SHOW FULL FIELDS FROM `hats`

and many more backtrace lines including these lines:

ActiveRecord::StatementInvalid: Mysql2::Error: Table 'sa_website4_dev.hats' doesn't exist: SHOW FULL FIELDS FROM `hats`
Mysql2::Error: Table 'sa_website4_dev.hats' doesn't exist
Mysql2::Error: Cannot add foreign key constraint

That last one turns out to be the clue!

==> db/migrate/20170927000118_create_role.rb <==
class CreateRole < ActiveRecord::Migration[5.1]
  def change
    create_table :roles do |t|
      t.string :name, index: true, unique: true
      t.string :description
    end
  end
end

==> db/migrate/20170927000202_create_hat.rb <==
class CreateHat < ActiveRecord::Migration[5.1]
  def change
    create_table :hats do |t|
      t.references :member, foreign_key: true
      t.references :role, foreign_key: true
    end
  end
end

When the SQL is generated, the attempt is to make a hats.member_id which is created as a bigint(20) be a foreign key referencing members.id which is a lowly int(11) having been created from an old migration (before the version tagging began and when 4-byte primary keys were good enough).

The “fix” is to change the migrations to be 5.0 rather than 5.1 and the constraints get created successfully. It would be better to avoid the massive backtrace and give a succinct message or perhaps recognize that the reference requested is to an int(11) column and create the new column and the constraint accordingly.

System configuration

Rails 5.1.3
ruby 2.3.5p376 (2017-09-14 revision 59905) [x86_64-darwin14]
mysqld  Ver 5.7.19 for osx10.10 on x86_64 (Homebrew)

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 7
  • Comments: 16 (6 by maintainers)

Most upvoted comments

The line t.references :role, foreign_key: true is incorrect - it doesn’t correctly reflect the desired behavior. I believe it would work if it specified the type of the reference explicitly:

  t.references :role, foreign_key: true, type: :integer

The default for type was changed in #26266 but the documentation here didn’t get updated.

@dankozlowski there are two different “fixes”. One involves claiming that the migrations are [5.0], but you’re already using Rails 5.0.4 so that can’t be it. The other fix is to specify the :type of the foreign key as @al2o3cr indicated (and I verified that it worked for me having a new 5.1 join table include a reference to a pre-5.1 table):

t.references :role, foreign_key: true, type: :integer

In my case, the targeted table was actually created in a Rails 4.x system.

How are you encountering this issue in Rails 5.0.4?

I had the same issue, and for any future visitors, it turns out I hadn’t set the RAILS_ENV; I have no idea how this solves the problem but it worked for me. None of the other solutions worked.

bin/rails db:environment:set RAILS_ENV=development

Then a good old reset:

bin/rails db:reset

Everything worked like a charm then.


I’m using Rails 5.1.6 and Ruby 2.5.0 on macOS High Sierra.

I’m having this issue as well, and @rab 's fix doesn’t work for me. I’m using Rails 5.0.4.

I’m running into this issue after my migrations started being numbered at 5.1 and it’s really annoying. I can’t have foreign keys right now in my migrations.

Getting same error now

rails (7.0.4.3) ruby (3.2.2) mysql (2-0.5.4)

class CreateJQISContractorSnapshots < ActiveRecord::Migration[7.0]
  def change
    create_table :jqis_contractor_snapshots do |t|
      t.belongs_to :contractor, null: false, foreign_key: true, index: true, type: :bigint
    
      t.timestamps
    end
  end
Column `contractor_id` on table `jqis_contractor_snapshots` does not match column `id` on `contractors`, which has type `bigint(20)`. To resolve this issue, change the type of the `contractor_id` column on `jqis_contractor_snapshots` to be :bigint. (For example `t.bigint :contractor_id`).
Original message: Mysql2::Error: Cannot add foreign key constraint
/src/vendor/bundle/ruby/3.2.0/gems/mysql2-0.5.4/lib/mysql2/client.rb:148:in `_query'

I think the error handling for this has had some weird knock-on effects. We’re getting this error in our migrations, and while we haven’t found a resolution, we’re getting instructions to change to :integer even though that’s clearly not the solution.

Further details:

Existing Schema

  create_table "accounts", id: :string, limit: 36, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["id"], name: "index_accounts_on_id"
  end

Migration

class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.text :content
      t.string :author_id, type: :string, limit: 36
      t.timestamps
    end

    add_foreign_key :comments, :accounts, column: :author_id
  end
end

Error Message

Column `author_id` on table `comments` has a type of `varchar(36)`.
This does not match column `id` on `accounts`, which has type `varchar(36)`.
To resolve this issue, change the type of the `author_id` column on `comments` to be :integer. (For example `t.integer author_id`).

Original message: Mysql2::Error: Cannot add foreign key constraint: ALTER TABLE `comments` ADD CONSTRAINT `fk_rails_f44b1e3c8a`
FOREIGN KEY (`author_id`)
  REFERENCES `accounts` (`id`)

For any foreign key that is a reference to an old table, add type: :integer to fix the type to be explicitly integer – int(11), that is – rather than the default :bigint – aka, bigint(20).