【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】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18