Ruby on Rails,创建和执行migrations迁移文件

《Ruby on Rails,rake工具使用和数据库migrations迁移的概念》中,我们知道Rails中进行数据库迁移操作的基本概念和重要性。现在着手进行一个简单的数据库迁移实践吧。

所有的数据库迁移文件存放在simple_cms/db/migrations目录中,在之前我们没有做过创建迁移的操作所以这个目录还没有生成。

有两种方式来创建迁移工作,其一是创建模型的时候自动生成迁移文件,其二是直接创建迁移文件。

创建模型的同时生成迁移文件:

在simple_cms应用的根目录中,执行创建模型的命令。其中User是要创建的模型的名字(注意模型的命名一般来说是单数形式,创建出来的表自动是复数形式),如此做会在创建User模型的同时创建出迁移文件。回显提示创建出了名为“YYYYMMDDHHMMSS_create_users.rb”,以时间开头下划线分割每个单词的格式。

E:\greensoft\RailsInstaller\Sites\simple_cms>rails generate model User invoke active_record create db/migrate/20120613163730_create_users.rb create app/models/user.rb invoke test_unit create test/unit/user_test.rb create test/fixtures/users.yml

我们注意到从Rails3.1版本之后,生成的迁移文件中使用了更智能的change方法来代替传统的up和down方法。推荐只在change方法中加入与数据库结构相关的操作,这样即使不写回滚方法Rails也能在收到回滚命令时做出正确的操作,简直太贴心了!

class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.timestamps end end end

还可以直接创建迁移文件:
在simple_cms应用的根目录中,执行创建迁移的命令。

E:\greensoft\RailsInstaller\Sites\simple_cms>rails generate migration DoNothing invoke active_record create db/migrate/20120613163818_do_nothing.rb

打开simple_cms/db/migrations/20120613163818_do_nothing.rb,里面只有两个空方法up,和down分别对应执行这个版本的迁移和回滚这个版本的迁移。现在这个文件正如我们在名字里暗示的一样,执行迁移和回滚都是空方法什么都不干。

class DoNothing < ActiveRecord::Migration def up end def down end end

这时候我们就有了两个迁移文件,通过执行迁移工作可以让迁移文件中描述的结构及数据作用到数据库中。在执行迁移之前先看一下数据库,除了记录数据库版本的schema_migrations表之外什么都没有。

C:\Windows\system32>mysql -u abbuggy -p simple_cms_development Enter password: ******* ... mysql> SHOW TABLES; +----------------------------------+ | Tables_in_simple_cms_development | +----------------------------------+ | schema_migrations | +----------------------------------+ 1 row in set (0.00 sec) mysql>

执行迁移命令,没有参数的rake db:migrate会将没有执行过的迁移文件按照时间顺序执行一遍。CreateUsers的作用是创建一个叫做users的表,DoNothing什么都不干。 

E:\greensoft\RailsInstaller\Sites\simple_cms>rake db:migrate == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.4120s == CreateUsers: migrated (0.4120s) =========================================== == DoNothing: migrating ====================================================== == DoNothing: migrated (0.0000s) ============================================= E:\greensoft\RailsInstaller\Sites\simple_cms>


看看schema_migrations表,这个表只有一个列version,两个记录分别是刚才执行的两个迁移文件名前面的版本号。这个表就是记录目前为止执行过的迁移文件的版本用的。每当执行迁移命令,这个表都会做出相应的记录,这样我们就知道那些迁移文件已经执行,哪些是未曾执行的。

mysql> select * from schema_migrations; +----------------+ | version | +----------------+ | 20120613163730 | | 20120613163818 | +----------------+ 2 rows in set (0.02 sec) mysql>


再去看看simple_cms/db/schema.rb,与之前对比增加了users表的定义。这个文件的内容与当前数据库的结构保持一致。version的值就是最后一个迁移文件的版本。

ActiveRecord::Schema.define(:version => 20120613163818) do create_table "users", :force => true do |t| t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end end

在迁移命令后面指定版本号可以前往或回滚至指定的版本,如果输入版本号为0,回到数据库的初始状态。

E:\greensoft\RailsInstaller\Sites\simple_cms>rake db:migrate VERSION=0 == DoNothing: reverting ====================================================== == DoNothing: reverted (0.0000s) ============================================= == CreateUsers: reverting ==================================================== -- drop_table("users") -> 0.1410s == CreateUsers: reverted (0.1420s) ===========================================

刚才创建的表被清除。 

mysql> SHOW TABLES; +----------------------------------+ | Tables_in_simple_cms_development | +----------------------------------+ | schema_migrations | +----------------------------------+ 1 row in set (0.00 sec)

迁移版本归零

mysql> select * from schema_migrations; Empty set (0.00 sec)

指定某一个版本rake db:migrate VERSION=20120613163818命令将数据库前往至20120613163818执行后的状态。这里要注意,这里的“前往至”指的是从当前的版本开始执行迁移文件,直到指定的版本。换句话说,如果当前的版本为0,手头有5个迁移文件分别是1,2,3,4,5。rake db:migrate VERSION=3意味着将按顺序分别执行1,2,3版本的迁移。

E:\greensoft\RailsInstaller\Sites\simple_cms>rake db:migrate VERSION=20120613163818 == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.1770s == CreateUsers: migrated (0.1780s) =========================================== == DoNothing: migrating ====================================================== == DoNothing: migrated (0.0000s) =============================================

如果只想执行某一个版本的迁移工作,需要使用rake db:migrate:up VERSION=xxx或rake db:migrate:down VERSION=xxx命令。比如我们可以直接执行rake db:migrate:down VERSION=20120613163730。这个命令是创建users表的逆过程,也就是删除users表。我个人觉得单独执行某一个迁移文件这不是一个明智的行为,因为有可能中间没有执行的某个迁移会影响最终的结果使得表结构或内容不同步。

E:\greensoft\RailsInstaller\Sites\simple_cms>rake db:migrate:down VERSION=20120613163730 == CreateUsers: reverting ==================================================== -- drop_table("users") -> 0.0630s == CreateUsers: reverted (0.0660s) ===========================================

最后说说迁移文件名开头的时间戳,也就是迁移文件的版本。为什么要用一个精确到秒的时间作为迁移文件的版本呢?如果Bob早上10点创建了一个模型同时生成了当时为版本的迁移文件,后来Tom下午五点创建了另一个迁移文件。他们两个人之后分别检出代码并执行数据库迁移db:migrate时,Rails会自动按照顺序执行迁移操作保证数据库状态正确。时间版本能让团队中开发人员创建的迁移文件能够按照顺序执行,减少冲突的可能。 
不得不提的是,迁移文件只能是减少冲突,并不能完全解决冲突,更不能完全代替团队之间的沟通。如果Tom下午的迁移工作所操作的内容已经被Bob在上午的迁移工作中移除了,错误肯定是不可避免了。
关于错误回滚,有时候可能因为书写或是其他原因,迁移文件执行错误。这时候如果直接修改迁移文件至正确状态后重新执行肯定是不行的,因为Rails记录的当前迁移版本和修改的迁移文件版本相同,执行不了。正确的作法是将库回滚至上一版本后再次执行修改过的迁移文件。