#本記事の対象
・references型のカラムを後からテーブルに追加したいが苦戦している方
・プログラミング学習歴が数ヶ月の方
⚠️途中までは自分が試したことの記録なので、どうやるのかだけを見たい方は「正解は以下の通り」という見出し以下を見ていただけたらと思います
#やりたいこと
・追加し忘れたreferences型のカラムをテーブルに追加したい
#背景
現在オリジナルアプリを作成している。usersテーブル・recordsテーブルと、2つのみで構成されているアプリケーションで、users : records = 一 : 多
という関係にしてある。
一対多の関係においては、多の方に外部キー制約(foreign_key: true)のあるreferences型のカラムが存在するのが一般的である。
よって、上記の様なアプリではユーザー機能をある程度作った後に、もう一方のreferences型のカラムを含むテーブルを作成するべきだったが、references型のカラムの存在を完全に忘れており、ルートパスで入れるトップページを作成している際に、外部キーのカラム抜きで何故かrecordsテーブルも作成してしまっていた。
⚠️当然だが、この段階↑でreferences型のカラムをマイグレーションファイルに書いていたら、参照するusersテーブルが存在しないのでエラーが発生する。
そして、ユーザーに紐づく投稿(recordsテーブルの情報)を表示しようとする際に、ようやくreferences型のカラムを追加していないことに気がつき、苦戦したという次第である。
#仮説
今までの学習してきた内容から、後からカラムを追加したい際のやり方は2通りが考えられた。
やり方①
statusがupになっている既存のマイグレーションをdownにしてから、カラムを追加したいテーブルのマイグレーションファイルを編集する
やり方②
rails g migration Addカラム名Toテーブル名
のコマンドを実行して、新しく作成されたマイグレーションファイルのchangeメソッド内にadd_column :テーブル名, :カラム名, :データ型, オプション
を記述する。
個人的にだが、カラムを追加したり変更したりする時は①の方がわかりやすいと感じていたので、まず①を実践してみた。
#「①状態がupになっている既存のマイグレーションをdownにしてから、カラムを追加したいテーブルのマイグレーションファイルを編集する」 を実践
①-1. rails db:migrate:status
で現在のマイグレーションファイルの中身を確認
Status Migration ID Migration Name
--------------------------------------------------
up 20201206094413 Create records
up 20201206115636 Devise create users
①-2. rails db:rollback
をした後にdownになっていることを確認
Status Migration ID Migration Name
--------------------------------------------------
down 20201206094413 Create records
down 20201206115636 Devise create users
①-3. references型のuser_idのカラムを追加したいのは20201206094413 20201206094413 Create records
の方なので、該当ファイルを以下の様に編集した。
class CreateRecords < ActiveRecord::Migration[6.0]
def change
create_table :records do |t|
t.integer :time, null: false
t.string :skip, null: false
t.string :to_do
t.references :user, foreign_key: true
t.timestamps
end
end
end
①-4. rails db:migrateを実行したら、以下の様に出力された
== 20201206094413 CreateRecords: migrating ====================================
-- create_table(:records)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Table 'アプリ名_development.users' doesn't exist
/Users/ユーザー名/projects/アプリ名/db/migrate/20201206094413_create_records.rb:3:in `change'
/Users/ユーザー名/projects/アプリ名/bin/rails:9:in `<top (required)>'
/Users/ユーザー名/projects/アプリ名/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'アプリ名_development.users' doesn't exist
/Users/ユーザー名/projects/アプリ名/db/migrate/20201206094413_create_records.rb:3:in `change'
/Users/ユーザー名/projects/アプリ名/bin/rails:9:in `<top (required)>'
/Users/ユーザー名/projects/アプリ名/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Caused by:
Mysql2::Error: Table 'アプリ名_development.users' doesn't exist
/Users/ユーザー名/projects/アプリ名/db/migrate/20201206094413_create_records.rb:3:in `change'
/Users/ユーザー名/projects/アプリ名/bin/rails:9:in `<top (required)>'
/Users/ユーザー名/projects/アプリ名/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Caused by:
Mysql2::Error: Cannot add foreign key constraint
/Users/ユーザー名/projects/アプリ名/db/migrate/20201206094413_create_records.rb:3:in `change'
/Users/ユーザー名/projects/アプリ名/bin/rails:9:in `<top (required)>'
/Users/ユーザー名/projects/アプリ名/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
テーブルを作成することはできなかった。エラーメッセージの中で注目したのは、以下2箇所。
Mysql2::Error: Table 'アプリ名_development.users' doesn't exist
Mysql2::Error: Cannot add foreign key constraint
「usersテーブルが存在しないので、外部キー制約を追加することができない。」ということだと考えたが、usersテーブルを作成するマイグレーションファイルはdownにしているだけで、削除はしていないので初めはよくわからなかった。
結局、確実な答えはわからなかったが、マイグレーションファイルの読み込まれる順番が、usersテーブルを作成するものよりrecordsテーブルを作成するものの方が先だから参照できるテーブルがないと判断されていると考えた。(これは自分がrecordsテーブル作成のマイグレーションファイルを先に生成した為)
理由は、上記エラーメッセージにusersテーブル読み込みに関するエラーメッセージがない点にあり、recordsテーブル読み込み失敗で動作が止まってしまったという流れがあると考えた方が自然だからである。
つまり、usersテーブルの方だけを先に読み込むことができれば上手くいくかもしれないと考え、指定したマイグレーションファイルのみを実行するやり方を調べたところ、
rake db:migrate VERSION=マイグレーションファイルのID
を実行することで可能らしいので、usersテーブルのマイグレーションファイルのIDを入れて実行したが、①-4でのエラーメッセージ
と全く同じものがでた。
この時点で仮説でのやり方①は無理だと判断し、references型をマイグレーションファイルから除いて、
rails db:migrate
を実行して当初のテーブルの状態に戻し、やり方②に取り掛かった。
#「②rails g migration Addカラム名Toテーブル名
のコマンドを実行して、新しく作成されたマイグレーションファイルのchangeメソッド内にadd_column :テーブル名, :カラム名, :データ型, オプション
を記述する。」 を実践
②-1. rails g migration AddUserToRecords
を実行し、生成されたマイグレーションファイルを以下の様に編集。
class AddUserToRecord < ActiveRecord::Migration[6.0]
def change
add_reference :records, :user, null: false, foreign_key: true
end
end
②-2. ``rails db:migrate``で実行……しようとしたが、これではできないことを記事(下部の参考記事に載せてある)で見た為、実行もせずエラーメッセージも見ずに①で生成されたマイグレーションファイルを削除。
結局のところ、自身の仮説ではどちらもreferences型のカラムを追加することができなかった。
#正解は以下の通り
①rails g migration ファイル名(キャメルorスネークケース) モデル名:references
でマイグレーションファイルを作成
(自分の場合は rails g migration Add_User_id_To_Record user:references
)
②生成されたマイグレーションファイルを以下の様に編集
class AddUserIdToRecord < ActiveRecord::Migration[6.0]
def change
add_reference :records, :user, null: false, foreign_key: true
end
end
③rails db:migrate
を実行し、以下の様に出力された
== 20201212172855 AddUserIdToRecord: migrating ================================
-- add_reference(:records, :user, {:null=>false, :foreign_key=>true})
-> 0.2707s
== 20201212172855 AddUserIdToRecord: migrated (0.2709s) =======================
#参考記事
it-swarm-ja.tech 「特定の移行をロールバックする方法」
https://www.it-swarm-ja.tech/ja/ruby-on-rails/%E7%89%B9%E5%AE%9A%E3%81%AE%E7%A7%BB%E8%A1%8C%E3%82%92%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/970919017/
(2020年12月12日閲覧)
その辺にいるWebエンジニアの備忘録 「後からreferencesカラムを追加しようとするとdb:migrateできない」
https://kossy-web-engineer.hatenablog.com/entry/2018/09/03/022121
(2020年12月12日閲覧)
Qiita(@ryouya3948さん) 「【Rails】外部キー制約とreference型について」
https://qiita.com/ryouya3948/items/7417c3acabb4d5e1657b
(2020年12月12日閲覧)