初めまして。当方実務歴2ヵ月半のド新米プログラマーです。
予てから知識整理や備忘録としてQiitaに記事を投稿していきたいと考えていましたが、業務中にちょうどいい(?)トラブルに遭遇したので、今回初めて記事を書かせていただきます。
まだ記事の書き方もよくわからないため、知識不足な点や稚拙な表現など、読みづらい部分が多々あるかと思いますが、ご容赦ください。また、ご指摘がご指導があれば、コメントや編集リクエストにてぜひお願いいたします。
発端
既存プロジェクトを写経しながらAPI作成の勉強中、突然localhostのDBのデータが全部消えました。
原因の考察は後ほどするとして、解決までの道のりから書いていきます。
環境
OS : macOS Mojave 10.14.1
Docker : 18.09.0
Ruby : 2.4.2
Rails : 5.2.0
MySQL : 5.6
Sequel Pro : 1.1.2
対応
1.環境を再構築
ディレクトリからローカルリポジトリからDockerコンテナまで一旦全て削除し、初めから環境を作り直します。
Dockerコンテナの削除は下記を参考にしました。
→Dockerイメージとコンテナの削除方法
削除し終わったら、Gitを再度クローンし、docker-compose build
でコンテナをビルドし直して、docker-compose up -d
で起動。
結果:解決せず
2.レコードのdumpファイルからリストア
ここで職場の先輩にSOS。
DBのデータが格納されるディレクトリを削除してdocker-compose rm api
、docker-compose rm db
からのdocker-compose build
で理論上は元に戻るということで、そのディレクトリを探すことに。
するとその途中で、プロジェクト内にDBレコードのdumpファイルが保存されていることが判明したため、方針転換してそのファイルからDBのリストア(復元)を目指すことにしました。
下記を参考に、先輩が一瞬で対応。眺める筆者。
→gz圧縮されたmysqlのdumpをリストア
$ docker-compose exec db bash
root@PERSONAL:/# cd docker-entrypoint-initdb.d
root@PERSONAL:/docker-entrypoint-initdb.d# zcat [gzファイル名] | mysql -u[ユーザー名] -p chat
docker-entrypoint-initdb.d
はDockerの公式イメージらしいです。
→Docker MySQL公式イメージを使用してDBに初期データを流し込む
結果:普通はこれで解決…のはずなのですが、本件ではERROR 1045 (28000): Access denied for user
が発生。
ユーザー権限に関するエラーらしいのですが、先輩曰く解消するのはかなり面倒そうとのことで、別の方法を探すことに。
3.Sequel Proにgzファイルを直接インポート
筆者が空っぽのSequel Proの画面を死んだ目で見ていると
「ていうかSequelで直接gzインポートできなかったっけ?」
と思い付く先輩。
探してみると確かにありました。
[メニューバー]→[ファイル]→[インポート]から、先ほどのgzファイルを選択すると…
インポートが始まり…無事復元に成功しました!
4.結論
・DBのdumpは用意しておこう
・dumpがあればSequel Proに直接インポートしてしまおう
・先輩を崇めよ
原因
DBが消えた直前に行っていた作業はRSpecファイルの実行でしたので、そこに原因があると推測して調査をしてみました。
今回の件では、テストデータの自動生成を行うFactoryBotと、テスト実行後にそのテストデータを自動消去するDatabaseCleanerという2つのgemがRSpecの中で使われていました。このDatabaseCleanerが、今回テストデータが消えた原因ではないかと推測しました。
DatabaseCleanerの実装と使い所
RubyGems-DatabaseCleanerの設定内容がよくわかりません
実際の実装がこちら↓
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
exampleが実行されるごとにcleaningが動き、関連するテーブルがTRUNCATEで全レコード削除される、といったところでしょうか。
ログを追ってみると、実際にDatabaseCleaner.clean_with(:truncation)
の行で、ほぼ全てのテーブルに対してTRUNCATE TABLE
が実行されていました…道理で。
ただ、調べた限り上記のコードは典型的な記法のようですし、そもそも実行したのは複数あるAPI中の1つのアクションに対してのRSpecで、それがなぜDBのテーブル全てを呼び出し、それらがTRUNCATEされてしまうのかまでは執筆時点では解明できませんでした。
あるいはここ以外に原因があるのかもしれませんが…力量不足。
また詳しいことが分かったら追記していこうと思います。
今後も記録しておきたい題材があればなるべく記事にしたいと思います。それでは。