【Rails】モデルに外部キーを設定する方法とよく起こるエラー内容について

はじめに

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_onebelongs_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でモデルに外部キーを設定する方法と、よく起こるエラー内容について説明しました。本記事を参考にしていただければと思います。

関連記事

【Rails】Railsアップグレードまとめ
# はじめに Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。 一般的にフレームワークはバージョン毎にEOL (End of Life [...]
2022年10月1日 14:32
【Rails】ユーザー登録時に行うメールアドレス認証機能の実装方法
# はじめに ユーザー登録/解除やログイン/ログアウトといった認証機能の導入に「devise」というGemを使っている人は多いと思います。「devise」では以下のように記述するだけで、ユーザー登録時に確認メールを送付しメールアドレス認証を行う機 [...]
2022年9月24日 14:24
【Rails】モデルに列挙型(enum)を定義し、使いこなす方法
# はじめに Railsはモデルでカラム名と同名の列挙型(enum)を定義することで、カラムと列挙型の変数を紐付けることができます。カラムと列挙型の変数を紐付けると、カラムに対して様々な便利な使い方ができるようになります。 本記事では、モデ [...]
2022年9月3日 10:29
【Rails】RailsでCORSとPreflight requestの設定を行う方法
# はじめに RailsアプリをAPIサーバーとして構築するには、CORS (Cross-Origin Resource Sharing)と Preflight requestの設定を行う必要があります。APIサーバーは外部からの要求に対して処理 [...]
2022年8月27日 10:44
【Ruby】Bundlerを使ってRubyGemsを作成/公開する方法
# はじめに Bundlerを使ってRubyGemsを作成および公開する方法について説明します。Bundlerを使わずにRubyGemsを作成/公開する方法については以下の記事を参照してください。 <iframe class="hatena [...]
2022年7月12日 23:18
【Ruby】RubyGemsを作成/公開する方法
# はじめに RubyGemsを作成および公開する方法について説明します。Bundlerを使ってRubyGemsを作成する方法については以下の記事を参照してください。 <iframe class="hatenablogcard" style [...]
2022年7月11日 21:52
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12