Rails Migrations Ⅱ

16.3 Managing Tables ( 管理表 )

到现在我们已经使用migration迁移来管理现有表内的列。现在看看创建和删除表。
class CreateOrderHistories < ActiveRecord::Migration
def self.up
create_table :order_histories do |t|
t.column :order_id, :integer, :null => false
t.column :created_at, :timestamp
t.column :notes, :text
end
end
def self.down
drop_table :order_histories
end
end
create_table接受一个表的名字(记住,表的名字是复数)和一个块。(稍后会看到它也接受一些可选的参数)。块传递一个表定义对象,通过调用该对象的column方法,我们来定义表内的列。
column的调用看起来很熟悉 --- 它们同我们前面使用的add_column方法是一样的,除了它们不是使用表的名字做第一个参数之外。
注意我们没有为新表定义id列。除非我们说不要,否则Railsmigration迁移自动添加一个名为id的主键给它创建的所有表。对这个的深入讨论,可参阅261页的16.3一节。
一、Options For Creating Tables (用于创建表选项)
你可以传递个选项哈希表给create_table,做为它的第二个参数。如果你指定force=>true,则migration迁移将在创建新表前删除现有的同名表。如果你想创建个migration迁移,它强制数据库进入已知状态,这个选项是很用的,但很明显它会丢失数据。
:temporary=>true选项创建一个临时表 --- 当应用程序从数据库断开时,就可以使用它。很明显在migration迁移上下文环境内是无用的,但稍后会看到,在其它地方它还是有用的。
:options=>”xxxx”参数可让你指定用于基础数据库的选项。这些会被添加到CREATE TABLE语句的尾部,即在右圆括号之后。例如,一些MySQL版本允许你指定id列自动增加的初始化值。我们可以像下面这样在migration迁移中传递它:
create_table :tickets, :options => "auto_increment = 10000" do |t|
t.column :created_at, :timestamp
t.column :description, :text
end
在背后,migration迁移将从这个表描述中生成下面DDL
create table tickets (
‘id‘ int(11) default null auto_increment primary key,
‘created_at‘ datetime,
‘description‘ text
) auto_increment = 10000;
当为MySQL使用:options参数时要小心。RailsMySQL适配器设置了ENGINE=InnoDB缺省选项。这会覆写你设置本地缺省值并强迫migration迁移为新表使用InnoDB存储引擎。但是,如果你覆写 :options ,你将会丢失这个设置;新表将会使用你的站点缺省配置的数据库引擎。你可以明确地添加ENGINE=InnoDB给选项字符串来强迫这个标准的行为。
二、Renaming Tables (表的重命名)
如果重构需要我们重命名变量和列,那么它也可能需要我们重命名表名字。
class RenameOrderHistories < ActiveRecord::Migration
def self.up
rename_table :order_histories, :order_notes
end
def self.down
rename_table :order_notes, :order_histories
end
end
注意,down方法是如何通过反向重命名表名来回溯修改的。
三、Problems with rename_table (rename_table的问题)
当你在migration迁移内重命名表名字时有个微妙的问题。
例如,假设在migration迁移4内,你创建了order_histories表,并输入了一些数据。
def self.up
create_table :order_histories do |t|
t.column :order_id, :integer, :null => false
t.column :created_at, :timestamp
t.column :notes, :text
end
order = Order.find :first
OrderHistory.create(:order = order, :notes => "test" )
end
假设你在migration迁移7内重命名表order_historiesorder_notes。此时你也会重命名模型OrderHistoryOrderNote
现在你决定清空你的development数据库并重新应用所有的migration迁移。当你这么做时,migration迁移4会抛出异常说:你的应用程序不再包含一个名为OrderHistory的类,因此migration迁移失败。
Tim Lucas提出的一种解决办法是创建migration迁移本身需要的,本地的,模型类的哑版本。例如,下面版本4migration迁移就会工作,即使应用程序不再有OrderHistory类。
class CreateOrderHistories < ActiveRecord::Migration
class Order < ActiveRecord::Base; end
class OrderHistory < ActiveRecord::Base; end
def self.up
create_table :order_histories do |t|
t.column :order_id, :integer, :null => false
t.column :created_at, :timestamp
t.column :notes, :text
end
order = Order.find :first
OrderHistory.create(:order = order, :notes => "test" )
end
def self.down
drop_table :order_histories
end
end
这只在你使用在migration迁移内的模型类没有包含任何新增功能情况下才会工作。你在此处所创建的只是个架子版本而已。
四、Defining Indices (定义索引)
Migration迁移可以(或许应该)为表定义索引。例如,你可能注意到一旦你的应用程序在数据库内有大量的定单时,基于客户名字的搜索会比你想像的时间要长。解决办法是添加一个索引。
class AddCustomerNameIndexToOrders < ActiveRecord::Migration
def self.up
add_index :orders, :name
end
def self.down
remove_index :orders, :name
end
end
如果你给add_index以可选的参数 :unique=>true ,则会创建一个唯一性的索引,强迫被索引列的值必须是唯一的。缺省地索引被命名为table_column_index。你可以使用 :name=>”somename”选项来覆写这个名字。如果在添加一个索引时,你使用了:name选项的话,你在移除索引时,也需要指定它。
你也可以创建一个复合索引 --- 多个列上的索引 --- 通过传递列名字数组给create_index。在这种情况下只有第一个列名字在命名索引用时被使用。
五、Primary Keys (主键)
Rails假定每个表都有个数字的主键(通常称为id)Rails的责任是为添加到表的每个新行,自动地增加该列的值。
--------------------------------------------------------
为什么要使用这些人工的主键?
大多数数据库schema都很不喜欢自然主键 --- 现实生活中主键是有意义的。为什么?因为现实世界每时都在改变。这些带给我们烦恼。使用票号最初看来可能是个好主意。毕竟,票号是唯一的。但如果下一年有了改变呢,我们发现我们需要包括两个字符来加强票号?突然间我们的主键必须更改。当主键被更改时,我们必须修改与这个票号关联的,数据库内的每个表。
如果你使用了一个人工的主键像id,你会减少你的schema被现实世界修改的可能。当票号格式被修改时,你只需要更新票号表内的数据。
-------------------------------------------------------------------
除非每个表有个自动增加的数字主键,否则Rails也不会很好地工作。它减少了列名字的繁琐。因此,对于你的Rails应用程序,我强烈建议让Rails有个id列。
如果你喜欢冒险,你可以为主键列使用不同名字(但要保证它是一个自动增加的整数)。可通过给create_table调用指定个:primary_key选项来完成。
create_table :tickets, :primary_key => :ticket_number do |t|
t.column :created_at, :timestamp
t.column :description, :text
end
这添加ticket_number列给表,并设置它为主键。
mysql> describe tickets;
+---------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+----------+------+-----+---------+----------------+
| ticket_number | int(11) | NO | PRI | NULL | auto_increment |
| created_at | datetime | YES | | NULL | |
| description | text | YES | | NULL | |
+---------------+----------+------+-----+---------+----------------+
3 rows in set (0.34 sec)
冒险的下一步可能就是创建个不是整数的主键。这儿有个线索,但Rails开发者并不认为这是个好主意:migration迁移不会让你这样做。
六、Tables With No Primary Key (没有主键的表)
有时候你可能需要定义一个没有主键的表。Rails中的大多数情况是用于join --- 该表只有两个表,彼此是对方的外键。要使用migration迁移创建个join表,你必须告诉Rails不要自动添加id列。
create_table :books_authors(:id => false) do |t|
t.column :book_id, :null => false
t.column :author_id, :null => false
end
在这个例子中,你可能想在这个表上创建一个或多个索引,以加速在booksauthors两者之间导航。注意这些索引不能是唯一属性。
16.4 Data Migrations (数据迁移)

 
Migration迁移就是Ruby代码,因此它们可以完成你想得到的任何事,因为它们也是Rails代码,它们完全可以访问你在应用程序内已写的代码。实际上,migration迁移可以访问模型类。这就让它可容易地创建能管理development数据库内数据的migration迁移。
让我们看看两个不同的情况,它们对管理migration迁移内的数据是很有用的:加载development数据和在应用程序版本之间迁移数据。
一、Loading Data With Migrations (用迁移加载数据)
大多数应用程序在我们可以使用它们之前,要求将一些背景信息加入到数据库中,甚至在开发阶段也是如此。如果我们在写在线商店,我们会需要product数据。我们也可能需要消费利率,用户简介等等信息。在以前,开发者把这些数据放到它们的数据库中,通常是用手工输入SQLinsert语句。这很难管理,并且不能重复利用。也很难在工程开发中途加入这些。
Migration迁移可让这变得容易。事实上在我的所有Rails工程中,发现我自己创建纯数据migration迁移 --- 即加载数据到一个现的schema内而不修改schema本身的migration迁移。
这儿有个典型的纯数据migration迁移,它是从新版Pragmatic Bookshelf store应用程序中抽取的。
class TestDiscounts < ActiveRecord::Migration
def self.up
down
rails_book_sku = Sku.find_by_sku("RAILS-B-00" )
ruby_book_sku = Sku.find_by_sku("RUBY-B-00" )
auto_book_sku = Sku.find_by_sku("AUTO-B-00" )
discount = Discount.create(:name => "Rails + Ruby Paper" ,
:action => "DEDUCT_AMOUNT" ,
:amount => "15.00" )
discount.skus = [rails_book_sku, ruby_book_sku]
discount.save!
discount = Discount.create(:name => "Automation Sale" ,
:action => "DEDUCT_PERCENT" ,
:amount => "5.00" )
discount.skus = [auto_book_sku]
discount.save!
end
def self.down
Discount.delete_all
end
end
注意,这个migration迁移是如何使用现有活动记录类的强大功能来寻找现有的skus,创建新的discount并将两者组织起来的。同样注意在up()方法的开始处 --- 它初始化调用down()方法,down()方法依次从discounts数据库删除所有行。这是纯数据migration迁移的通用模式。
二、Loading Data From Fixtures (Fixture中加载数据)
Fixture是包含运行测试时使用数据的文件。但是,稍微加工一下,我们也可使用它们在migration迁移期间来加载数据。
为了阐明过程,假设我们的数据库有个新的users表。我们会用下面的migration迁移来定义它:
class AddUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :name, :string
t.column :status, :string
end
end
def self.down
drop_table = :users
end
end
我们在db/migratte目录创建个子目录,它用于保存我们要加载到development数据库的数据。我们称子目录为dev_data
depot> mkdir db/migrate/dev_data
在此目录内,我们将创建一个YAML文件来包含我们想加入到users表内的数据。我们称此文件为users.yml
dave:
name: Dave Thomas
status: admin
mike:
name: Mike Clark
status: admin
fred:
name: Fred Smith
status: audit
现在,我们生成一个migration迁移,让它将这个Fixture数据加入到development数据库。
depot> ruby script/generate migration load_users_data
exists db/migrate
create db/migrate/0xx_load_users_data.rb
最后,我们在migration迁移文件内写从Fixture加载数据的代码。This is slightly magical, as it relies on a backdoor interface into the Rails fixture code.
require 'active_record/fixtures'
class LoadUserData < ActiveRecord::Migration
def self.up
down
f = Fixtures.new(User.connection, # a database connection
"users" , # table name
User, # model class
File.join(File.dirname(__FILE__), "dev_data/users" ))
f.insert_fixtures
end
def self.down
User.delete_all
end
end
传递给Fixtures.new的最后一个参数是包含Fixture数据的文件路径,但没有 .yml 扩展名。
三、Migrating Data With Migrations --- migration迁移来迁移数据
有时候一个schema修改也包括迁移数据。例如,你可能有个使用float来存储priceschema。但是,如果你稍后出现冲突,你可能想修改price为一个存储分币的integer
如果你已经使用migration迁移来加载数据到数据库内,那么这儿有个问题: just change the migration file so that rather than loading 12.34 into the price column, you instead load 1234. But if that’s not possible, you might instead want to perform the conversion inside the migration. One way is to multiply the existing column values by 100 before changing the column type.
class ChangePriceToInteger < ActiveRecord::Migration
def self.up
Product.update_all("price = price * 100" )
change_column :products, :price, :integer
end
def self.down
change_column :products, :price, :float
Product.update_all("price = price / 100.0" )
end
end
Note how the down migration undoes the change by doing the division only after the column is changed back.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值