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

More than 1 year has passed since last update.


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人でも減る事を祈ります。