データベースからテストフィクスチャを抽出する(to_yaml 不使用)
ar_fixtures の出力はなんだかダサくない?
データベースから yaml 形式のフィクスチャを抽出するプラグインとしては、ar_fixtures がある。さっきインストールして使ってみたけど、どうも yaml の出力が気に入らない。たとえば次のような感じになってしまう。
--- - !ruby/object:Entry attributes: title: MyString1 body: MyText1 id: "1" - !ruby/object:Entry attributes: title: MyString2 body: MyText2 id: "2"
- !ruby/object:Entry ってなんだかよくわからないのがついてるし、カラムの表示順がランダムになってしまっていて、見づらい。次のような出力がほしいのだ。
entry1: id: 1 title: MyString1 body: MyText1 entry2: id: 2 title: MyString2 body: MyText2
あと、ar_fixtures は内部的に to_yaml というメソッドを使っていて、これが UTF-8 の文字列をうまく扱えない。そこで、
日本語をto_yamlするとエンコードされてしまう問題を安直な方法で解決する
to_yamlでUTF-8な日本語がbinaryになってしまう問題を回避するRailsプラグイン
みたいな hack が必要になってくる。
そんなわけで、Chad Flower「Rails レシピ」のレシピ41「生データからのテストフィクスチャの抽出」(p155) のソースコードをベースに次のような Rake タスクを作ってみた。to_yaml は使ってないので、UTF-8 文字列にまつわる頭痛とも無縁だ。興味があれば、使ってみてほしい。
使い方
下のコードを丸ごとコピーして、extract_fixtures.rake というファイルを作り、$RAILS_ROOT/lib/tasks の直下に置く。すると rake db:fixtures:extract という新しい Rake タスクが使えるようになる。
% rake db:fixtures:extract # => すべてテーブルの内容が tmp/fixtures/*.yml に抽出される % rake db:fixtures:extract FIXTURES=entries,comments # => テーブル entries, comments の内容のみが tmp/fixtures/*.yml に抽出される
という感じで使えるはずだ。
ソースコード
def fixture_entry(table_name, obj) res = [] klass = table_name.singularize.camelize.constantize res << "#{table_name.singularize}#{obj['id']}:" klass.columns.each do |column| res << " #{column.name}: #{obj[column.name]}" end res.join("\n") end namespace :db do fixtures_dir = "#{RAILS_ROOT}/tmp/fixtures/" namespace :fixtures do desc "Extract database data to the tmp/fixtures/ directory. Use FIXTURES=table_name[,table_name...] to specify table names to extract. Otherwise, all the table data will be extracted." task :extract => :environment do sql = "SELECT * FROM %s ORDER BY id" skip_tables = ["schema_info"] ActiveRecord::Base.establish_connection FileUtils.mkdir_p(fixtures_dir) if ENV['FIXTURES'] table_names = ENV['FIXTURES'].split(/,/) else table_names = (ActiveRecord::Base.connection.tables - skip_tables) end table_names.each do |table_name| File.open("#{fixtures_dir}#{table_name}.yml", "w") do |file| objects = ActiveRecord::Base.connection.select_all(sql % table_name) objects.each do |obj| file.write fixture_entry(table_name, obj) + "\n\n" end end end end end end