開発者間でDBデータを共有するためのRakeタスク

  • 仕様
    • rake db:data:dump
      • DB内のすべてのテーブルのデータを抽出(schema_infoテーブルを除く)
      • 接続先DBは、#{RAILS_ENV} に従います
      • 抽出先は、#{RAILS_ROOT}/db/data
      • YAML形式(日本語対応)
    • rake db:data:load
      • #{RAILS_ROOT}/db/data 内の YAML形式のデータをすべてDBにロード
      • ロード前にDB内の既存のデータはすべて消えます
  • 動機

ペアプロで開発中に実装者を交代したところ、開発しながら適宜用意してきたデータの受け渡しに困ってしまいました。ここでは、各開発者はローカルに自分用のDBを持っているとします。開発しながら自由にデータを弄っています。スキーマの同期は migrate で対応していますが、データ自体は migrate には記述していません。
fixture あたりを上手に使えないかと探してみると「Railsレシピ」によさげなものを発見しました。これを利用して日本語の文字化け問題を解決した状態でDBからのデータ抽出ができましたが、 fixtureをすべて上書きしてしまうのはあまりに忍びない...抽出したデータを元に fixtureを作成するのが正しい気がします。楽だし。 (^^;
なので、別フォルダにデータを YAML形式で抽出して、必要であればDBにロードできるような Rakeタスクを作成してみました。

  • 使い方

べちっと。

rake db:data:dump

YAML形式で抽出されたデータを、開発者間で共有。どうやって共有するかはとりあえず置いておく。ファイル共有?バージョン管理システム??
共有された側が、

rake db:data:load

相手のデータをそっくりそのままロードできます。ただし、rake db:migrate でスキーマの同期をあらかじめ取っておく必要があります。

  • ソース
    • share_data.rake
require 'yaml/encoding'
class String
  alias :old_to_yaml :to_yaml
  def to_yaml(ops = {})
    YAML.escape(self).old_to_yaml(ops)
  end
end

require "yaml"
class String
  def is_binary_data?
    false
  end

  def decode
    gsub(/\\x(\w{2})/){[Regexp.last_match.captures.first.to_i(16)].pack("C")}
  end
end

ObjectSpace.each_object(Class){|klass|
  klass.class_eval{
    if method_defined?(:to_yaml) && !method_defined?(:to_yaml_with_decode)
      def to_yaml_with_decode(*args)
        result = to_yaml_without_decode(*args)
        if result.kind_of? String
          result.decode
        else
          result
        end
      end
      alias_method :to_yaml_without_decode, :to_yaml
      alias_method :to_yaml, :to_yaml_with_decode
    end
  }
}

namespace :db do
  namespace :data do
    desc "dump data from database for sharing among developers"
    task :dump => :environment do
      sql = "SELECT * from %s order by id"
      skip_tables = ["schema_info"]
      ActiveRecord::Base.establish_connection
      (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
        i = "000"
        FileUtils.mkdir_p("#{RAILS_ROOT}/db/data")
        File.open("#{RAILS_ROOT}/db/data/#{table_name}.yml", 'w') do |file|
          data = ActiveRecord::Base.connection.select_all(sql % table_name)
          file.write data.inject({}) { |hash, record|
            hash["#{table_name}_#{i.succ!}"] = record 
            hash
          }.to_yaml()
        end
      end
    end

    desc "load data to database for sharing among developers"
    task :load => :environment do
      require 'active_record/fixtures'
      ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
      (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'db', 'data', '*.{yml,csv}'))).each do |fixture_file|
        Fixtures.create_fixtures('db/data', File.basename(fixture_file, '.*'))
      end
    end

  end
end

Railsレシピ

Railsレシピ