はじめに
Railsでモデルに外部キーを設定する方法について説明します。
モデルに外部キーを設定する
リレーションシップ
今回は1つのブログ記事は複数のコメントを持つ1対多のリレーションシップを例に説明します。現在はArticle
モデルのみ存在し、外部キーとしてArticle
モデルのIDを持つComment
モデルを作成します。
モデルの作成
さっそくComment
モデルを作成していきます。ターミナルで以下のコマンドを実行します。余談ですが、string
型の長さを指定したいときはname:string{20}
のように長さを中括弧で囲む必要があります。
$ rails generate model Comment article:references name:string{20} content:text
上記のコマンドを実行すると、以下のようなマイグレーションファイルが作成されます。
yyyymmddhhmmss_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.references :article, foreign_key: true
t.string :name, limit: 20
t.text :content
t.timestamps
end
end
end
references
型のカラムを外部キー制約付きで追加する設定が記述されています。
さらに、以下のようなComment
モデルのモデルファイルも作成されます。
comment.rb
class Comment < ApplicationRecord
belongs_to :article
end
Article
モデルに所属することを示すbelongs_to
キーワードが記述されています。なお、Article
モデルにはコマンドを実行しただけでは何も追記されないので、自分で追記する必要があります。
article.rb
class Article < ApplicationRecord
# 以下を追記
has_many :comments
# 1対1の場合は以下を追記
# has_one :comment
end
モデルの関連付けについて詳しくは以下の記事を参照してください。
マイグレーションの実行
マイグレーションファイルが用意できたら、マイグレーションを実行します。
$ rails db:migrate
上記のコマンドを実行すると、DBにcomments
テーブルが追加され、スキーマファイルが更新されます。
schema.rb
ActiveRecord::Schema.define(version: yyyy_mm_dd_hhmmss) do
create_table "comments", force: :cascade do |t|
t.bigint "article_id"
t.string "name", limit: 20
t.text "content"
t.index ["article_id"], name: "index_comments_on_article_id"
end
add_foreign_key "comments", "articles"
end
マイグレーションファイルにはt.references :article, foreign_key: true
のように記述しましたが、追加されたカラム名はちゃんとarticle_id
になっています。
また、マイグレーションファイルにはインデックスを追加する記述はありませんでしたが、自動でarticle_id
のにインデックスが追加されています。マイグレーションファイルでreferences
型で外部キーを作成するとデフォルトでインデックスが追加されるようになっています。
一意制約の追加
references
型を使った外部キーに一意制約を追加して作成するには以下のように修正します。
yyyymmddhhmmss_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
# 以下を修正
t.references :article, foreign_key: true, index: { unique: true }
t.string :name, limit: 20
t.text :content
t.timestamps
end
end
end
外部キーの参照先のモデルと参照元のモデルが1対1(has_one
とbelongs_to
の関係)の場合、一意制約がないと整合性が崩れる可能性があるため注意が必要です。
外部キーの設定時によく起こるエラー内容
ここからは、references
型を使って外部キーを設定するときによく起こるエラーについて説明します。
事象
上記の「マイグレーションの実行」セクションの通りマイグレートコマンドを実行すると、以下のようなエラーが発生することがあります。
$ rails db:migrate
Column `article_id` on table `comments` does not match column `id` on `articles`, which has type `integer`. To resolve this issue, change the type of the `id` column on `articles` to be :bigint. (For example `t.bigint :id`).
テーブル `comments`の列` article_id`は、タイプ `integer`の` articles`の列 `id`と一致しません。 この問題を解決するには、 `articles`の` id`列のタイプを:bigintに変更します。 (たとえば、 `t.bigint:id`)。
今回外部キーを設定しようとしているcomments.article_id
のデータ型と、その参照先であるarticles.id
のデータ型が違っているため、外部キーの設定が行えなかったということです。当然ながら、外部キーを設定するには参照元と参照先のデータ型が一致している必要があります。
原因
このエラーが起こるひとつの要因として、Active Recordが作成する主キー(id
)のデータ型がRails 5.1から変更されたという背景があります。Rails 5.0までは主キーのデータ型としてinteger
が使われていましたが、Rails 5.1からはbigint
に変更されました。
つまり、Rails 5.0以前に作られたモデルの主キーを参照先とする外部キーをRails 5.1以降に作成しようとすると、参照元と参照先のデータ型の不一致が発生し、上記のようなエラーが発生することになります。
上記のエラーメッセージの場合、Article
モデルはRails 5.0以前に作られ、Rails 5.1以降にComment
モデルを作ろうとしたと考えられます。
対処
上記のエラーコマンドには、外部キーの参照先のデータ型をbigint
型に変更することが解決策として記述されていますが、既に存在するモデルの設定変更を行うよりは、これから作ろうとしているモデルの設定変更を行ったほうがいいでしょう。
yyyymmddhhmmss_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
# 以下を修正
t.references :article, foreign_key: true, type: :integer
t.string :name, limit: 20
t.text :content
t.timestamps
end
end
end
外部キーを設定するカラムにtype: integer
オプションを追加します。このオプションにより、Rails 5.1以降でも主キーのデータ型にbigint
ではなくinteger
を使うようにでき、外部キーの参照先であるarticles.id
のデータ型と一致するようになります。
まとめ
Railsでモデルに外部キーを設定する方法と、よく起こるエラー内容について説明しました。本記事を参考にしていただければと思います。