Ruby on Railsのマイグレーションで,テストデータやサンプルデータをうまく管理する方法
Ruby on Railsで開発を進めていく際,マイグレーション中には
- スキーマ情報
- マスタデータ
- サンプルデータ
などの構築情報を含めることができる。
たとえば 01_create_users.rb でまず users というテーブルを作成したら,
すぐに 02_add_users.rb でサンプルのユーザ情報を投入し,動作確認できる。
DB構造をすぐにテストできるので,これは便利だ。*1
例:
Railsのmigrationで大量のデータを生成する
http://rubyist.g.hatena.ne.jp/Ubuntu/...RailsのActiveRecordでダミーデーターを生成する
http://db2.jugem.cc/?eid=1558
しかしアプリのリリース時までには,サンプルデータを全て除去し,アプリの「初期状態」を再現・テストしなければならない。
サンプルデータ投入のマイグレーションは開発中は役に立っていても,リリース時には余計な存在になってしまう。
マイグレーション管理時には,アプリケーションにとって必須な情報と,そうでないサンプルデータとを分離する必要がある。
開発の最初の段階から,両者を別々に管理する事にしよう。
下記はその方法。
(1)基本マイグレーション
基本マイグレーションとは,アプリの動作に必須なマイグレーションファイルのこと。
- スキーマ
- マスタデータ
- デフォルトデータ
などを生成するマイグレーションファイルの集まり。
これを実行すると,アプリケーションの初期状態を作り出すことができる。
逆に,これが無いとアプリが動かない。
必須マイグレーションと呼んでもいい。
ここでは下記のようなパスを仮定して,この基本マイグレーションを管理する。
- /root/hoge/ : Railsルート
- /root/hoge/db/migrate_basic : 基本マイグレーションの格納ディレクトリ
- /root/hoge/db/migrate : マイグレーション実行時にファイルを置くディレクトリ。リポジトリ上では空。
基本マイグレーションを実行するシェルスクリプト
migration_basic.sh
#!/bin/sh # ディレクトリ情報 rails_root_dir="/root/hoge" # Railsルート(developmentモード) migrate_exec_dir="${rails_root_dir}/db/migrate" # マイグレーション実行ディレクトリ migrate_basic_dir="${rails_root_dir}/db/migrate_basic" # 基本マイグレーションのあるディレクトリ # down実行(バージョン0に戻す) cd ${rails_root_dir} rake db:migrate VERSION=0 # up用のファイルを収集(基本マイグレーションのみ) rm -rf ${migrate_exec_dir}/* cp -p ${migrate_basic_dir}/*.rb ${migrate_exec_dir} # up実行 rake db:migrate # up実行後のマイグレーションファイルは, # 次回のdown実行のためにそのままにしておく。
※マイグレーションの「保管場所」と「実行場所」を分離しているところがミソ。
もし保管場所として別のパスを指定すれば,全然異なるマイグレーションセットを実行できる。
(2)サンプルデータ付きのマイグレーション
基本マイグレーションに加え,開発で利用するサンプルデータを投入したい場合。
下記のパスにそれらのrbファイルを格納しておく。
- /root/hoge/db/migrate_additional : サンプルデータのマイグレーションの格納ディレクトリ
ファイルの番号は,基本マイグレーションとは別に,サンプルデータの中で「01_〜」から連番をふってよい。
処理の流れとしては,
- まず基本マイグレーションを収集して,
- 次にサンプルデータのマイグレーションを収集して,
- 1と2を並べたものを,「01_」から順に最後まで番号を振りなおして,
- 3に対してマイグレーションを実行
という感じになる。
前項のシェル内で「up用のファイルを収集」となっている所を少し書き換えればよい。
migration_with_sample.sh
#!/bin/sh # ディレクトリ情報 rails_root_dir="/root/hoge" # Railsルート(developmentモード) migrate_exec_dir="${rails_root_dir}/db/migrate" # マイグレーション実行ディレクトリ migrate_basic_dir="${rails_root_dir}/db/migrate_basic" # 基本マイグレーションのあるディレクトリ migrate_additional_dir="${rails_root_dir}/db/migrate_additional" # サンプルデータのあるディレクトリ # down実行(バージョン0に戻す) cd ${rails_root_dir} rake db:migrate VERSION=0 # up用のファイルを収集(1:基本マイグレーション) rm -rf ${migrate_exec_dir}/* cp -p ${migrate_basic_dir}/*.rb ${migrate_exec_dir} # up用のファイルを収集(2:サンプルデータ) basic_files=$(ls -1 ${migrate_basic_dir/* | wc -l}) counter_num=`expr ${basic_files} + 1` # 基本マイグレーションの次から番号を振り始める for old_name in $(ls -1 ${migrate_additional_dir}/ ) do # マイグレーション番号を0パディングする counter_str=$( echo ${counter_num} | perl -nle 'printf(qq{%03d},$_);' ) # 番号を振りなおしたファイル名を取得 new_name=$( echo "${old_name},${counter_str}" | perl -nle '($name,$counter_str)=split/,/;$name=~s/^\d{3}/$counter_str/g;print qq{$name};' ) # 新ファイル名でコピー cp -p ${migrate_additional_dir}/${old_name} ${migrate_exec_dir}/${new_name} # 番号を増やす counter_num=`expr ${counter_num} + 1` done # up実行(サンプルデータ付き) rake db:migrate # up実行後のマイグレーションファイルは, # 次回のdown実行のためにそのままにしておく。
コマンドの補足:
- ls -1 はフォルダ内のファイル名を縦長に列挙する。
- wc -l は行数を出力する。
- perlのqqはダブルクオートで囲むのと同じ。
- シングルクオート中ではbashの変数展開はされないので,perlに渡っているコマンド内の$〜は,bashではなくperlの変数。
- exprは足し算などの算術演算を行なう。
これで,アプリケーションの
- 初期状態
- サンプルデータが投入された状態
の両方を,それぞれバッチ1つで再現できるようになった。
サンプルデータを分離することによって,各種サンプルデータを別個に投入しやすくなったのだ。
これはテストデータの投入に利用することができる。
Seleniumで回帰テストを行なう前にDBにテストデータを投入しておきたい場合,
それ専用のサンプルデータ付きのマイグレーションを準備しておいて,テスト前に毎回上記スクリプトを実行すればよい。
補足
ここではLinux上でRailsを動かしているので,たまたまbashのスクリプトを作成した。
やりたいことは単にファイルの移動なので,別にbashではなくてもrubyやWSHなど好きな方法でスクリプトを書いてOK。
ちなみに,テストデータとして CSVやyaml形式のfixturesを使うという手もある。
特に単体テストではそれが一般的。
マイグレーションでフィクスチャ(マイグレーション内からCSVインポートする)
http://d.hatena.ne.jp/hs9587/20090323...
第12回 CSVのfixtureを取り込んで、都道府県を選択するセレクトボックスを作ってみる Rails格闘記
http://ponk.jp/?p=2266
rake db:fixtures:load FIXTURES=フィクスチャ名
だがしかし,少し複雑なアプリケーションだと,CSVやyamlだけ(=つまりINSERT文だけ)では「運用中の状態」を再現できなくなる。*2
サンプルデータの投入に際して,
- ローカルに保管されている各種ファイルを初期化したり,
- フォルダを作成したり,
- テーブルが動的に生成されたり,
- カラムが動的に追加されたり,
といったドロドロした処理が伴う場合があるからだ。
こういった一連の初期化手続きは,モデル内にビジネスロジックとして登録しておくわけだが,もしCSVとかyamlを使ってしまうとこういった用途でモデルのコードを呼び出すことができない。
このような処理はfixturesではなく,サンプルデータ用のマイグレーションファイル内に,通常のrubyコードとして記述しておく必要が生じる。
そこで今回の方法が役立つ。