スポンサーリンク

Ruby on Railsのマイグレーションで,テストデータやサンプルデータをうまく管理する方法

Ruby on Railsで開発を進めていく際,マイグレーション中には

  1. スキーマ情報
  2. マスタデータ
  3. サンプルデータ

などの構築情報を含めることができる。



たとえば 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. 次にサンプルデータのマイグレーションを収集して,
  3. 1と2を並べたものを,「01_」から順に最後まで番号を振りなおして,
  4. 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. 初期状態
  2. サンプルデータが投入された状態

の両方を,それぞれバッチ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コードとして記述しておく必要が生じる。

そこで今回の方法が役立つ。



 

*1:機関車本のp73のAddTestDataなどがその例。

*2:本当はINSERT文だけで全部済むようなDB設計が望ましいのだが,現実的にそういう設計が採用できない場合もある。