Rails: herokuでstagingからproductionにデータコピーする方法
herokuは起動に30秒かかってもOKならdyno=1で無料なので、staging環境が気軽に作れる。
stagingで確認したデータをそのままproductionに移したい。 こんなかんじでできる。
def self.copy_to_production(object) ... #DBを本番に切り替え object.class.establish_connection :adapter => 'postgresql', :encoding => 'unicode', :database => ENV["PRODUCTION_DB_NAME"], :pool => '5', :username => ENV["PRODUCTION_DB_USERNAME"], :password => ENV["PRODUCTION_DB_PASSWORD"], :host => ENV["PRODUCTION_DB_HOST"], :port => ENV["PRODUCTION_DB_PORT"] ... #本番にデータを追加 attrs = object.attributes attrs.delete("id") attrs.delete_if{|item| item.include?("_id")} #外部キーは環境で異なるので削除 new_object = object.class.create(attrs) new_object.save end
objectはActiveRecord::Baseのサブクラス(RailsのModel)のインスタンス。
establish_connectionで別DB(productionのDB)に繋ぎ直して、そっちでcreateするとそっちにcommitされる。
connectionの設定情報はherokuのherokupostgresの管理画面から確認できる。
connectionを元に戻す
で、これだけだとproductionのDBに繋げたまんまで、stagingに戻ってこれない。 (正確には、コピーしたオブジェクトのクラス単位(ActiveRecord::Baseのサブクラス単位)で別DBにつながるみたいなんで、例えばコピーしたクラスがUserクラスだったとすると、このままでもそれ以外の例えばBookクラスがあったとすると、これはstagingにつながったままになっている。)
Herokuでいうと、
heroku run rails c -a staging
して、上のメソッド使ってコピーして、rails cを終了させれば、DBの接続も切られて、新たにrails cしたときには再びデフォルトのstagingの方につながるので、この用途だけで使ってる分には問題ないっちゃないけど、 でもアプリケーションの中のロジックとして使うならちゃんと明示的に戻してやる必要あり。
def self.copy_to_production(object) begin #DBを本番に切り替え object.class.establish_connection :adapter => 'postgresql', :encoding => 'unicode', :database => ENV["PRODUCTION_DB_NAME"], :pool => '5', :username => ENV["PRODUCTION_DB_USERNAME"], :password => ENV["PRODUCTION_DB_PASSWORD"], :host => ENV["PRODUCTION_DB_HOST"], :port => ENV["PRODUCTION_DB_PORT"] #本番にデータを追加 attrs = object.attributes attrs.delete("id") attrs.delete_if{|item| item.include?("_id")} #外部キーは環境で異なるので削除 new_object = object.class.create(attrs) new_object.save ensure object.class.establish_connection(ENV["DATABASE_URL"]) #元に戻す end end
begin 〜 ensureで書いてやればOK。 establish_connectionはDB情報の引数での渡し方いろいろあって、
establish_connection (ActiveRecord::ConnectionHandling) - APIdock
URLでも渡せるし、上でやったようにhashで一個一個渡してもOK。
herokuだとENV["DATABASE_URL"]にデフォルトで必要情報はいってるから、戻すときはこれでOK。
動的にconfigurationになにか追加されている可能性があるため、元々の設定を取得しておいて、あとからそれを戻すほうが正解。
original_config = ActiveRecord::Base.configurations[Rails.env] || Rails.application.config.database_configuration[Rails.env] begin ... ensure object.class.establish_connection(original_config) #元に戻す end
参考:
これによると、begin 〜 ensureの書き方だとtransactionを絡めようと思うとうまくいかないらしく、サブクラス作って
class ReadDatabaseConnection < ActiveRecord::Base def self.abstract_class? true # So it gets its own connection end end
を実装したうえで、このサブクラスに対してestablish_connection呼んでやれば別connectionが貼られるらしい。 transaction絡める場合は注意。
paperclipでS3に画像保存している場合
あとで書こう。