在我的博客网站开发过程中,我在开发环境使用sqlite3,而在生产环境则使用性能更好的mysql。一直以来我也觉得没什么,因为rails会屏蔽所有的数据库差异。但这样也带来一些问题,其中一个就是schema.rb的版本控制问题。Rails建议对此文件进行版本控制,这样可以更清楚的看到数据库版本的变动。但由于开发和生产环境使用的数据库不一致,导致每次rake db:migrate后schema.rb都会变化,开发环境和生产环境的这个文件版本不一致(由于数据库的不同),将测试环境的schema.rb传到版本控制显然不好,但是不传的话总是提示有文件未提交感觉很不好。
经过权衡决定在测试环境也使用mysql,并且将测试数据也导入到新的mysql数据库。数据库不一致的另一个问题是我在迁移时发现的,不同的数据库还是有差异的,而且还影响到测试案例的成功与否,其中一个问题就是这次要重点介绍的datetime类型的精度问题。
datetime类型是rails在generate model
时自动生成的,如果执行
$ bin/rails generate model Product name:string description:text
会生成一个迁移文件如下:
#file name: db/migrate/20160501090706_create_products.rb
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps null: false
end
end
end
其中 t.timestamps null: false
会生成2个字段created_at
,updated_at
,其schema.rb
中的
#file name : schema.rb
#...
t.datetime "created_at"
t.datetime "updated_at"
#...
这种datetime类型的字段是作为时间戳使用的,在sqlite3中为6位精度,如2016-05-01 11:28:38.860909,这没有问题。但是换为使用mysql就有问题了,datetime 类型在mysql中默认精度只到秒,这显然不能满足要求,秒级的时间戳是没有意义的。在网上找了很多资料,有很多比较陈旧,下面把我测试通过的解决方法记录一下,方便大家参考 。
在Rails 4.2的Release Notes有下面一句话:
Added support for fractional seconds for MySQL 5.6 and above
可以看出Rails 4.2已经支持日期精度功能,关于mysql的fractional seconds功能可以参考 mysql reference.
我们在Rails 4.2+应用中应该如何使用此功能呢?可以通过添加migrate文件来解决,具体如下:
bin/rails g migration ChangeDatetimeLimitForMysql
在生成的文件中修改如下:(下面只以修改一个表为例,多个表修改方法类似)
# file name: db/migrate/20160501090706_change_datetime_limit_for_mysql.rb
# mysql 5.6.4以上的版本支持分数精度(fractional seconds),默认mysql精度只到秒
# limit 置为 6 精确到微妙;如将limit 修改为 3 则精确到毫秒
class ChangeDatetimeLimitForMysql < ActiveRecord::Migration
def up
change_column :comments, :created_at, :datetime, limit: 6
change_column :comments, :updated_at, :datetime, limit: 6
#...
end
def down
change_column :comments, :created_at, :datetime
change_column :comments, :updated_at, :datetime
#...
end
end
然后执行rake db:migrate
即可,这样之后新增修改的记录的时间戳都会精确到微秒。注意测试环境也需要执行rake db:test:prepare
使其生效。
[注]:此方案适用于Rails 4.2+,mysql 5.6.4+.
参考资料: