Not All Migrations are Equal: Schema vs. Data

之前发过一个帖子「Rails should have data migration」,当时也没太弄清楚App和Schema Migration之间,以及Schema Migration和 Data Migration之间的关系。直到读完这篇「Not All Migrations are Equal: Schema vs. Data」才茅塞顿开。

本质上App依赖Schema Migration,但Schema Migration不应该去依赖App,Schema Migration是可以脱离App独立存在的。
而Data Migration呢,是需要依赖App的业务逻辑的。所以呢,Schema和Data Migration是不能放在一起的。

理解了这两个前提,看一下作者举的例子:

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

当然SQL也不是那么好写,作者又讨(批)论(评)了一下流行的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的架构,其实本质上是基于one off rake task的扩充,包括:

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是否执行过的机制:

> rake db:datafix
  migrating AddValidWholesalePriceToProducts up

> rake db:datafix:status

  database: somedatabase_development

   Status   Datafix ID            Datafix Name
  --------------------------------------------------
     up    20141211143848       AddValidWholesalePriceToProducts

可以为Data Migration写测试:

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机制:

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.

你可能感兴趣的:(Not All Migrations are Equal: Schema vs. Data)