DIVX テックブログ

catch-img

Rails初学者に向けたマイグレーションの仕組みと実践方法


目次[非表示]

  1. 1.はじめに
  2. 2.実施環境
  3. 3.マイグレーションとは
  4. 4.up・downについて
  5. 5.修正方法2つ
    1. 5.1.修正したマイグレーションファイルの追加
    2. 5.2.ロールバック
    3. 5.3.changeについて
  6. 6.NO FILEと表示された時の対処法
    1. 6.1.そもそもなぜこのようなエラーが起こるか
    2. 6.2.解決方法
  7. 7.終わりに
  8. 8.お悩みご相談ください


はじめに

エンジニアのみなさん、こんにちは。
入社してから1年が過ぎ、日々知識を吸収しながら業務にあたっています。

最近Ruby on Rails(以下、Rails)での開発案件にアサインされました。
Rails自体は過去に一度触ったことがありますが、その時にはバックのロジックを修正しただけで、データベース(以下、DB)設計などはしたことがありませんでした。

実際に業務を進めていく中でDB設計をすることとなり、「マイグレーション」という言葉を耳にするようになりました。
マイグレーションってなんだろう?と思い調べてみるも、DBに関連していること以外は結局よくわからず苦労をしました。

今回の記事では、自分と同じようなRails初学者の方向けにマイグレーションとは?と言った部分からその仕組みやマイグレーションファイルの修正方法、自分が出会ったエラーの解決方法などをご紹介していきたいと思います。

実施環境

本題の前に、まずこの記事には実際に色々検証してみた結果が出てきます。 検証環境は以下になります。

  • MacBookAir
    • チップ → Apple M2 1
    • メモリ → 16GB
    • macOS → Ventura 13.2.1
  • rubyのバージョン → 3.2.3
  • rbenvのバージョン → 1.2.0
  • railsのバージョン → 7.1.3
  • mysqlのバージョン → 8.0
  • コードエディター → VScode

いよいよ本題に入ります。

マイグレーションとは

まず、そもそもの話になりますがマイグレーションとは何かを噛み砕いて説明していきたいと思います。もう知っているよという方は次の章に進んでいただいて大丈夫です。

公式のリファレンスでは以下のように紹介されています。

マイグレーションは、データベーススキーマの継続的な変更(英語)を、統一的かつ簡単に行なうための便利な手法です。マイグレーションではRubyのDSLが使われているので、生のSQLを作成する必要がなく、スキーマおよびスキーマ変更がデータベースに依存しなくなります。
個別のマイグレーションは、データベースの新しい「バージョン」とみなせます。スキーマは空の状態から始まり、マイグレーションによる変更が加わるたびにテーブル、カラム、エントリが追加または削除されます。Active Recordはマイグレーションの時系列に沿ってスキーマを更新する方法を知っているので、履歴のどの時点からでも最新バージョンのスキーマに更新できます。Active Recordはdb/schema.rbファイルを更新し、データベースの最新の構造と一致するようにします。

https://railsguides.jp/active_record_migrations.html より引用


正直一読した時点では初学者の方にとっては理解し難い内容かと思います。
なので以下に噛み砕いて説明をします。

まずDBを作るときに、何という名前のテーブルがあって、そのテーブルにはどういう型の何という名前のカラムがあるかといった情報が必要になるかと思います。
それらの情報をマイグレーションファイルというファイルに書き込みます。つまりマイグレーションファイルとはDBの設計図になります。
そしてこのマイグレーションファイルに記述された内容を元に、テーブルの作成やカラムの追加・削除などの操作を行うことをマイグレーション(マイグレーション実行)と言います。

実際にマイグレーションファイルを作ってみます。
最初ですので、ターミナルにコマンドを打ち込むところからやってみます。
なお、本記事ではマイグレーションファイルの修正方法や仕組みについてがメインであるため、具体的なマイグレーションファイル内の記述については触れません。

以下をmacのターミナルに打ち込みます。

rails generate migration クラス名

クラス名はパスカルケースで書くことが多いです。

下記サイトのように、マイグレーションやRailsにおける命名規則を紹介しているサイトがありますので、命名規則に則って名前をつけます。
https://www.sejuku.net/blog/60950#index_id3:~:text=みました。-,migrationクラスの命名規則,-マイグレーション

今回はよくありそうな商品情報を管理するProductsテーブルを作成しようと思います。クラス名をCreateProductsとします。

すると以下のように
/db/migrate直下に
20240613082350_create_products.rb
というファイルが出来上がりました。
パスカルケースはスネークケースに変換されますが、気にしないで大丈夫です。

VScode

ファイルの中身(まだ何も書いていない)

class CreateProducts < ActiveRecord::Migration[7.1]
  def change

  end
end

ファイル名はスネークケースですが、クラス名はパスカルケースのままです。

そしてこのマイグレーションファイルのファイル名に注目です。
20240613082350 〇〇〇〇〇〇〇〇
というような名前になっています。

先頭によくわからない数字の羅列がありますが、これはこのファイルが作られた日時を表しています。この日時のことをタイムスタンプと言います。
上の例でいうと2024年6月13日の8時23分50秒にこのマイグレーションファイルが作られたことを意味します。

なぜこのようにタイムスタンプがファイル名になっているかというと、マイグレーションは時系列順に実行されるからです。

例えば
20240613082350 〇〇〇〇〇〇〇〇
20240613082351 □□□□□□□□□□ という2つのマイグレーションファイルがあったら、20240613082351 □□□□□□□□□□ の方が作られた時間が1秒遅いので、こちらの方が後に実行されます。

時系列によってテーブル操作が行われることで、テーブル同士で整合性が取れます
後にマイグレーションファイルの修正方法をご紹介しますが、その時に非常に便利です。

そちらは後で触れるので、ここではマイグレーションファイルの時系列順にマイグレーションが実行されるんだな〜くらいの認識で大丈夫です。

マイグレーションが実行されると schema.rb というファイルが自動で更新されます。
このschema.rbには何が書かれているかというと、最新のマイグレーションが実行された日時とその内容が書かれています。
つまりこのファイルをみれば今どういうテーブルがあって、その中にはどういう型のどういうカラムがあるかということが一眼で分かるわけです。

up・downについて

急に謎の英単語が出てきました。
Railsにまつわるこの考え方が以降の内容に深く関わるので、紹介したいと思います。

実際の結果を見た方が早いと思うので、ターミナルで以下のように打ってみます。

rails db:migrate:status

上記のコマンドを実行すると以下のように返ってきました。(記号の部分はクラス名です)

Status  Migration ID  Migration Name
----------------------------------------------------
up   2024020905247 Create 〇〇
up   20240223015331 Rename △△
up   20240407072621 Create □□
down  20240409042306 Create ◇◇


これはそれぞれのマイグレーションファイルのステータスをup・downと共に一覧で表してくれています。
このup・downが何を意味かしているかというと、マイグレーションファイルがDBに反映済みであるか、そうでないかを表しています。
反映済みであればup、そうでなければdownが表示されます。 上記の例で言うと一番下のマイグレーションファイルがDBに反映されていないということになります。

そして rails db:migrate 実行時はステータスがdownのマイグレーションファイルのみがDBに反映されます。
DBに反映された後のマイグレーションファイルは反映済みということでステータスがupになります。

以下を簡単に図解化しました。(汚くてすいません。。。)


なのでup(DBに反映済み)になっている状態のマイグレーションファイルを修正して、rails db:migrate を再度実行しても、そもそもupのファイルは無視されてDBに反映されないので、意味はありません。

修正方法2つ

上記でupの状態のファイルを修正して再度rails db:migrate を実行しても意味はないと書きました。 じゃあ、すでにDBに反映済みのカラムを削除したい場合や、逆にカラムを追加したい場合は、テーブル操作はどうすればいいのか?ということで、修正方法についてご紹介します。

修正方法は大きく分けて以下の2つがあります。

修正したマイグレーションファイルの追加

マイグレーション実行時に、downのファイルしか見られないと言うのであれば、修正したマイグレーションファイルを新たに作っちゃおうという方法です。
この方法であれば、新たに作ったマイグレーションファイルのステータスはまだマイグレーションを実行していないので、downになるはずです。

以下のようなステータスが既にupのマイグレーションファイルがあったとします。

class CreateProducts < ActiveRecord::Migration[7.1]
 def change
  create_table :products do |t|
   t.string :product_name
   t.bigint :product_id

      t.timestamps
  end
 end
end


このマイグレーションファイルではproduct_nameというカラムを作成しています。
修正用のマイグレーションファイルを作成してproduct_nameというカラムを削除したい時は以下のようにします。

まず修正用のマイグレーションファイルを作成します。
作り方は最初と同じです。 今回はProductsテーブルからproduct_nameカラムを削除するので、RemoveProductNameFromProductsとします。

rails generate migration RemoveProductNameFromProducts


このコマンドをターミナルに入力すると新しいマイグレーションファイルが作成されます。そして以下のように記述します。

class RemoveProductNameFromProducts < ActiveRecord::Migration[7.1]
 def change
  remove_column :products, :product_name, :string
 end
end


該当箇所についてのみ記述するだけでOKです。product_nameカラムを削除したいだけなので、他のカラム等については記述しなくていいということです。
なお、changeというメソッドが登場していますが、こちらは後で触れます。

修正ファイルを作成し、マイグレーション実行前のステータスです。


今作ったRemoveProductNameFromProductsはマイグレーションを実行していないので当然DBは何も変わっていません。

DBのカラムの状態は以下です。


ここでマイグレーションを実行するとステータスがdownからupになります。
実際にDBを確認するとproduct_nameカラムが削除されていることがわかります。


修正用のマイグレーションファイルを作成することでDBの修正ができました。

ロールバック

もう1つ修正方法があります。

まず先に、デメリットをお伝えします。
こちらの方法を行うと、該当のテーブルのデータが消えてしまいます。ですので、本番環境などのすでにシステムが稼働している場合は基本的には使わない方法です。
ローカル環境内など、消えてもいいデータが保存されているテーブルに対して行う方法です。ですのでロールバックを行うときはバックアップなどをとっておくことを推奨します。
以下は基本的にローカル内で使う方法であるということをご留意ください。

1つ目の方法だと修正をするたびに新たなマイグレーションファイルが作られます。  
修正したマイグレーションファイルをさらに修正することが繰り返される場合、無尽蔵にマイグレーションファイルが増えていく可能性があります。ファイル数が増えれば増えるほど管理が面倒くさくなります。

そこでファイル数を増やさない修正方法がこのロールバックという方法になります。
考え方としてはup・downがわかっていれば問題ないです。
結局downのファイルしかマイグレーション実行時に見られないので、修正したいマイグレーションファイルのステータスをdownにしてから、修正し、再度マイグレーションを行うというものです。

ファイルのステータスをdownにすれば、マイグレーション実行時に無視されないという根本的な考え方はどちらの方法を使っても共通しています。

また、ロールバックする際は修正したい該当のファイルまで時系列を遡ってdownにしていくので、特にファイル数が多い場合は1つ目の方法よりは多少面倒くさい部分があります。

では具体的なロールバックの方法を紹介します。
以下のようにステータスがupのファイルが3つ並んでいるとします。


ターミナル上でrails db:rollback と入力し実行すると、一番最新のマイグレーションファイルのステータスがdownになります。さらにもう一回rails db:rollback を実行するとその次に新しいマイグレーションファイルのステータスがdownになります。
このように、修正したいマイグレーションファイルまで1つづつ時系列を遡っていきます。

なので20240613082350 Create productsの記述内容を修正したいときはrails db:rollback を3回実行します。一応複数ファイルを一度に差し戻す方法もありますが、本記事ではデフォルトの1つづつ差し戻す方法でロールバックを行います。



20240613082350 Create productsがdownになっているので、この状態で修正を行いマイグレーションを実行します。
今回はproduct_nameカラムを削除する修正を行います。

元のマイグレーションファイル

class CreateProducts < ActiveRecord::Migration[7.1]
 def change
  create_table :products do |t|
   t.string :product_name
   t.bigint :product_id

      t.timestamps
  end
 end
end


元のDBの状態



修正したマイグレーションファイル(product_nameの記述部分を削除)

class CreateProducts < ActiveRecord::Migration[7.1]
 def change
  create_table :products do |t|
   t.bigint :product_id

      t.timestamps
  end
 end
end


修正したDBの状態


こちらの方法でもカラムが消えていることが確認できました。

changeについて

まず、上記の修正では使用していませんが、upというメソッドとdownというメソッドがあります。
これは先ほど紹介していたup・downと関係するもので、down → upにするときはupメソッドが実行され、up → downにするときはdownメソッドの内容が実行されます。

下記の例ではupする時に元々あったproduct_nameカラムを削除し、downする時に元の状態に戻るようにproduct_nameカラムを追加するというものです。

def up
 remove_column :products, :product_name, :string
end

def down
 add_column :products, :product_name, :string
end


なぜこんな面倒くさいことをするかというと、最初に紹介した時系列というのがポイントになってきます。
upしたマイグレーションファイルにロールバックを行いdownした時、元の状態に戻すには時系列を遡っていく必要があるので、反対の内容が書かれていないと整合性が取れなくなるからです。

なので基本的にはupメソッドを書いたのであれば、同時にdownメソッドも書いておくことが推奨されます。

次に本題のchangeについてです。
前の章まではchangeというメソッドを使っておりました。
changeというメソッドはロールバックする時に逆の処理を自動で行ってくれるものです。なので時系列を遡ってロールバックしていっても問題なくDBの状態が元通りになります。
すごく簡単に大雑把にいうとupとdownの両方を一度に記述できるメソッドです。なので、前の章まではロールバックしても問題なくDBが元の状態に戻りました。

「じゃあ全部up・downに分けないでchangeで書けばいいじゃん」となりそうですが、全てのテーブル操作がchangeで対応できるわけではないのです。

下記が公式のchangeに対応しているテーブル操作です。 https://railsguides.jp/active_record_migrations.html#changeメソッドを使う

自分が行いたいテーブル操作によってchangeを使うのか、up・downを使うのかを使い分けましょう。

NO FILEと表示された時の対処法

この章では自分が実際に出会ってエラーとその解決法をご紹介します。

以下のようにマイグレーションファイル名が表示されるはずの場所に、NO FILEと表示されました。最初は今までにこんなことが無かったので戸惑いました。


このような状態になると、NO FILEより前にロールバックすることはできません。
もし、自分が修正したいファイルがそれよりも前にある場合はぜひ解決したい問題です。

そもそもなぜこのようなエラーが起こるか

このようなエラーが起こる問題は、NO FILEという名前の通り、ステータスがupのファイルが見つからないためです。
upのファイルを削除してしまったり、Gitでブランチを変えた際に変えたブランチでup済みのマイグレーションファイルが見つからない場合などにこうなります。

コンピューター的には「このマイグレーションファイルの変更はDBに反映しているよ〜、あれ?でもそのファイルが見つからないな〜。」となるわけです。

逆にステータスがdownのマイグレーションファイルを消去しても、問題はありません。
rails db:migrate:status 実行時に自動で一覧から消えます。

この状態になると、上述の通りステータスをdownにしようにものそのファイルが見つからないので、NO FILEより前にはロールバックができなくなります。

解決方法

コンピューター的にはマイグレーションファイルが見つからないことが原因なので、同じタイムスタンプのマイグレーションファイルを作ってあげます。
実施にやってみます。

まず、同じタイムスタンプのマイグレーションファイルを作るところからです。
rails generate migration コマンドを使うと自動的にタイムスタンプが付与されて、同じタイムスタンプのファイルが作成できなくなるので、マイグレーションファイルはエディター上で作るなり、touchコマンドで作るなりします。

今回はタイムスタンプが20240613084244のファイルがNO FILEとなっているので、 20240613084244_create_re_accounts.rb
というファイル名のマイグレーションファイルを新たに作成します。

re_accountsの部分は削除してしまう前のマイグレーションファイルと同じ名前でなくてもいいです。
ダミーファイルなので中身の記述も以下のように最低限で構いません。

VScodeの画面



20240613084244_create_re_accounts.rbの記述内容

class CreateReAccounts < ActiveRecord::Migration[7.1]
 def change

 end
end


このファイルを保存してrails db:migrate:status を実行してみると、NO FILEがなくなり正常に表示されたことがわかります。


これでさらに前にもロールバックすることができます。
また、不要なファイルがあれば、消すときはdownにしてしてから削除しましょう。
また、ブランチを変える際は、変更先のブランチに存在しないファイルのステータスが変更前のブランチでdownになっているか確認してから変更しましょう。

終わりに

以上 マイグレーションの仕組みやマイグレーションの修正方法などでした。
こうやって記事を書くことが初めてなので、伝わりにくかったかもしれませんが、ご覧いただきありがとうございました。
これからも業務を進めていく中で、このようにわからないこととたくさん出会うと思いますが、その都度調べて、このように記事にできたらと思います。

お悩みご相談ください


  ご相談フォーム | 株式会社divx(ディブエックス) DIVXのご相談フォームページです。 株式会社divx(ディブエックス)



お気軽にご相談ください


ご不明な点はお気軽に
お問い合わせください

サービス資料や
お役立ち資料はこちら

DIVXメディアカテゴリー

テックブログ タグ一覧

採用ブログ タグ一覧

人気記事ランキング

GoTopイメージ