Rails开发细节《五》Migrations 数据迁移

1.简介

在rails中用migration可以很方便的管理数据库的结构。可以创建数据库,创建表,删除表,添加字段,删除字段,整理数据。

migration就是一系列的class,这些类都继承了ActiveRecord::Migration类。

 

 
  
  1. class CreateProducts < ActiveRecord::Migration 
  2.   def up 
  3.     create_table :products do |t| 
  4.       t.string :name 
  5.       t.column :description:text 
  6.       t.timestamps 
  7.     end 
  8.   end 
  9.  
  10.   def down 
  11.     drop_table :products 
  12.   end  
  13. end 

上面就是一个migration例子。up方法中的代码会在

rake db:migrate

之后执行。

down方法中的代码会在

rake db:rollback

之后执行。

t.timestamps会自动产生created_at和updated_at列。

还可以进行表结构修改。


 
   
  1. class AddReceiveNewsletterToUsers < ActiveRecord::Migration 
  2.   def up 
  3.     change_table :users do |t| 
  4.       t.boolean :receive_newsletter:default => false 
  5.     end 
  6.     User.update_all ["receive_newsletter = ?"true
  7.   end 
  8.   
  9.   def down 
  10.     remove_column :users:receive_newsletter 
  11.   end 
  12. end 

rails3.1之后产生了一个新的方法change,主要用来创建表和列,不用写一对up和down了,使用rake db:rollback回滚的时候数据库不用down方法也知道如何做了。

1.1.migration提供了很多的方法

  • add_column
  • add_index
  • change_column
  • change_table
  • create_table
  • drop_table
  • remove_column
  • remove_index
  • rename_column

如果想回滚migration对数据库造成的改变,可以使用rake db:rollback命令。

 

1.2.ActiveRecord支持的列类型

  • :binary
  • :boolean
  • :date
  • :datetime
  • :decimal
  • :float
  • :integer
  • :primary_key
  • :string
  • :text
  • :time
  • :timestamp

 

2.创建migration

2.1.创建model

rails generate model Product name:string description:text

创建的migration文件位于db/migrate目录,文件名称为yyyymmddmmss_create_products.rb。

 

 
  
  1. class CreateProducts < ActiveRecord::Migration 
  2.   def change 
  3.     create_table :products do |t| 
  4.       t.string :name 
  5.       t.text :description 
  6.   
  7.       t.timestamps 
  8.     end 
  9.   end 
  10. end 

 

2.2.创建单独的migration

 

 
  
  1. rails generate migration AddPartNumberToProduct 

 

 
   
  1. class AddPartNumberToProducts < ActiveRecord::Migration 
  2.   def change 
  3.   end 
  4. end 
 
指定列的名称
 
 
   
  1. rails generate migration AddPartNumberToProduct part_number:string 
 

 

 
     
  1. class AddPartNumberToProducts < ActiveRecord::Migration 
  2.   def change 
  3.     add_column :products:part_number:string 
  4.   end 
  5. end 
删除列
 
 
  
  1. rails generate migration RemovePartNumberToProduct part_number:string 
 
 
 
   
  1. class RemovePartNumberFromProducts < ActiveRecord::Migration 
  2.   def up 
  3.     remove_column :products:part_number 
  4.   end 
  5.  
  6.   def down 
  7.     add_column :products:part_number:string 
  8.   end 
  9. end 
 
还可以添加多个列
 

 

 
   
  1. rails generate migration AddDetailsToProducts part_number:string price:decimal 
  2.  
  3.  
  4. class AddDetailsToProducts < ActiveRecord::Migration 
  5.   def change 
  6.     add_column :products:part_number:string 
  7.     add_column :products:price:decimal 
  8.   end 
  9. end 

3.编写mirgation

3.1.创建表

 

 
  
  1. create_table :products do |t| 
  2.  
  3.   t.string :name 
  4.  
  5. end 
  6.  
  7. create_table :products do |t| 
  8.  
  9.   t.column :name:string:null => false 
  10.  
  11. end 

 

如果数据库是mysql,还可以通过下面的语句指定使用的引擎,mysql默认的引擎是InnoDB。

 
 
  
  1. create_table :products:options => "ENGINE=MyISAM" do |t| 
  2.   t.string :name:null => false 
  3. end 

3.2.修改表结构

 

 
   
  1. change_table :products do |t| 
  2.   t.remove :description:name 
  3.   t.string :part_number 
  4.   t.index :part_number 
  5.   t.rename :upccode:upc_code 
  6. end 
 

删除name,description字段,添加part_number字段,在part_number字段建立索引,重命名upccode为upc_code。

3.3.辅助工具

t.timestamps可以自动添加created_atupdated_at列。

 

 

 
    
  1. #创建表的同时添加 
  2. create_table :products do |t| 
  3.   t.timestamps 
  4. end 
  5.  
  6. #给已经存在的表添加 
  7. change_table :products do |t| 
  8.   t.timestamps 
  9. end 

还有一个帮助工具references,用来指明表的外键关系。

 

 
   
  1. create_table :products do |t| 
  2.   t.references :category 
  3. end 

上面的代码会在products表中添加一个外键字段category_id。

 

 
  
  1. create_table :products do |t| 
  2.   t.references :p_w_upload:polymorphic => {:default => 'Photo'
  3. end 

上面的代码不仅会在products表中添加外键字段p_w_upload_id,还会添加p_w_upload_type字段,string类型,默认值是Photo。

3.4.change方法的使用

change方法可以部分的替代up和down方法,数据库会自动的回滚。但是目前在change方法中只支持下面的migration。

  • add_column
  • add_index
  • add_timestamps
  • create_table
  • remove_timestamps
  • rename_column
  • rename_index
  • rename_table

如果要用其他的migration,你就需要自己写up和down了,不能再使用change了。

3.5.up和down方法的使用

down方法用来回滚数据库,你需要注意在down方法中的顺序,相对于up方法中的顺序。

 

 
  
  1. class ExampleMigration < ActiveRecord::Migration 
  2.   def up 
  3.     create_table :products do |t| 
  4.       t.references :category 
  5.     end 
  6.     #add a foreign key 
  7.     execute <<-SQL 
  8.       ALTER TABLE products 
  9.         ADD CONSTRAINT fk_products_categories 
  10.         FOREIGN KEY (category_id) 
  11.         REFERENCES categories(id) 
  12.     SQL 
  13.     add_column :users:home_page_url:string 
  14.     rename_column :users:email:email_address 
  15.   end 
  16.   
  17.   def down 
  18.     rename_column :users:email_address:email 
  19.     remove_column :users:home_page_url 
  20.     execute <<-SQL 
  21.       ALTER TABLE products 
  22.         DROP FOREIGN KEY fk_products_categories 
  23.     SQL 
  24.     drop_table :products 
  25.   end 
  26. end 

4.运行迁移

通过

 

 
  
  1. rake db:migrate 

命令就可以执行迁移任务。

如果指定了版本,就会执行指定版本的up,change或者down方法。版本就是migration文件的前缀。用VERSION参数来指定版本号。

 
  
  1. rake db:migrate VERSION=20080906120000 

拿上面的这个命令举例。如果20080906120000 比当前的版本大,会执行所有migration的up方法,包括20080906120000 中的up方法,不执行其他的migration方法。如果是小于,会执行所有migration的down方法,不包括20080906120000中的down方法。

4.1.回滚数据库

回滚上一次的数据库变更

rake db:rollback

回滚之前三次的数据库变更

rake db:rollback STEP=3

重做之前三次的数据库变更,重做之前会先回滚。

rake db:migrate:redo STEP=3

4.2.重置数据库

删除当前数据库,重新创建,重新执行migration。

 

 
  
  1. rake db:reset 

4.3.执行指定的migration

 
  
  1. rake db:migrate:up VERSION=20080906120000 

4.4.改变执行migration之后的提示信息

ethodPurpose
suppress_messagesTakes a block as an argument and suppresses any output generated by the block.
sayTakes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent or not.
say_with_timeOutputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected.

 

 
  
  1. class CreateProducts < ActiveRecord::Migration 
  2.   def change 
  3.     suppress_messages do 
  4.       create_table :products do |t| 
  5.         t.string :name 
  6.         t.text :description 
  7.         t.timestamps 
  8.       end 
  9.     end 
  10.     say "Created a table" 
  11.     suppress_messages {add_index :products:name
  12.     say "and an index!"true 
  13.     say_with_time 'Waiting for a while' do 
  14.       sleep 10 
  15.       250 
  16.     end 
  17.   end 
  18. end 

 

5.在migration中使用model对象

假设有两个开发者,公用一个代码库。

其中一个休假了,另外一个还在工作,对数据库进行了修改。

 

 
  
  1. # db/migrate/20100513121110_add_flag_to_product.rb 
  2.   
  3. class AddFlagToProduct < ActiveRecord::Migration 
  4.   def change 
  5.     add_column :products:flag:boolean 
  6.     Product.all.each do |product| 
  7.       product.update_attributes!(:flag => 'false'
  8.     end 
  9.   end 
  10. end 
  11.  
  12. # app/model/product.rb 
  13.   
  14. class Product < ActiveRecord::Base 
  15.   validates :flag:presence => true 
  16. end 
  17.  
  18. # db/migrate/20100515121110_add_fuzz_to_product.rb 
  19.   
  20. class AddFuzzToProduct < ActiveRecord::Migration 
  21.   def change 
  22.     add_column :products:fuzz:string 
  23.     Product.all.each do |product| 
  24.       product.update_attributes! :fuzz => 'fuzzy' 
  25.     end 
  26.   end 
  27. end 
  28.  
  29. # app/model/product.rb 
  30.   
  31. class Product < ActiveRecord::Base 
  32.   validates :flag:fuzz:presence => true 
  33. end 

添加了一些字段,添加了一些验证。

休假的同事回来了。

获取代码。

执行 rake db:migrate。

报错了。

 

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

因为在执行完第一个migration之后,验证model的时候,还没有执行第二个migration,也就还没有fuzz字段。

解决的办法就是在migration中添加空的model定义,阻止migration运行的时候验证model,因为空的model中没有验证规则,这样就migration就可以运行完毕。

由于在migration中包括更新字段的操作,还需要添加Product.reset_column_information来更新ActiveRecord中缓存的之前的空Product就可以了。

 

 
  
  1. # db/migrate/20100513121110_add_flag_to_product.rb 
  2.   
  3. class AddFlagToProduct < ActiveRecord::Migration 
  4.   class Product < ActiveRecord::Base 
  5.   end 
  6.   
  7.   def change 
  8.     add_column :products:flag:integer 
  9.     Product.reset_column_information 
  10.     Product.all.each do |product| 
  11.       product.update_attributes!(:flag => false
  12.     end 
  13.   end 
  14. end 
  15.  
  16.  
  17. # db/migrate/20100515121110_add_fuzz_to_product.rb 
  18.   
  19. class AddFuzzToProduct < ActiveRecord::Migration 
  20.   class Product < ActiveRecord::Base 
  21.   end 
  22.   
  23.   def change 
  24.     add_column :products:fuzz:string 
  25.     Product.reset_column_information 
  26.     Product.all.each do |product| 
  27.       product.update_attributes!(:fuzz => 'fuzzy'
  28.     end 
  29.   end 
  30. end 

 

6.数据库的描述文件

db/schema.rb或者是db/*.sql文件中就是数据库的结构,这个文件不是用来修改的,自动生成,用来查看数据库的当前状态。

6.1.描述文件的类型

config/application.rb文件中可以配置描述文件的类型,config.active_record.schema_format,值为 :sql:ruby,默认是:ruby。如果是:ruby,文件的内容就是。

 

 
  
  1. ActiveRecord::Schema.define(:version => 20080906171750) do 
  2.   create_table "authors":force => true do |t| 
  3.     t.string   "name" 
  4.     t.datetime "created_at" 
  5.     t.datetime "updated_at" 
  6.   end 
  7.   
  8.   create_table "products":force => true do |t| 
  9.     t.string   "name" 
  10.     t.text "description" 
  11.     t.datetime "created_at" 
  12.     t.datetime "updated_at" 
  13.     t.string "part_number" 
  14.   end 
  15. end 

 

参考文献

1.Migrations