您的位置:首页 > 数据库

Rails 数据库迁移(Migrations)

2012-06-24 12:47 351 查看


Rails 数据库迁移(Migrations)

数据库迁移(Migrations)提供了一些便利的方法让你有条理地修改数据库。虽然说直接 编写SQL也能修改数据库,但是这样你不但必须通知其他的开发者去执行一样的步骤,而且你 也得一直注意下次部署的时候和在正式上线的产品版服务器上面追踪并执行这些操作。

Active Record 会自动追踪哪些 Migrations 已经执行过、哪些还没执行。所以,你只要更新 你本地的代码然后执行 rake
db:migrate ,其他的就交给 Active Record ,它会自己搞懂该 跑哪些 Migrations 。还有,它也会自动更新db/schema.rb 文件,让它与修改后的数据库结构同步。

有了 Migrations ,你就可以用Ruby来写这些数据库变更。一件很棒的事情是 Migration 是独立 于数据库系统的(和大多数 Active Record 的功能一样),也就是说,你不用烦恼各种数据 库的语法差异,像是 SELECT
* 的各种写法之类的。(当然,如果要针对某个特定的数据库 系统编写特定的功能的话,你也可以直接编写原始的SQL语句)。例如,你可以在开发阶段 使用SQLite3,在正式上线阶段则使用MySQL,它会自动处理好两者间的语法细节。

在这个指南中,你将会了解到:

用于创建migration的生成器

Active Record 所提供用于操纵数据库的方法

用于操纵migrations的Rake任务

Migrations跟数据库纲要文件(schema.rb)的关联




目录

剖析迁移任务的构造

Migrations也是类

关于文件名

迁移任务变更

支持的数据类型

创建一个迁移任务

建立一个 Model

建立一个独立的迁移任务

编写一个迁移任务

建立数据表

创建连接表

变更数据表

特殊方法

使用change方法

使用up/down方法

执行Migrations

回滚(Rolling Back)

重置数据库

执行指定的migration

修改执行migration时的提示结果

在Migrations中使用Models

导出数据库纲要(Schema Dumping)

Schema文件的作用?

数据库纲要(Schema)的导出类型

导出数据库纲要(Schema)以及版本控制

Active Record 与 Referential Integrity(参照完整性)


1 剖析迁移任务的构造

在我们深入介绍migration的细节之前,我们先看下列例子,了解我们能怎么写 迁移任务:

这个迁移任务建立了一张叫 products 的数据库表,这张表中包含一个名为 name 的
string 类型字段和一个名为 description 的
text 类型字段。与此同时,一个名为 id 的字段也会被添加,这个字段是默认添加,我们不需要另外请求。

另外 Active Record 所需要的时间戳( timestamp )字段( created_at 和 updated_at )也会被自动添加。而要取消这个任务只需简单地把这张表删除掉即可。

数据迁移不仅可以胜任修改数据库架构,你还可以用它来修复数据库中的错误数据或者添加新字段。

在迁移任务中使用 Models 的一些"警告":#using-models-in-your-migrations

这个迁移为 users 表添加了一个 receive_newsletter 字段,并设定新的
user 创建 时它的默认值为 false ,对于数据库中已存在的
users 我们使用 User 模型来把他们的这个 标志位设为 true。

Rails 3.1 为数据迁移提供了 change 方法使它变得更精简。这个方法主要用于一
些构造性的migrations(例如添加新字段或者新的表),它能知道怎么样迁移你的数据库 并在你需要回滚的时候恢复回去而并不需要写一个分开的down方法。


1.1 Migrations也是类

Migration 是继承 ActiveRecord::Migration 的一个子类,它实现了两个方法: up (执行需要的改变)和 down (恢复所做的改变)

Active Record 提供以下独立于数据库的方法,用来执行普通数据定义任务的方法:

add_column

add_index

change_column

change_table

create_table

create_join_table

drop_table

remove_column

remove_index

rename_column

如果你需要为你的数据库完成一些特殊的任务(例如强制创建一个 外键 ), 这时候 execute 方法允许你执行任意的 SQL 语句。一个migration同时也是一个规则的
Ruby 类, 因而你不限于使用以上的方法。例如在添加完新字段后你可以编写代码去为数据库中已经 存在的记录设定这个字段的值(有需要的话利用你的 Models )。

某些数据库支持变更数据库纲要( schema )的语句的事务( transactions ),例如 PostgreSQL 或 SQLite3 ,它们的 migrations 就会包含在事务中处理。相反的,若你的数据库系统(如 MySQL ) 不支持的话,那么当 migration 失败了,发到一半的 schema ,并不会随着事务的取消而自动 回滚(roll back)。你必须手动挑出这些已经改动的部分。


1.2 关于文件名

Migrations 是以文件形式存储在 db/migrate 目录中,每一个迁移任务都作为一个文件。
文件名的格式为 YYYYMMDDHHMMSS_create_products.rb ,文件名前面是一个UTC时间截,
接下来以下划线连接 migration 的名称。migration 的类名是遵循驼峰式命名格式,它跟 文件名的后半部分是一样的。例如 20080906120000_create_products.rb 这一个
migration 应该定义了一个名为CreateProducts 的类,而 20080906120001_add_details_to_products.rb 则定义了一个AddDetailsToProducts的类。如果你需要修改文件名,你就 必须 修改文件中的类名,不然会报
missing class 的错误。

Rails 内部在辨别 migration 的时候只会用到编号(前面的时间截)的部分。在 Rails2.1 之前 的版本, migration 的编号是从1开始递增的,这样做法,在多人开发时很容易造成编号冲突, 一旦冲突了就需要将 migration 回滚(rollback)并重新编号。如果你坚持想要用回原来的这种 编号结构,可以在config/application.rb 中加入下面这一行:

通过时间截和记录已执行过哪些migrations的特性,让Rails能够应付多人开发的状况。

举例来说:爱丽丝新增了 20080906120000 和 20080906123000 这两个
migration ,而鲍勃 接下来新增了 20080906124500 并执行了它。当艾丽丝完成并提交后,鲍勃同步了最新的
代码。这时鲍勃执行 rake db:migrate,虽然鲍勃新增的
migraion 的时间截比较新,但 Rails会知道艾丽丝的这两个migration还没执行,它会自动执行对应的方法。

Of course this is no substitution for communication within the team. For example, if Alice’s migration removed a table that Bob’s migration assumed to exist, then trouble would certainly strike. 当然,团队内的沟通是必不可少的。例如爱丽丝的 migration 移除了鲍勃的 migration 会用到 的表,那肯定会出问题的。


1.3 迁移任务变更

有时你在写迁移任务的时候可能会不小心写错,如果你已经执行了这个迁移任务,那么, 你就不能单纯地把它修改一下再重新执行一次, Rails 会认为这个迁移任务已经执行过了, 所以执行 rake
db:migrate 时不会做任何操作。你应该先把写错的那个迁移任务回滚(可以执行 rake
db:rollback),然后修改你的migration再执行 rake
db:migrate 去 执行正确的版本。

一般来说,最好不要修改现有的迁移任务,因为这样做可能会给你跟你的同事带来很多 麻烦,特别是这个迁移任务已经在正式上线的服务器上执行过的话。你应该写一个新的 迁移任务来做数据库变更。如果这个迁移任务还没有进入版本控制(也就是说这些变更还 没有发布出去),那么直接修改还是可以的。


1.4 支持的数据类型

Active Record 支持的数据类型包括:

:binary

:boolean

:date

:datetime

:decimal

:float

:integer

:primary_key

:string

:text

:time

:timestamp

这些类型会自动对应到数据库,例如在MySQL下,:string 类型会对应到 VARCHAR(255)。
如果你要建立Active Recore不支持的数据类型,那可以用「non-sexy」语法,比方说:

不过,这样就失去了不同数据库系统之间的通用性。


2 创建一个迁移任务


2.1 建立一个 Model

Model和 scaffold的generators(生成器)在新建model的时候会自动生成对应的migration。 这个migraion里已经把建立数据表的步骤都写好了。如果生成的时候告诉Rails需要哪些字 段,Rails会把新增字段的代码都一起生成好。例如执行以下代码

将会生成的迁移任务如下:

如果有需要的话你可以追加更多的"名称/类型"字段。默认会生成的迁移任务都会包含t.timestamps(这个会生成 updated_at 和 created_at 字段,而
Active Record 会在 新增数据和更新时自动更新时间)。


2.2 建立一个独立的迁移任务

如果你建立迁移任务不是为了新增 Model,而是为了其他目的(例如为现在的数据库表添加 字段),那可以只用迁移任务的生成器:

这样会建立一个空白的但已经命名好的迁移任务:

如果迁移任务的文件名命名为 "AddXXXToYYY" 或 "RemoveXXXFromYYY" 这类格式,后面再加上
一串字段名和类型的清单,那么这个migration就会含有对应的 add_column 和 remove_column 语句。

将会生成:

类似的,

则生成:

这种方法可以操作多个字段,例如:

将会生成:

通常来说,这些自动生成的迁移任务只是起点,我们接下来可以直接修改文件直到我们满意。

The generated migration file for destructive migrations will still be old-style using the upand down methods.
This is because Rails needs to know the original data types defined when you made the original changes.


3 编写一个迁移任务

当你建立一个migration文件时,接下来就是我们要做的了。


3.1 建立数据表

一般要建立数据表可以用迁移任务的 create_table 方法。典型的用法如下:

这样会建立了一个名为 products 的数据表,里面包含一个名为 name 的字段(如之前所说,
这里也会自动加上一个id字段)。

这段代码块可以让你在数据表中新增字段。新增的的方法有格式,第一种,也是相对传统的 写法,如下:

第二种格式,就是所谓的"sexy"写法,把 column 去掉了,用 string 和 integer 等方法来
建立对应类型的字段。至于在后面添加的参数是一样的。

默认在 create_table 时新增的主键名为 id,要改主键的名称,你需要加上 :primary_key 这个选项(不要忘了更新对应
Model 的格式)。如果你根本就不要主键(例如使用多对多连接 HABTM的数据表时),那就传入 :id
=> false 。另外,如果要传入某个特定数据表的设定, 你可以在 :options选项中加上一个SQL片段。例如:

这样在建立数据表的SQL语句中就会加入 ENGINE=BLACKHOLE 。(如果是用
MySQL 的话,预设 是ENGINE=InnoDB)


3.2 创建连接表

迁移任务的 create_join_table 方法可以创建一张多对多的连接表,经典的写法如下:

这里创建了一张名为 categories_products 的连接表,里面包含 category_id 和 product_id 这两个字段。

你可以通过 :table_name 字段去定义自己想要的数据表名称。例如:

这里将会创建一张名为 categorization 的数据表。

默认情况下,create_join_table 将会创建两个不包含参数的字段,不过你也可以通过:column_options 来指定这些参数。例如:

这里将会创建允许为空的product_id和category_id字段。


3.3 变更数据表

要变更现有的数据表,可以用 create_table 的类似方法 change_table。它的用法跟create_table 差不多,但它的代码块有更多的方式。例如:

移除了两个字段 description 和 name,创建了一个 part_number 的字符串类型字段并
为其添加了索引。最后重命名了 upccode 字段。


3.4 特殊方法

有些功能很常用,例如 created_at 和 updated_at 字段,为此,Active
Record 提供了一 些捷径:

以上会建立一个新的名为 products 数据表,并包含 created_at 和 updated_at 字段(当然还有id)。
此外:

则会在原来的数据表中加入这两个字段。

另一个特殊的方法是 references (也可以写成 belongs_to )。它最基本的的功能就是
增加可读性。

以上会建立一个 category_id 的字段,并给它一个适当的类型。要注意这里要输入的是model
的名称而不是字段名。Active Record 会自动在model名称的后面加上 _id 。若你需要用到
多态的belongs_to 关联时,那么 references 会把两个所需的字段都加进去。

以上会建立一个 attachment_id 字段和一个默认值为’Photo’的 attachment_type 字段。references 同时允许你直接定义索引而不用另外在 create_table 中执行 add_index 方法:

以上将会创建一个索引,跟 `add_index :products, :category_id` 所做的一样。

references 辅助方法实际上不会帮你建立外键约束。你可能需要通过execute方法
或者用能够加入"外键支持":#7 的插件。

如果Active Record提供的辅助方法不能满足你的需求,你可以使用execute方法来执行任意
的SQL语句。

要想知道各个方法的细节和范例,请参考API文档,特别是关于ActiveRecord::ConnectionAdapters::SchemaStatements (提供了在up和down中可使用的方法)、 ActiveRecord::ConnectionAdapters::TableDefinition (提供了在create_table所产生的对象中可使用的方法)、以及 ActiveRecord::ConnectionAdapters::Table (提供了在create_table所产生的对象中可使用的方法)。


3.5 使用change方法

在一些情况下,Rails会知道如何去恢复所做的改变,使用change方法可以让我们不用同
时写up和down方法。目前来说change方法只支持以下migration的定义:

add_column

add_index

add_timestamps

create_table

remove_timestamps

rename_column

rename_index

rename_table

如果你需要使用其他方法,那么你就不能使用change方法而需要同时写up和down方法。


3.6 使用up/down方法

Migration里面的down方法能复原up方法所造成的变更。也就是说如果执行了up然后
再执行down,那么数据库的schema应该会没有改变。所以说,如果用up建立一个数据表,
就应该在down方法中删除它。明智的做法会使用跟up完全相反的顺便来做这些事情。
例如,

有时候,你的migrations会做出一些无法复原的事,例如删掉某些资料之类的。在这种情况 下,你可以在down方法中抛出(raise)ActiveRecord::IrreversibleMigration。这样一
来如果有人想恢复你的migration时就会出现错误信息,显示它无法执行。


4 执行Migrations

Rails提供一系列rake任务来执行migrations。第一个跟migration相关的rake任务是 rake
db:migrate。它最基本有用法就是单纯地执行所有还没执行migrations的up或者 change方法。若所有migrations都执行过了,它就会直接结束。执行的顺序是按照migration 的日期。

值得注意的是执行db:migrate也会一起执行db:schema:dump,去更新db/schema.rb文件,
以便跟数据库的结构同步。

如果你要migrate到某个特定版本,Active Record会执行所需的migrations(up,down,change) 直到到达指定的版本为上。所谓版本就是migration文件名前面的那串数字。例如要迁移到 版本20080906120000,只需执行:

如果指定的版本大于当前的版本(往上迁移),那么就会执行up方法到包含指定版本在内的
所有版本。如果是往下迁移的话则会执行所有down方法,但不包括指定版本本身。


4.1 回滚(Rolling Back)

另一个常见的任务是回滚最后一个版本。比如你不小心打错了要修正。输入回滚命令时可以 不用输入先前版本的版本号,直接这样就行了:

这样会执行最后一个migration的down方法。如果要恢复多个migrations的话,可以多给
一个STEP参数:

这样会执行最后3个migrations的down方法。

要回滚然后重新执行最后一个migration的话可以直接执行db:migrate:redo。如果要回滚
重新执行的不止一个版本时可以用STEP参数,就跟db:rollback的用法一样:

这两个rake任务只是用起来比较方便,让你可以不用输入一大串版本号数字。除了输入比较 方便外没有比db:migrate多做什么额外的工作。


4.2 重置数据库

最后是db:reset任务,它会删除数据库,然后重新建立数据库并在重新建立的数据库中
载入当前的schema。

所谓的载入schema跟执行全部的migrations是不一样的,请参照: schema.rb


4.3 执行指定的migration

如果你需要执行一个指定的migration的up或down方法,那么你可以用db:migrate:up和db:migrate:down这两个任务。你只需指定版本号,就可以触发它的up或down方法:

以上会执行20080906120000这个版本的migration的up方法。它会去确认这个migration之前有
没有跑过,所以,如果Active Record认为20080906120000已经跑过,那么执行 db:migrate:up
VERSION=20080906120000将不会做任何操作。


4.4 修改执行migration时的提示结果

默认情况下,migrations会告诉你它们在做什么,花多少时间。例如建立数据库并且添加 索引(index)的话会产生以下输出结果:

想要控制这些输出值的话可以使用这几种方法:
MethodPurpose
suppress_messages阻止这个代码块的任何输出结果
say输出一段文字(第二个参数可以指定是否要缩排)
say_with_time输出一段文字,以及这个代码块需要花多少时间才能跑完。如果代 码块中回传了一个整数,这个数代表受影响的数据(rows)有多少。
例如以下这个migration

会产生这样的输出结果:

如果你希望Active Record不作任何输出,那么执行rake
db:migrate VERBOSE=false就可 以阻止所有的输出结果。


5 在Migrations中使用Models

在migration中不管你是新增数据或者更新数据,多少都会用到一个model。毕竟model存在 就是为了方便地处理我们的数据。这当然是没问题的,只是有些地方需要留意一下。

比方说,当model使用不正确的数据库的字段或者使用的字段是后来的migration创建的时候 就会出现问题。

根据这个例子,当爱丽比和鲍勃在同一份代码上工作,这份代码包含一个Product的模型:

鲍勃正在放假期。

爱丽丝创建了一个migration给products表添加一个新的字段并为它初始化。她同时在 Product模型为这个新的字段添加了验证(validation)。

爱丽丝又添加了一个migration来添加和初始化另一个字段并同样在model中添加了验证。

这两个migrations在爱丽丝用起来是可以正常工作的。

鲍勃从他的假期回来后他做了以下操作:

更新源代码 – 包含了艾丽丝添加的那两个migrations和最新版本的Product model。

执行rake db:migrate命令来执行还未执行的migrations(包含那个更新productmodel
的migration)。

Migration执行失败,因为当model尝试保存的时候它会去验证那两个新增加的字段,而这些 字段在第一个migration执行的时候还没有添加数据库。

对于这种情况可以尝试在migration中建立一个本地的model来修复。这样避免了validations 的执行,因此这个migration可以完成。

当使用假的(faux)model时,我们可以直接调用Product.reset_column_information 来更新ActiveRecord中Product模型的缓存从而在数据库中更新数据。

如果爱丽丝能这样做,那么将不会有问题发生。


6 导出数据库纲要(Schema Dumping)


6.1 Schema文件的作用?

虽然我们用Migrations来定义database schema(数据库纲要),但是我们却不能一次看到完 整精确的schema。这个机制是由db/schema.rb或是Active
Record检验数据库后所生成的 SQL文件来担当。它们设计出来不是用来编辑的,只是纯粹代表着数据库的schema现况。

当我们要部署新的应用程序时,并不需要把整个migrations历程全部重跑一遍,我们只需要 把当前的schema载入新的数据库就可以了。这样做会更快更简单。

例如,这就是建立一个测试用数据库所做的操作:把当前开发环境下的数据库导出来( 看是db/schema.rb或db/development.sql都行),然后再载入到测试数据库。

另外,如果要快速浏览Active Record 对象中有哪些属性也可以通过schema文件。关于 Active Record对象属性的信息并不在model的代码中,而且可能会散布在多个migrations 之间,但是最终都会整理在schema文件中。其次,有个插件叫做"annotate_models":https://github.com/ctran/annotate_models , 可以把schema的结果自动用注释的方式放在每个model的上面,有需要也可以看一下。


6.2 数据库纲要(Schema)的导出类型

导出的schema有 :sql 和 :ruby 两种方式,可以通过 config/application.rb 文件中的config.active_record.schema_format 来设置。

如果设定成:ruby的话,schema就会存在db/schema.rb里面。这个文件看起来像是一个
超大的migration。

各方面来说,它也的确如此。这个文件的生成方式,正是在检查数据库并用create_table、add_index等方法,来表达数据库的结构。由于schema是独立于数据库系统的,只要是
Active Record支持的数据库系统,它都可以载入。如果你的应用程序要发布到多个数据库 系统,这点会非常有用。

不过,这也是有考量的:db/schema.rb没办法表达出特定数据库所专属的功能,像是外键
约束(foreign key constraints)、触发(triggers)或是预存程序(stored procedures)。 虽然migration里面可以执行自定义的SQL语句,但是schema dumper却无法从数据库中将它 们重组回来。如果你要用到这个自定义SQL的功能,那么就必须把schema的格式设定成:sql。

设定成:sql的话就不是用Active Record来导出Schema了,而是用该数据库系统的专门工具,
从db:structure:dump这个Rake任务导进db/#{Rails.env}_structure.sql里面。比方说,
用PostgreSQL的话,就是用pg_dump这个工具。用MySQL的话,则是各个数据表的+SHOW CREATETABLE+输出结果。载入这些schema只不过是执行了文件里面的SQL语句而已。

虽然用:sql方式可以完美地复制数据库结构,不过如果换一个不同的数据库系统那就往往
没办法将schema写入新的数据库了。


6.3 导出数据库纲要(Schema)以及版本控制

因为schema dumps是数据库结构的精确来源,这么重要的东西,强烈建议你把它加入到版本 控制系统内。


7 Active Record 与 Referential Integrity(参照完整性)

所谓的 Active Record 之道,认为有头脑的应该是 models ,而不是数据库。因此,像是触发器 (triggers)或外键约束(foreign key constraints)这类偏向数据库功能,在 Rails 中就 比较不常使用。

在 model里面要确保数据库完整性,可以用 validates
:foreign_key, :uniqueness => true来做验证。Model里面,数据库关联(associations)上有个:dependent的选项,可以设定
成当父对象删除(destroy)的时候,自动连其子对象也一起删除。不过就像其他应用程式层级 操作的东西一样,这并不能保证数据表之间的参照完整性,所以有些人用外键约束来增进 这个功能。

关于外建约束的功能,Active Record 并没有提供可以直接编辑它的工具,不过还是可以用 execute方法来执行任意的
SQL。也有不少的插件,可以帮 Active Record 加上外键的支持 (还可以把外键的设定全部显示在 db/schema.rb),像是这个"foreigner":https://github.com/matthuhiggins/foreigner


关于 Rails Guides 中文

你也可以为 Rails Guides 中文做出贡献.

如果你发现了任何翻译上的错误或者不足.请到 GitHub 帐号 clone 并且 fork 一份进行更正提交,我们会很快做出回应。 如果你发现了任何代码上的 bug 或者不足,你可以到 docrails 进行反馈与更正。
如果你发现了某一篇文章已经过时了并且并没有做出相应的提示,你也同样可以在 这里 上发起一个请求来提醒我们。

如果对 Rails 有什么建议的话去 这里.

当然你不管你有什么想要说的都可以发到 Ruby China 讨论或者 mail我.欢迎加入到
Ruby on Rails 社区!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: