Rails Database Migrations 6 Using Models in Your Migrations 在你的数据迁移中使用模型

version: Rails 4.1

当在一个数据迁移文件(migration)里创建或者修改数据时, 通常比较吸引人的做法是使用你的模型中的一个. 毕竟, 他们的存在是为了提供更便捷的读取那些潜在的数据. 这是能够实现的, 但有些警告应该要被注意到.
 
例如, 问题的产生是在模型使用数据列时,1)这些数据列没有存在于数据库中 2)将会被这个或者随后的 migration 来创建。
 
考虑这个例子,Alice 和 Bob 共同工作于同一个代码,包含一个 Product 模型:
 
Bob 度假去了。
 
Alice 创建了一个 数据迁移文件(migration) 服务于 products 数据表,增加了一个新的列,然后初始化它:

# db/migrate/20100513121110_add_flag_to_product.rb
 
class AddFlagToProduct < ActiveRecord::Migration
   def change
     add_column :products , :flag , :boolean
     reversible do |dir|
       dir.up { Product.update_all flag: false }
     end
   end
end

她同样地也给 Product的一个新的列 增加了一个验证:


# app/models/product.rb
 
class Product < ActiveRecord::Base
   validates :flag , inclusion: { in : [ true , false ] }
end

Alice 增加第二个 migration,用来在 products 数据表中增加另一个列,然后再初始化它:


# db/migrate/20100515121110_add_fuzz_to_product.rb
 
class AddFuzzToProduct < ActiveRecord::Migration
   def change
     add_column :products , :fuzz , :string
     reversible do |dir|
       dir.up { Product.update_all fuzz: 'fuzzy' }
     end
   end
end

她同样地也对于 Product 模型的一个新的列增加了一个验证:


# app/models/product.rb
 
class Product < ActiveRecord::Base
   validates :flag , inclusion: { in : [ true , false ] }
   validates :fuzz , presence: true
end

两个 migrations 都是为 Alice 工作的。

Bob 从假期中回来了:

  • 更新了代码—— 这其中包含了两个 migrations 和最近版本的 Product 模型。
  • 运行未被执行过的 migrations 用 rake db:migrate,包括那个 Product 模型的更新。

migration 有冲突了,因为当模型想要保存的时候,尝试着去验证第二个新增的列,当第一个 migration 运行的时候,这一列是不存在与数据库中的:


rake aborted!
An error has occurred, this and all later migrations canceled:
 
undefined method `fuzz' for #<Product:0x000001049b14a0>

一个解决的办法就是,在一个 migration 中创建一个本地模型。这使得让 Rails 持续地执行验证,因此 migrations 可以完整的运行了。

当使用一个本地模型时,比较好的做法是调用 Product.reset_column_information 来刷新 Active Record 的缓存,为了 Product 模型在数据库中优先更新数据。

如果 Alice 已经这样做了,那就不会存在问题了:

# db/migrate/20100513121110_add_flag_to_product.rb
 
class   AddFlagToProduct < ActiveRecord::Migration
   class   Product < ActiveRecord::Base
   end
 
   def   change
     add_column  :products ,  :flag ,  :boolean
     Product.reset_column_information
     reversible  do   |dir|
       dir.up { Product.update_all flag:  false   }
     end
   end
end
# db/migrate/20100515121110_add_fuzz_to_product.rb
 
class   AddFuzzToProduct < ActiveRecord::Migration
   class   Product < ActiveRecord::Base
   end
 
   def   change
     add_column  :products ,  :fuzz ,  :string
     Product.reset_column_information
     reversible  do   |dir|
       dir.up { Product.update_all fuzz:  'fuzzy'   }
     end
   end
end

在上面的例子中,可能还有其他的错误。

 

例如,想象一下,Alice 创建了一个 migration ,有选择地更新某些 products 的 description 字段。她运行 migration,提交代码,然后开始工作于下一个任务,在 products 数据表中增加一个新的列。

 

对于这个新的任务,她创建了两个 migrations,一方面是增加了新的列,另一方面是有选择地更新 fuzz 列,基于 product 的其他特性。

 

这些 migrations 可以很好地执行,但当 Bob 从他的假期中归来,调用 rake db:migrate 去运行所有未执行过的 migrations,他得到了一个不可思议的 bug :description 字段中有默认值,fuzz 列是显示的,但 所有的products中 fuzz 的值是nil。

 
解决的办法是再一次使用 Product.reset_column_information, 位于一个 migration 里引用 Product 模型之前。在操作那些记录数据之前,确保 Active Record 能了解当前其数据表结构。
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值