Railsで既存テーブルにカラムを追加した後に順番を変更したかったが上手くいかなかった。schema.rbファイルに設定を書いてしまい、それで良さそうならマイグレーションファイルもそれに合わせて書き直そう(とりあえずの荒業)

Ruby on Rails5で、既存のテーブルにカラムを追加し、そのあとにカラムの順番を変更したかったのですが、上手くいきませんでした。
それでもどうにかカラムを調整してデータベースを構築するための方法です。

結論から言うと、「schema.rbファイルに直接、データ構造の設定を書いてデータベースに反映させて、それで良さそうならマイグレーションファイルを書き直そう」をいう内容です。

今回の手法はとりあえずの荒業(あらわざ)です。
ただ、Railsの書籍『Ruby on Rails5アプリケーションプログラミング』に「スキーマファイルによるデータベースの再構築の方法(P.316)」が書いてあるので、あながち間違っている訳でもなさそう。鋭意勉強中です。

カラムの追加はハマりポイントでした。

・既存のテーブルにカラム(列)を追加したい
・マイグレーションファイルを作って…と思いきや(うまくいかなかった例)
・マイグレーションファイルに設定を書いて変更するのは諦めてschema.rbファイルに設定を書こう
・新しく、更新した部分のマイグレーションを修正しておこう
・その他もろもろ何かを行う手段など
・役に立ったリンク等
・Herokuで上記のデータベースをコマンドラインで構築する方法

このような内容でお届けします。

ちなみに今回の方法は、変更するテーブルのデータは消えてしまうので、テーブルを1から作り直すのとあまり変わりません。

みなさん、どうやって上手くやっているのでしょうか。
テーブルのカラムの順番を変更したいだけでかなり一苦労でした。



・既存のテーブルにカラム(列)を追加したい

テーブルを作った後に、カラム(列)を追加したい、と思うことは普通にあると思います。

例えば今回の場合「 videos」テーブルがあったとします。

・テーブル名はvideos
・列(カラム)はid、video_id、created_at(自動生成)、updated_at(自動生成)
だとします

※今回、「videosテーブルにvideo_id」という行を作ってしまっていますが、気にしないで下さい。
その後、名称を変更しています。

「created_at」と「updated_at」は自動生成される、時刻を記録するためのカラムなので、それよりも前に新規カラムを追加したいと多くの人は思うでしょう。

マイグレーションファイルを作って…と思いきや(うまくいかなかった例)

カラムを追加する場合は、通常はコマンドでまずはマイグレーションファイルを作ります。
そして、出来上がったマイグレーションファイルに追加したいカラムの設定情報を入れていきます。

今回は
rails g migration AddColumnToVideo

というコマンドでまずはAddColumnToVideoというマイグレーションファイルを作ります。
中身はほぼ空で、テンプレートだけ作られます。

AddColumnToVideoの名前は何でも良いですが、出来るだけ何をしているかを
ルールを統一して書きます。

マイグレーションファイルの中身に

add_column :videos, :title, :string #videosテーブルに動画タイトルを追加
add_column :videos, :description, :string #videosテーブルに動画説明を追加
add_column :videos, :post_day, :datetime #videosテーブルに投稿日時を追加
add_column :videos, :length, :time #videosテーブルに動画の長さを追加

を追加。

その後、マイグレーションを行います。
コマンドで
rails db:migrate

結果
PS D:\rails\songstory_app> rails db:migrate
== 20200916081909 AddColumnToVideo: migrating =================================
-- add_column(:videos, :title, :string)
-> 0.0120s
-- add_column(:videos, :description, :string)
-> 0.0008s
-- add_column(:videos, :post_day, :datetime)
-> 0.0005s
-- add_column(:videos, :length, :time)
-> 0.0004s
== 20200916081909 AddColumnToVideo: migrated (0.0157s) ========================

このようになります。

しかしこの場合、created_atとupdated_atの「うしろ」にカラムが追加されてしまいます。

そこで、これを解決する手段として after: というオプションがあるようです。しかしどうもうまく反映されませんでした。

このようになってしまいます。

「created_at」と「updated_at」のあとにカラムが作成されているのが確認できるかと思います。

今回使っているadd_columnではどうやらafter:を使えないです。
after: を使ってもうまくいきません。

change_columnではafter:が使えるらしいです。ただ、change_columnだとマイグレーションのロールバック(1つ前に戻したり)が行えません。)

add_column :videos, :title, :string, :after => :video_id
add_column :videos, :description, :string, :after => :title
add_column :videos, :post_day, :datetime, :after => :description
add_column :videos, :length, :time, :after => :post_day
このようにafterを使いましたが駄目でした。

add_column :videos, :title, :string, after: :video_id
add_column :videos, :description, :string, after: :title
add_column :videos, :post_day, :datetime, after: :description
add_column :videos, :length, :time, after: :post_day

これでも駄目でした。add_columnではafter:は使えないようです。
1行にしても駄目でした。

ちなみに、change_columnでは当然ですが行は追加できません。

add_columnを行ったあとにafter:を使ってchange_columnを行った記憶もありますが、確か上手くいきませんでした。(これはあとで再度検証してみます)

まぁいずれにせよchange_columnを混ぜるとロールバックが出来ないので色々と面倒だと分かりました。

・マイグレーションファイルに設定を書いて変更するのは諦めてschema.rbファイルに設定を書こう

マイグレーションファイルに設定を書いて変更していくのはひとまず諦めました。

一旦ロールバックしたあとにschema.rbファイルに直接、データ構造の設定を書いて反映させます。

ロールバックは
rails db:rollback

これで、先ほどのマイグレーションコマンドが1つ戻り、データベースに追加したカラムが削除されます。※ちなみにまだ何も余計な事をしていない場合はロールバックはしなくてOKです。

そしてさっきコマンド作ったマイグレーションファイルはマウスの右クリックで手動で削除します。

次に、schema.rbの場所に行きます。

アプリ>db>schema.rb
というファイルがあります。

schema.rb

create_table "videos", force: :cascade do |t|
t.string "video_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

となっている部分を

create_table "videos", force: :cascade do |t|
  t.string "video_id"
  t.string "title" #videosテーブルに動画タイトルを追加
  t.string "description" #videosテーブルに動画説明を追加
  t.datetime "post_day" #videosテーブルに投稿日時を追加
  t.time "length" #videosテーブルに動画の長さを追加
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

今回はこのように変更します。

そうして、コマンドプロンプト(ターミナル)でコマンドを実行。

rails db:schema:load

これで、上記で作成したデータベースの設定が反映されます。

実行結果
PS D:\rails\songstory_app> rails db:schema:load
-- enable_extension("plpgsql")
-> 0.0438s
-- create_table("videos", {:force=>:cascade})
-> 0.0340s
-- enable_extension("plpgsql")
-> 0.0859s
-- create_table("videos", {:force=>:cascade})
-> 0.0465s

これによりデータベースがうまく作成されました。

やりたいことは出来たので一応成功です。
ただ、この方法だとデータベースのデータは消えてしまうので、結局はテーブルを作り直すのと同じです。ご注意下さい。

ちなみに色々やってマイグレーションファイルが消せなくなった場合、空のマイグレーションファイルを作り、空のテンプレを貼った後、(参照
bundle exec rake db:migrate:down VERSION=マイグレーションファイル名

これで消せます。また、空のマイグレーションファイルを作る際は、Rails5.2の場合は
ActiveRecord::Migration[5.2]

この部分は[5.2]としましょう。
現在のRailsのバージョンと同じ番号が書かれていないとエラーになります。

・新しく、更新した部分のマイグレーションを修正しておこう

このままだと、マイグレーションファイルがschema.rbと一致しません。
そこで、追加部分のマイグレーションを、テーブルをcreateした時のマイグレーションファイルに書き足しておきましょう。

Createのときのマイグレーションファイルに

t.string :title
t.string :description
t.datetime :post_day
t.time :length

を追加し、

class CreateVideos < ActiveRecord::Migration[5.2]
  def change
    create_table :videos do |t|
      t.string :video_id
      t.string :title
      t.string :description
      t.datetime :post_day
      t.time :length
      t.timestamps
    end
  end
end

このようにしました。
おそらくこれで大丈夫なはずです。

やっている事がすべて逆な気がしますが、そのお陰で色々と勉強になりました。

今回の問題の本質は、change_columnがロールバック出来ない事だと思います。
また、整合性を保つようにマイグレーションファイルを削除するには手間がかかる事です。

開発段階であまり余計なファイルを増やすことはしたくなかったので、「書き足す前提」のマイグレーションファイルでのデータベース構築は足かせになりました。


・その他もろもろ何かを行う手段など

マイグレーションファイルを消したいときは、まずそのマイグレーションファイルをダウン状態にします。

まず、マイグレーションファイルの状態を確認。

rails db:migrate:status

これで確認できます。

マイグレーションファイルをダウン状態にするには
bundle exec rake db:migrate:down VERSION=20200916101725(ファイル名でも可)

このようなコマンドを打ちます。rakeの部分はrailsでも動くはず。VERSION=のあとはダウンさせたいバージョンのマイグレーションファイル名です。.rbまで入れて大丈夫です。

そうしたら、マイグレーションファイルの、クリエイト(作成)のものを、そのマイグレーションのみで完成形が出来るように作り直しておきます。

その後、ダウン状態を確認しておいた該当ファイルを手動で削除します。

削除したら

rails db:migrate:reset

を実行。これにより、先ほど作った完成形のマイグレーション通りのデータベースが新たに構築されます。データベースのデータは消えるのでご注意下さい。

蛇足ですが、

rails db:schema:load

このコマンドは、マイグレーションファイルではなくschema.rbファイルの情報からデータベースを構築します。

マイグレーションファイルの命名は以下のように

rails g migration change_data_post_day_to_videos

rails g migration add_column_nico_username_and_nico_user_id_to_videos

rails g migration change_data_post_day_to_videos

rails g migration rename_video_id_column_to_videos

等々、色々考えられます。 _ (アンダーバー)で区切っても、頭文字を大文字にするキャメルケース記法でもどちらでも大丈夫です。

・役に立ったリンク等

参考記事:

rails db:migrate:resetできなかったのでrails db:resetした
https://qiita.com/mom0tomo/items/a252ff8a42eea00f81b1

schema.rbとmigrationファイルの関係をまとめてみたよ
https://qiita.com/hirohero/items/2f29334878b0cb525bda

カラムのデータ型の変更(Rails)
https://qiita.com/Kiichiro-T/items/245fb6946e5bf42d889b

[Rails5]migrationファイルの削除方法(DB反映前/反映後)
https://qiita.com/gita/items/2198e2961a9fc7d10bd2

migrationファイルの削除
https://qiita.com/tanaka-t/items/cd6aa0526725e88f5024


・Herokuで上記のデータベースをコマンドラインで構築する方法

てっきり「heroku run」と書いたあとにローカルと同じようにコマンドを打てば良いのかと思っていたのですが、
うまく動きませんでした。

そこで、HeidiSQLというデータベース接続クライアントを使い、まずは  videosテーブルを手動で削除したりもしたのですが、うまくいきませんでした。

そこで、この記事にあるように

heroku pg:reset DATABASE_URL

で、データベースのリセットを行い、あとは

heroku run rake db:migrate

を行うとデータベースが構築されます。

初期データを投入したい場合は

heroku run rake db:seed

なども使えるようです。

Rails5の本番環境では破壊系のコマンドは安易に通常実行されないようにプロテクトされているようです。
本番環境では少しクセがあるという話でした。

参考記事:heroku上のDBをリセットする
https://qiita.com/G-awa/items/792b7cd60205823f7ac0

Rails5のproductionでrake db:dropはできない、普通には
https://qiita.com/Esfahan/items/75ade0233fe02ab04381


今回のやり方は「自己流で自分はこんな感じでやっているよ」という自分用のメモです。

いちいちデータベースに変更を加えようとするとデータベースをリセットしなければいけないので、もっと良いやり方があるのではないかと思っています。

ただ、「0から1への取っ掛かり用」などに役立つのではないかと思い、記事にしました。データベースは最初の取っ掛かりが難しいです。

ちなみにWindows用のデータベース接続クライアントツールにはPostgreSQL用に色々試した結果、HeidiSQLというソフトが、動作も軽くてデータベースを複数記憶してくれてログインがしやすいのでオススメです。

他の方のブログを見てみると、開発時はデータベース設計をしながらマイグレーションファイルを今回の記事のように書き直していき、デプロイ後はマイグレーションを使って運用していく、というような方もいらっしゃいました。

「Railsのマイグレーション管理方法について」
https://qiita.com/newburu/items/820ae5eba1a88e87d271

ちなみにクックパッド開発の標準アプリではマイグレーションは使っていないそうです。

Ridgepoleの導入も視野に入れたほうが良いかもしれませんね。
以上、何かの参考までどうぞ。


※なにか間違っている部分などございましたら @so_meru までリプライを頂けると助かります。