之前發(fā)過一個(gè)帖子「Rails should have data migration」,當(dāng)時(shí)也沒太弄清楚App和Schema Migration之間,以及Schema Migration和 Data Migration之間的關(guān)系丐怯。直到讀完這篇「Not All Migrations are Equal: Schema vs. Data」才茅塞頓開惩歉。
本質(zhì)上App依賴Schema Migration胧华,但Schema Migration不應(yīng)該去依賴App角雷,Schema Migration是可以脫離App獨(dú)立存在的古徒。
而Data Migration呢摆马,是需要依賴App的業(yè)務(wù)邏輯的臼闻。所以呢,Schema和Data Migration是不能放在一起的囤采。
理解了這兩個(gè)前提述呐,看一下作者舉的例子:
Bad Schema Migration
class ChangeAdminDefaultToFalseOnUsers < ActiveRecord::Migration
def up
change_column_default(:users, :admin, false)
User.reset_column_information # 注:這里Migration依賴App里的User model和reset_column_information方法了。
# Bad: Use of application code that changes over time.
User.update_null_to_false!
end
end
Good Schema Migration
class ChangeAdminDefaultToFalseOnUsers < ActiveRecord::Migration
# Create empty AR model that will attach to the users table,
# and isolate migration from application code.
class User < ActiveRecord::Base; end
def up
change_column_default(:users, :admin, false)
User.where(admin:nil).update_all(admin: false)
end
end
Better Schema Migration
class ChangeAdminDefaultToFalseOnUsers < ActiveRecord::Migration
def up
change_column_default(:users, :admin, false)
execute "UPDATE users SET admin = false WHERE admin IS NULL"
end
end
當(dāng)然SQL也不是那么好寫蕉毯,作者又討(批)論(評(píng))了一下流行的one off rake task
方案:
Create a one off rake task? No.
Perhaps, but the code will be difficult to test and won’t have mechanisms in place to roll back to changes. Even if you refactor the logic out of the rake task into a separate ruby >class, you will now have to maintain code that is ephemeral in nature. It merely exists for this one off data migration.
最后作者提出Datafixes的架構(gòu)乓搬,其實(shí)本質(zhì)上是基于one off rake task
的擴(kuò)充,包括:
Generator:
> rails g datafix AddValidWholesalePriceToProducts
create db/datafixes/20141211143848_add_valid_wholesale_price_to_products.rb
create spec/db/datafixes/20141211143848_add_valid_wholesale_price_to_products_spec.rb
像schema_migration表一樣記錄data migration是否執(zhí)行過的機(jī)制:
> rake db:datafix
migrating AddValidWholesalePriceToProducts up
> rake db:datafix:status
database: somedatabase_development
Status Datafix ID Datafix Name
--------------------------------------------------
up 20141211143848 AddValidWholesalePriceToProducts
可以為Data Migration寫測(cè)試:
require "rails_helper"
require Rails.root.join("db", "datafixes", "20141211143848_add_valid_wholesale_price_to_products")
describe Datafixes::AddValidWholesalePriceToProducts do
describe ".up" do
# Fill out the describe block
let!(:product) do
product = FactoryGirl.build(:product, wholesale_price_cents: nil)
product.save(validate: false)
product
end
it "should fix the price and be valid" do
expect(product).to_not be_valid
subject.migrate('up')
expect(product.reload).to be_valid
end
end
end
Rollback機(jī)制:
rake db:datafix:rollback
另外又提到了和Datafix類似的nondestructive_migrations gem:
Update 01/26/2015: Check out the nondestructive_migrations gem. It’s similar to dimroc/datafix but simpler because it leverages existing AR code. It does not however generate specs… yet.