33
24

More than 5 years have passed since last update.

Rails 5.1以前に作成されたテーブルにFK制約を張れない問題の解決策

Last updated at Posted at 2017-12-23

Rails 5.1のmigrationに関する変更点

Rails 5.1以前はmigrationを作成するとデフォルトでidカラムはintで作成されますが5.1からはこれがbigintに変更されました。
Restore the behaviour of the compatibility layer for integer-like PKs #27389

この変更で以下のようなmigrationが既に存在している場合に、

db/migrate/20171225000001_create_hoges.rb
class CreateHoges < ActiveRecord::Migration[5.0]
  def change
    # hoges.idはintで作成される
    create_table :hoges do |t|
      t.string :name

      t.timestamps
    end
  end
end

Rails5.1以降にアップデートするとFK制約を張れなくなりました。

db/migrate/20171225000101_create_piyos.rb
class CreatePiyos < ActiveRecord::Migration[5.1]
  def change
    create_table :piyos do |t|
      # piyos.hoge_idはbigintで作成される
      t.references :hoge, foreign_key: true

      t.timestamps
    end
  end
end

hoges.id はintで作成されますが piyos.hoge_id はbigintで作成されるので、別のタイプのカラム同士ではFK制約を張れないので rails db:migrate が失敗します。

この問題の解決策を2つ紹介します。

hogesテーブルのidを変更せずにintでpiyos.hoge_idを作成する

piyos.hoge_idをintで作成する

db/migrate/20171225000101_create_piyos.rb
class CreatePiyos < ActiveRecord::Migration[5.1]
  def change
    create_table :piyos do |t|
      # piyos.hoge_idをintで作成する
      t.references :hoge, foreign_key: true, type: :integer

      t.timestamps
    end
  end
end

もしくは

piyos.hoge_idをintにして別途FK制約を張る

db/migrate/20171225000101_create_piyos.rb
class CreatePiyos < ActiveRecord::Migration[5.1]
  def change
    create_table :piyos do |t|
      # piyos.hoge_idをintで作成する
      t.integer :hoge_id, default: nil

      t.timestamps
    end
    # 別途FK制約を作成する
    add_foreign_key :piyos, :hoges
  end
end

add_foreign_keyについて

add_foreign_keyは名前の通りFK制約を作成します。
最初の引数で参照元のテーブル名、次の引数で参照先のテーブル名を指定します。
オプションとしてcolumnで参照元のカラム名を、primary_keyで参照先のカラム名を指定できます。
他のオプションとして参照先更新及び削除時の挙動を変更するon_updateとon_deleteもありますが、ここでは扱いません。

つまり、下の2つは同義です。

add_foreign_key :piyos, :hoges
add_foreign_key :piyos, :hoges, column: :hoge_id, primary_key: :id

piyos.hoge_idをpiyos.foo_idにしたい場合

add_foreign_key :piyos, :hoges, column: :foo_id

とすればOKです。

another_id等がPRIMARY KEYになってしまっている場合

db/migrate/20171225000001_create_hoges.rb
class CreateHoges < ActiveRecord::Migration[5.0]
  def change
    create_table :hoges, id: false do |t|
      t.string :name
      t.column :another_id, 'INTEGER PRIMARY KEY AUTO_INCREMENT'

      t.timestamps
    end
  end
end

以下のようにすればOKです。

db/migrate/20171225000101_create_piyos.rb
class CreatePiyos < ActiveRecord::Migration[5.1]
  def change
    create_table :piyos do |t|
      t.integer :hoge_another_id

      t.timestamps
    end
    add_foreign_key :piyos, :hoges, column: :hoge_another_id, primary_key: :another_id
  end
end

id以外をPRIMARY KEYにしたりPRIMARY KEY以外にFK制約を張るのはRails wayから外れるのでやむを得ない事情があっても控えましょう。

hogesテーブルのidをintからbigintに変更してからpiyosを作成する

hoges.idをintからbigintに変更する

db/migrate/20171225000002_change_hoges_id.rb
class ChangeHogesId < ActiveRecord::Migration[5.1]
  def up
    # intからbigintへの変更用
    change_column :hoges, :id, :bigint, auto_increment: true
  end
  def down
    # rollback時のbigintからintへの変更用
    change_column :hoges, :id, :int, auto_increment: true
  end
end

普通にpiyosを作成する

db/migrate/20171225000002_change_hoges_id.rb
class CreatePiyos < ActiveRecord::Migration[5.1]
  def change
    create_table :piyos do |t|
      t.references :hoge, foreign_key: true

      t.timestamps
    end
  end
end

終わりに

いかがだったでしょうか、Railsは基本後方互換性を保ちながら開発が進められていますが、Webの世界は流れが早いのでこういった破壊的な変更が稀に起こるのは仕方のない事でしょう。

この記事によってRDBにFK制約を張らずにModel側でのみassociationを設定してしまうRailsエンジニアが1人でも減る事を祈ります。

33
24
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
24