単数形/複数形の変換がおかしいときの外部キー制約がエラーになる場合の対処法

外部キー制約の設定ができない

テーブルを作成するためにmigrationファイルを作成しdb:migrateしたら思いもよらないエラーが発生したので対処法を調査しました。

エラー内容

既存のテーブル(document_bases)があり、そのテーブルのidを外部キーに持つテーブル(document_views)を作成します。

migrationファイルを準備します

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views' }
t.timestamps
end
end
end

db:migrateします。すると以下のようなエラーが表示されました。

1
ActiveRecord::StatementInvalid: Mysql2::Error: Key column 'document_basis_id' doesn't exist in table: ALTER TABLE `document_views` ADD CONSTRAINT `document_base_id_on_document_views` FOREIGN KEY (`document_basis_id`) REFERENCES `document_bases` (`id`)

原因調査

最初なぜエラーになるのか全くわからなかったのですが、よく見るとKey column 'document_basis_id'となっていて、存在するカラムdocument_base_idと異なっています。

調べてみるとbasesはbaseの複数形でもあり、basisの複数形でもあるようです。basesから単数形に戻すときにbaseではなくbasisを選択されてしまっているようです。

対応方法

t.referencesのオプションであるforeign_keyでなんとか指定できないかと思い、add_foreign_keyのオプションを調べてみました。

https://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_foreign_key

するとoptionsの説明の最初に答えが書いてありました。

1
2
:column
The foreign key column name on from_table. Defaults to to_table.singularize + "_id"

Defaults to to_table.singularize + "_id"と書かれているので、singularizeメソッドを試してみました。

1
2
[1] pry(main)> 'bases'.singularize
=> "basis"

ということでforeign_keyのオプションにcolumn: :document_base_idを指定することで無事migrateできました。

修正済migrationファイル

1
2
3
4
5
6
7
8
class CreateDocumentView < ActiveRecord::Migration
def change
create_table :document_views do |t|
t.references :document_base, index: true, foreign_key: { name: 'document_base_id_on_document_views', column: :document_base_id }
t.timestamps
end
end
end

まとめ

Railsの単数形/複数形変換で存在しないカラム名/テーブル名に変換されてしまった場合は明示的に名前を指定するようにする。

APIドキュメントをちゃんと読むようにする。