-
Notifications
You must be signed in to change notification settings - Fork 2
/
active_record_migrations.html
895 lines (806 loc) · 51.8 KB
/
active_record_migrations.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Active Record 数据库迁移 — Ruby on Rails 指南</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
更多内容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">综览</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下载</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">源码</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">视频</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首页">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首页</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目录</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="L">
<dt>入门</dt>
<dd><a href="getting_started.html">Rails 入门</a></dd>
<dt>模型</dt>
<dd><a href="active_record_basics.html">Active Record 基础</a></dd>
<dd><a href="active_record_migrations.html">Active Record 数据库迁移</a></dd>
<dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
<dd><a href="association_basics.html">Active Record 关联</a></dd>
<dd><a href="active_record_querying.html">Active Record 查询</a></dd>
<dt>视图</dt>
<dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
<dd><a href="form_helpers.html">Action View 表单帮助方法</a></dd>
<dt>控制器</dt>
<dd><a href="action_controller_overview.html">Action Controller 简介</a></dd>
<dd><a href="routing.html">Rails 路由全解</a></dd>
</dl>
<dl class="R">
<dt>深入</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
<dd><a href="i18n.html">Rails 国际化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
<dd><a href="active_job_basics.html">Active Job 基础</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">调试 Rails 程序</a></dd>
<dd><a href="configuring.html">设置 Rails 程序</a></dd>
<dd><a href="command_line.html">Rails 命令行</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>扩展 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客制与新建 Rails 产生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 应用程式模版</a></dd>
<dt>贡献 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件准则</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a></dd>
<dt>维护方针</dt>
<dd><a href="maintenance_policy.html">维护方针</a></dd>
<dt>发布记</dt>
<dd><a href="upgrading_ruby_on_rails.html">升级 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
</dl>
</div>
</li>
<!-- <li><a class="nav-item" href="//github.com/docrails-tw/wiki">参与翻译</a></li> -->
<li><a class="nav-item" href="https://github.com/ruby-china/guides/blob/master/CONTRIBUTING.md">贡献</a></li>
<li><a class="nav-item" href="credits.html">致谢</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目录</option>
<optgroup label="入门">
<option value="getting_started.html">Rails 入门</option>
</optgroup>
<optgroup label="模型">
<option value="active_record_basics.html">Active Record 基础</option>
<option value="active_record_migrations.html">Active Record 数据库迁移</option>
<option value="active_record_validations.html">Active Record 数据验证</option>
<option value="active_record_callbacks.html">Active Record 回调</option>
<option value="association_basics.html">Active Record 关联</option>
<option value="active_record_querying.html">Active Record 查询</option>
</optgroup>
<optgroup label="视图">
<option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
<option value="form_helpers.html">Action View 表单帮助方法</option>
</optgroup>
<optgroup label="控制器">
<option value="action_controller_overview.html">Action Controller 简介</option>
<option value="routing.html">Rails 路由全解</option>
</optgroup>
<optgroup label="深入">
<option value="active_support_core_extensions.html">Active Support 核心扩展</option>
<option value="i18n.html">Rails 国际化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基础</option>
<option value="active_job_basics.html">Active Job 基础</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">调试 Rails 程序</option>
<option value="configuring.html">设置 Rails 程序</option>
<option value="command_line.html">Rails 命令行</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="扩展 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客制与新建 Rails 产生器</option>
<option value="rails_application_templates.html">Rails 应用程式模版</option>
</optgroup>
<optgroup label="贡献 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件准则</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</option>
</optgroup>
<optgroup label="维护方针">
<option value="maintenance_policy.html">维护方针</option>
</optgroup>
<optgroup label="发布记">
<option value="upgrading_ruby_on_rails.html">升级 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide" />
<div id="feature">
<div class="wrapper">
<h2>Active Record 数据库迁移</h2><p>迁移是 Active Record 提供的一个功能,按照时间顺序管理数据库模式。使用迁移,无需编写 SQL,使用简单的 Ruby DSL 就能修改数据表。</p><p>读完本文,你将学到:</p>
<ul>
<li>生成迁移文件的生成器;</li>
<li>Active Record 提供用来修改数据库的方法;</li>
<li>管理迁移和数据库模式的 Rake 任务;</li>
<li>迁移和 <code>schema.rb</code> 文件的关系;</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E8%BF%81%E7%A7%BB%E7%AE%80%E4%BB%8B">迁移简介</a></li>
<li>
<a href="#%E5%88%9B%E5%BB%BA%E8%BF%81%E7%A7%BB">创建迁移</a>
<ul>
<li><a href="#%E5%8D%95%E7%8B%AC%E5%88%9B%E5%BB%BA%E8%BF%81%E7%A7%BB">单独创建迁移</a></li>
<li><a href="#%E6%A8%A1%E5%9E%8B%E7%94%9F%E6%88%90%E5%99%A8">模型生成器</a></li>
<li><a href="#%E6%94%AF%E6%8C%81%E7%9A%84%E7%B1%BB%E5%9E%8B%E4%BF%AE%E9%A5%B0%E7%AC%A6">支持的类型修饰符</a></li>
</ul>
</li>
<li>
<a href="#%E7%BC%96%E5%86%99%E8%BF%81%E7%A7%BB">编写迁移</a>
<ul>
<li><a href="#%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E8%A1%A8">创建数据表</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E8%81%94%E5%90%88%E6%95%B0%E6%8D%AE%E8%A1%A8">创建联合数据表</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E8%A1%A8">修改数据表</a></li>
<li><a href="#%E5%A6%82%E6%9E%9C%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95%E4%B8%8D%E5%A4%9F%E7%94%A8">如果帮助方法不够用</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-change-%E6%96%B9%E6%B3%95">使用 <code>change</code> 方法</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-reversible-%E6%96%B9%E6%B3%95">使用 <code>reversible</code> 方法</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-up-%E5%92%8C-down-%E6%96%B9%E6%B3%95">使用 <code>up</code> 和 <code>down</code> 方法</a></li>
<li><a href="#%E6%92%A4%E9%94%80%E4%B9%8B%E5%89%8D%E7%9A%84%E8%BF%81%E7%A7%BB">撤销之前的迁移</a></li>
</ul>
</li>
<li>
<a href="#%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB">运行迁移</a>
<ul>
<li><a href="#%E5%9B%9E%E6%BB%9A">回滚</a></li>
<li><a href="#%E6%90%AD%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93">搭建数据库</a></li>
<li><a href="#%E9%87%8D%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93">重建数据库</a></li>
<li><a href="#%E8%BF%90%E8%A1%8C%E6%8C%87%E5%AE%9A%E7%9A%84%E8%BF%81%E7%A7%BB">运行指定的迁移</a></li>
<li><a href="#%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E7%8E%AF%E5%A2%83%E4%B8%AD%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB">在不同的环境中运行迁移</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB%E6%97%B6%E7%9A%84%E8%BE%93%E5%87%BA">修改运行迁移时的输出</a></li>
</ul>
</li>
<li><a href="#%E4%BF%AE%E6%94%B9%E7%8E%B0%E6%9C%89%E7%9A%84%E8%BF%81%E7%A7%BB">修改现有的迁移</a></li>
<li>
<a href="#%E5%AF%BC%E5%87%BA%E6%A8%A1%E5%BC%8F">导出模式</a>
<ul>
<li><a href="#%E6%A8%A1%E5%BC%8F%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%9C%E7%94%A8">模式文件的作用</a></li>
<li><a href="#%E5%AF%BC%E5%87%BA%E7%9A%84%E6%A8%A1%E5%BC%8F%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B">导出的模式文件类型</a></li>
<li><a href="#%E6%A8%A1%E5%BC%8F%E5%AF%BC%E5%87%BA%E5%92%8C%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6">模式导出和版本控制</a></li>
</ul>
</li>
<li><a href="#active-record-%E5%92%8C%E5%BC%95%E7%94%A8%E5%AE%8C%E6%95%B4%E6%80%A7">Active Record 和引用完整性</a></li>
<li><a href="#%E8%BF%81%E7%A7%BB%E5%92%8C%E7%A7%8D%E5%AD%90%E6%95%B0%E6%8D%AE">迁移和种子数据</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="迁移简介">1 迁移简介</h3><p>迁移使用一种统一、简单的方式,按照时间顺序修改数据库的模式。迁移使用 Ruby DSL 编写,因此不用手动编写 SQL 语句,对数据库的操作和所用的数据库种类无关。</p><p>你可以把每个迁移看做数据库的一个修订版本。数据库中一开始什么也没有,各个迁移会添加或删除数据表、字段或记录。Active Record 知道如何按照时间线更新数据库,不管数据库现在的模式如何,都能更新到最新结构。同时,Active Record 还会更新 <code>db/schema.rb</code> 文件,匹配最新的数据库结构。</p><p>下面是一个迁移示例:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
</pre>
</div>
<p>这个迁移创建了一个名为 <code>products</code> 的表,然后在表中创建字符串字段 <code>name</code> 和文本字段 <code>description</code>。名为 <code>id</code> 的主键字段会被自动创建。<code>id</code> 字段是所有 Active Record 模型的默认主键。<code>timestamps</code> 方法创建两个字段:<code>created_at</code> 和 <code>updated_at</code>。如果数据表中有这两个字段,Active Record 会负责操作。</p><p>注意,对数据库的改动按照时间向前 推移。运行迁移之前,数据表还不存在。运行迁移后,才会创建数据表。Active Record 知道如何撤销迁移,如果回滚这次迁移,数据表会被删除。</p><p>在支持事务的数据库中,对模式的改动会在一个事务中执行。如果数据库不支持事务,迁移失败时,成功执行的操作将无法回滚。如要回滚,必须手动改回来。</p><div class="note"><p>某些查询无法在事务中运行。如果适配器支持 DDL 事务,可以在某个迁移中调用 <code>disable_ddl_transaction!</code> 方法禁用。</p></div><p>如果想在迁移中执行 Active Record 不知如何撤销的操作,可以使用 <code>reversible</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ChangeProductsPrice < ActiveRecord::Migration
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
</pre>
</div>
<p>或者不用 <code>change</code> 方法,分别使用 <code>up</code> 和 <code>down</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ChangeProductsPrice < ActiveRecord::Migration
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
</pre>
</div>
<h3 id="创建迁移">2 创建迁移</h3><h4 id="单独创建迁移">2.1 单独创建迁移</h4><p>迁移文件存储在 <code>db/migrate</code> 文件夹中,每个迁移保存在一个文件中。文件名采用 <code>YYYYMMDDHHMMSS_create_products.rb</code> 形式,即一个 UTC 时间戳后加以下划线分隔的迁移名。迁移的类名(驼峰式)要和文件名时间戳后面的部分匹配。例如,在 <code>20080906120000_create_products.rb</code> 文件中要定义 <code>CreateProducts</code> 类;在 <code>20080906120001_add_details_to_products.rb</code> 文件中要定义 <code>AddDetailsToProducts</code> 类。文件名中的时间戳决定要运行哪个迁移,以及按照什么顺序运行。从其他程序中复制迁移,或者自己生成迁移时,要注意运行的顺序。</p><p>自己计算时间戳不是件简单的事,所以 Active Record 提供了一个生成器:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddPartNumberToProducts
</pre>
</div>
<p>这个命令生成一个空的迁移,但名字已经起好了:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddPartNumberToProducts < ActiveRecord::Migration
def change
end
end
</pre>
</div>
<p>如果迁移的名字是“AddXXXToYYY”或者“RemoveXXXFromYYY”这种格式,而且后面跟着一个字段名和类型列表,那么迁移中会生成合适的 <code>add_column</code> 或 <code>remove_column</code> 语句。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddPartNumberToProducts part_number:string
</pre>
</div>
<p>这个命令生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
end
end
</pre>
</div>
<p>如果想为新建的字段创建添加索引,可以这么做:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddPartNumberToProducts part_number:string:index
</pre>
</div>
<p>这个命令生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
</pre>
</div>
<p>类似地,还可以生成删除字段的迁移:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration RemovePartNumberFromProducts part_number:string
</pre>
</div>
<p>这个命令生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class RemovePartNumberFromProducts < ActiveRecord::Migration
def change
remove_column :products, :part_number, :string
end
end
</pre>
</div>
<p>迁移生成器不单只能创建一个字段,例如:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddDetailsToProducts part_number:string price:decimal
</pre>
</div>
<p>生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
</pre>
</div>
<p>如果迁移名是“CreateXXX”形式,后面跟着一串字段名和类型声明,迁移就会创建名为“XXX”的表,以及相应的字段。例如:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration CreateProducts name:string part_number:string
</pre>
</div>
<p>生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.string :part_number
end
end
end
</pre>
</div>
<p>生成器生成的只是一些基础代码,你可以根据需要修改 <code>db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb</code> 文件,增删代码。</p><p>在生成器中还可把字段类型设为 <code>references</code>(还可使用 <code>belongs_to</code>)。例如:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddUserRefToProducts user:references
</pre>
</div>
<p>生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
end
end
</pre>
</div>
<p>这个迁移会创建 <code>user_id</code> 字段,并建立索引。</p><p>如果迁移名中包含 <code>JoinTable</code>,生成器还会创建联合数据表:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
rails g migration CreateJoinTableCustomerProduct customer product
</pre>
</div>
<p>生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateJoinTableCustomerProduct < ActiveRecord::Migration
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
</pre>
</div>
<h4 id="模型生成器">2.2 模型生成器</h4><p>模型生成器和脚手架生成器会生成合适的迁移,创建模型。迁移中会包含创建所需数据表的代码。如果在生成器中指定了字段,还会生成创建字段的代码。例如,运行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate model Product name:string description:text
</pre>
</div>
<p>会生成如下的迁移:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
</pre>
</div>
<p>字段的名字和类型数量不限。</p><h4 id="支持的类型修饰符">2.3 支持的类型修饰符</h4><p>在字段类型后面,可以在花括号中添加选项。可用的修饰符如下:</p>
<ul>
<li>
<code>limit</code>:设置 <code>string/text/binary/integer</code> 类型字段的最大值;</li>
<li>
<code>precision</code>:设置 <code>decimal</code> 类型字段的精度,即数字的位数;</li>
<li>
<code>scale</code>:设置 <code>decimal</code> 类型字段小数点后的数字位数;</li>
<li>
<code>polymorphic</code>:为 <code>belongs_to</code> 关联添加 <code>type</code> 字段;</li>
<li>
<code>null</code>:是否允许该字段的值为 <code>NULL</code>;</li>
</ul>
<p>例如,执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
</pre>
</div>
<p>生成的迁移如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
end
end
</pre>
</div>
<h3 id="编写迁移">3 编写迁移</h3><p>使用前面介绍的生成器生成迁移后,就可以开始写代码了。</p><h4 id="创建数据表">3.1 创建数据表</h4><p><code>create_table</code> 方法最常用,大多数时候都会由模型或脚手架生成器生成。典型的用例如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_table :products do |t|
t.string :name
end
</pre>
</div>
<p>这个迁移会创建 <code>products</code> 数据表,在数据表中创建 <code>name</code> 字段(后面会介绍,还会自动创建 <code>id</code> 字段)。</p><p>默认情况下,<code>create_table</code> 方法会创建名为 <code>id</code> 的主键。通过 <code>:primary_key</code> 选项可以修改主键名(修改后别忘了修改相应的模型)。如果不想生成主键,可以传入 <code>id: false</code> 选项。如果设置数据库的选项,可以在 <code>:options</code> 选择中使用 SQL。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
</pre>
</div>
<p>这样设置之后,会在创建数据表的 SQL 语句后面加上 <code>ENGINE=BLACKHOLE</code>。(MySQL 默认的选项是 <code>ENGINE=InnoDB</code>)</p><h4 id="创建联合数据表">3.2 创建联合数据表</h4><p><code>create_join_table</code> 方法用来创建 HABTM 联合数据表。典型的用例如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_join_table :products, :categories
</pre>
</div>
<p>这段代码会创建一个名为 <code>categories_products</code> 的数据表,包含两个字段:<code>category_id</code> 和 <code>product_id</code>。这两个字段的 <code>:null</code> 选项默认情况都是 <code>false</code>,不过可在 <code>:column_options</code> 选项中设置。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_join_table :products, :categories, column_options: {null: true}
</pre>
</div>
<p>这段代码会把 <code>product_id</code> 和 <code>category_id</code> 字段的 <code>:null</code> 选项设为 <code>true</code>。</p><p>如果想修改数据表的名字,可以传入 <code>:table_name</code> 选项。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_join_table :products, :categories, table_name: :categorization
</pre>
</div>
<p>创建的数据表名为 <code>categorization</code>。</p><p><code>create_join_table</code> 还可接受代码库,用来创建索引(默认无索引)或其他字段。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
</pre>
</div>
<h4 id="修改数据表">3.3 修改数据表</h4><p>有一个和 <code>create_table</code> 类似地方法,名为 <code>change_table</code>,用来修改现有的数据表。其用法和 <code>create_table</code> 类似,不过传入块的参数知道更多技巧。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
</pre>
</div>
<p>这段代码删除了 <code>description</code> 和 <code>name</code> 字段,创建 <code>part_number</code> 字符串字段,并建立索引,最后重命名 <code>upccode</code> 字段。</p><h4 id="如果帮助方法不够用">3.4 如果帮助方法不够用</h4><p>如果 Active Record 提供的帮助方法不够用,可以使用 <code>execute</code> 方法,执行任意的 SQL 语句:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
</pre>
</div>
<p>各方法的详细用法请查阅 API 文档:</p>
<ul>
<li>
<a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html"><code>ActiveRecord::ConnectionAdapters::SchemaStatements</code></a>:包含可在 <code>change</code>,<code>up</code> 和 <code>down</code> 中使用的方法;</li>
<li>
<a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html"><code>ActiveRecord::ConnectionAdapters::TableDefinition</code></a>:包含可在 <code>create_table</code> 方法的块参数上调用的方法;</li>
<li>
<a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html"><code>ActiveRecord::ConnectionAdapters::Table</code></a>:包含可在 <code>change_table</code> 方法的块参数上调用的方法;</li>
</ul>
<h4 id="使用-change-方法">3.5 使用 <code>change</code> 方法</h4><p><code>change</code> 是迁移中最常用的方法,大多数情况下都能完成指定的操作,而且 Active Record 知道如何撤这些操作。目前,在 <code>change</code> 方法中只能使用下面的方法:</p>
<ul>
<li><code>add_column</code></li>
<li><code>add_index</code></li>
<li><code>add_reference</code></li>
<li><code>add_timestamps</code></li>
<li><code>create_table</code></li>
<li><code>create_join_table</code></li>
<li>
<code>drop_table</code>(必须提供代码块)</li>
<li>
<code>drop_join_table</code>(必须提供代码块)</li>
<li><code>remove_timestamps</code></li>
<li><code>rename_column</code></li>
<li><code>rename_index</code></li>
<li><code>remove_reference</code></li>
<li><code>rename_table</code></li>
</ul>
<p>只要在块中不使用 <code>change</code>、<code>change_default</code> 或 <code>remove</code> 方法,<code>change_table</code> 中的操作也是可逆的。</p><p>如果要使用任何其他方法,可以使用 <code>reversible</code> 方法,或者不定义 <code>change</code> 方法,而分别定义 <code>up</code> 和 <code>down</code> 方法。</p><h4 id="使用-reversible-方法">3.6 使用 <code>reversible</code> 方法</h4><p>Active Record 可能不知如何撤销复杂的迁移操作,这时可以使用 <code>reversible</code> 方法指定运行迁移和撤销迁移时怎么操作。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ExampleMigration < ActiveRecord::Migration
def change
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
</pre>
</div>
<p>使用 <code>reversible</code> 方法还能确保操作按顺序执行。在上面的例子中,如果撤销迁移,<code>down</code> 代码块会在 <code>home_page_url</code> 字段删除后、<code>products</code> 数据表删除前运行。</p><p>有时,迁移的操作根本无法撤销,例如删除数据。这是,可以在 <code>down</code> 代码块中抛出 <code>ActiveRecord::IrreversibleMigration</code> 异常。如果有人尝试撤销迁移,会看到一个错误消息,告诉他无法撤销。</p><h4 id="使用-up-和-down-方法">3.7 使用 <code>up</code> 和 <code>down</code> 方法</h4><p>在迁移中可以不用 <code>change</code> 方法,而用 <code>up</code> 和 <code>down</code> 方法。<code>up</code> 方法定义要对数据库模式做哪些操作,<code>down</code> 方法用来撤销这些操作。也就是说,如果执行 <code>up</code> 后立即执行 <code>down</code>,数据库的模式应该没有任何变化。例如,在 <code>up</code> 中创建了数据表,在 <code>down</code> 方法中就要将其删除。撤销时最好按照添加的相反顺序进行。前一节中的 <code>reversible</code> 用法示例代码可以改成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ExampleMigration < ActiveRecord::Migration
def up
create_table :products do |t|
t.references :category
end
# add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
drop_table :products
end
end
</pre>
</div>
<p>如果迁移不可撤销,应该在 <code>down</code> 方法中抛出 <code>ActiveRecord::IrreversibleMigration</code> 异常。如果有人尝试撤销迁移,会看到一个错误消息,告诉他无法撤销。</p><h4 id="撤销之前的迁移">3.8 撤销之前的迁移</h4><p>Active Record 提供了撤销迁移的功能,通过 <code>revert</code> 方法实现:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require_relative '2012121212_example_migration'
class FixupExampleMigration < ActiveRecord::Migration
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
</pre>
</div>
<p><code>revert</code> 方法还可接受一个块,定义撤销操作。<code>revert</code> 方法可用来撤销以前迁移的部分操作。例如,<code>ExampleMigration</code> 已经执行,但后来觉得最好还是序列化产品列表。那么,可以编写下面的代码:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class SerializeProductListMigration < ActiveRecord::Migration
def change
add_column :categories, :product_list
reversible do |dir|
dir.up do
# transfer data from Products to Category#product_list
end
dir.down do
# create Products from Category#product_list
end
end
revert do
# copy-pasted code from ExampleMigration
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
# The rest of the migration was ok
end
end
end
</pre>
</div>
<p>上面这个迁移也可以不用 <code>revert</code> 方法,不过步骤就多了:调换 <code>create_table</code> 和 <code>reversible</code> 的顺序,把 <code>create_table</code> 换成 <code>drop_table</code>,还要对调 <code>up</code> 和 <code>down</code> 中的代码。这些操作都可交给 <code>revert</code> 方法完成。</p><h3 id="运行迁移">4 运行迁移</h3><p>Rails 提供了很多 Rake 任务,用来执行指定的迁移。</p><p>其中最常使用的是 <code>rake db:migrate</code>,执行还没执行的迁移中的 <code>change</code> 或 <code>up</code> 方法。如果没有未运行的迁移,直接退出。<code>rake db:migrate</code> 按照迁移文件名中时间戳顺序执行迁移。</p><p>注意,执行 <code>db:migrate</code> 时还会执行 <code>db:schema:dump</code>,更新 <code>db/schema.rb</code> 文件,匹配数据库的结构。</p><p>如果指定了版本,Active Record 会运行该版本之前的所有迁移。版本就是迁移文件名前的数字部分。例如,要运行 20080906120000 这个迁移,可以执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate VERSION=20080906120000
</pre>
</div>
<p>如果 20080906120000 比当前的版本高,上面的命令就会执行所有 20080906120000 之前(包括 20080906120000)的迁移中的 <code>change</code> 或 <code>up</code> 方法,但不会运行 20080906120000 之后的迁移。如果回滚迁移,则会执行 20080906120000 之前(不包括 20080906120000)的迁移中的 <code>down</code> 方法。</p><h4 id="回滚">4.1 回滚</h4><p>还有一个常用的操作时回滚到之前的迁移。例如,迁移代码写错了,想纠正。我们无须查找迁移的版本号,直接执行下面的命令即可:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:rollback
</pre>
</div>
<p>这个命令会回滚上一次迁移,撤销 <code>change</code> 方法中的操作,或者执行 <code>down</code> 方法。如果想撤销多个迁移,可以使用 <code>STEP</code> 参数:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:rollback STEP=3
</pre>
</div>
<p>这个命令会撤销前三次迁移。</p><p><code>db:migrate:redo</code> 命令可以回滚上一次迁移,然后再次执行迁移。和 <code>db:rollback</code> 一样,如果想重做多次迁移,可以使用 <code>STEP</code> 参数。例如:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate:redo STEP=3
</pre>
</div>
<p>这些 Rake 任务的作用和 <code>db:migrate</code> 一样,只是用起来更方便,因为无需查找特定的迁移版本号。</p><h4 id="搭建数据库">4.2 搭建数据库</h4><p><code>rake db:setup</code> 任务会创建数据库,加载模式,并填充种子数据。</p><h4 id="重建数据库">4.3 重建数据库</h4><p><code>rake db:reset</code> 任务会删除数据库,然后重建,等价于 <code>rake db:drop db:setup</code>。</p><div class="note"><p>这个任务和执行所有迁移的作用不同。<code>rake db:reset</code> 使用的是 <code>schema.rb</code> 文件中的内容。如果迁移无法回滚,<code>rake db:reset</code> 起不了作用。详细介绍参见“<a href="#schema-dumping-and-you">导出模式</a>”一节。</p></div><h4 id="运行指定的迁移">4.4 运行指定的迁移</h4><p>如果想执行指定迁移,或者撤销指定迁移,可以使用 <code>db:migrate:up</code> 和 <code>db:migrate:down</code> 任务,指定相应的版本号,就会根据需求调用 <code>change</code>、<code>up</code> 或 <code>down</code> 方法。例如:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate:up VERSION=20080906120000
</pre>
</div>
<p>这个命令会执行 20080906120000 迁移中的 <code>change</code> 方法或 <code>up</code> 方法。<code>db:migrate:up</code> 首先会检测指定的迁移是否已经运行,如果 Active Record 任务已经执行,就不会做任何操作。</p><h4 id="在不同的环境中运行迁移">4.5 在不同的环境中运行迁移</h4><p>默认情况下,<code>rake db:migrate</code> 任务在 <code>development</code> 环境中执行。要在其他环境中运行迁移,执行命令时可以使用环境变量 <code>RAILS_ENV</code> 指定环境。例如,要在 <code>test</code> 环境中运行迁移,可以执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate RAILS_ENV=test
</pre>
</div>
<h4 id="修改运行迁移时的输出">4.6 修改运行迁移时的输出</h4><p>默认情况下,运行迁移时,会输出操作了哪些操作,以及花了多长时间。创建数据表并添加索引的迁移产生的输出如下:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
</pre>
</div>
<p>在迁移中可以使用很多方法,控制输出:</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>suppress_messages</td>
<td>接受一个代码块,禁止代码块中所有操作的输出</td>
</tr>
<tr>
<td>say</td>
<td>接受一个消息字符串作为参数,将其输出。第二个参数是布尔值,指定输出结果是否缩进</td>
</tr>
<tr>
<td>say_with_time</td>
<td>输出文本,以及执行代码块中操作所用时间。如果代码块的返回结果是整数,会当做操作的记录数量</td>
</tr>
</tbody>
</table>
<p>例如,下面这个迁移:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateProducts < ActiveRecord::Migration
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created a table"
suppress_messages {add_index :products, :name}
say "and an index!", true
say_with_time 'Waiting for a while' do
sleep 10
250
end
end
end
</pre>
</div>
<p>输出结果是:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
== CreateProducts: migrating =================================================
-- Created a table
-> and an index!
-- Waiting for a while
-> 10.0013s
-> 250 rows
== CreateProducts: migrated (10.0054s) =======================================
</pre>
</div>
<p>如果不想让 Active Record 输出任何结果,可以使用 <code>rake db:migrate VERBOSE=false</code>。</p><h3 id="修改现有的迁移">5 修改现有的迁移</h3><p>有时编写的迁移中可能有错误,如果已经运行了迁移,不能直接编辑迁移文件再运行迁移。Rails 认为这个迁移已经运行,所以执行 <code>rake db:migrate</code> 任务时什么也不会做。这种情况必须先回滚迁移(例如,执行 <code>rake db:rollback</code> 任务),编辑迁移文件后再执行 <code>rake db:migrate</code> 任务执行改正后的版本。</p><p>一般来说,直接修改现有的迁移不是个好主意。这么做会为你以及你的同事带来额外的工作量,如果这个迁移已经在生产服务器上运行过,还可能带来不必要的麻烦。你应该编写一个新的迁移,做所需的改动。编辑新生成还未纳入版本控制的迁移(或者更宽泛地说,还没有出现在开发设备之外),相对来说是安全的。</p><p>在新迁移中撤销之前迁移中的全部操作或者部分操作可以使用 <code>revert</code> 方法。(参见前面的 <a href="#reverting-previous-migrations">撤销之前的迁移</a> 一节)</p><h3 id="导出模式">6 导出模式</h3><h4 id="模式文件的作用">6.1 模式文件的作用</h4><p>迁移的作用并不是为数据库模式提供可信的参考源。<code>db/schema.rb</code> 或由 Active Record 生成的 SQL 文件才有这个作用。<code>db/schema.rb</code> 这些文件不可修改,其目的是表示数据库的当前结构。</p><p>部署新程序时,无需运行全部的迁移。直接加载数据库结构要简单快速得多。</p><p>例如,测试数据库是这样创建的:导出开发数据库的结构(存入文件 <code>db/schema.rb</code> 或 <code>db/structure.sql</code>),然后导入测试数据库。</p><p>模式文件还可以用来快速查看 Active Record 中有哪些属性。模型中没有属性信息,而且迁移会频繁修改属性,但是模式文件中有最终的结果。<a href="https://github.com/ctran/annotate_models">annotate_models</a> gem 会在模型文件的顶部加入注释,自动添加并更新模型的模式。</p><h4 id="导出的模式文件类型">6.2 导出的模式文件类型</h4><p>导出模式有两种方法,由 <code>config/application.rb</code> 文件中的 <code>config.active_record.schema_format</code> 选项设置,可以是 <code>:sql</code> 或 <code>:ruby</code>。</p><p>如果设为 <code>:ruby</code>,导出的模式保存在 <code>db/schema.rb</code> 文件中。打开这个文件,你会发现内容很多,就像一个很大的迁移:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
ActiveRecord::Schema.define(version: 20080906171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "part_number"
end
end
</pre>
</div>
<p>大多数情况下,文件的内容都是这样。这个文件使用 <code>create_table</code>、<code>add_index</code> 等方法审查数据库的结构。这个文件盒使用的数据库类型无关,可以导入任何一种 Active Record 支持的数据库。如果开发的程序需要兼容多种数据库,可以使用这个文件。</p><p>不过 <code>db/schema.rb</code> 也有缺点:无法执行数据库的某些操作,例如外键约束,触发器,存储过程。在迁移文件中可以执行 SQL 语句,但导出模式的程序无法从数据库中重建这些语句。如果你的程序用到了前面提到的数据库操作,可以把模式文件的格式设为 <code>:sql</code>。</p><p><code>:sql</code> 格式的文件不使用 Active Record 的模式导出程序,而使用数据库自带的导出工具(执行 <code>db:structure:dump</code> 任务),把数据库模式导入 <code>db/structure.sql</code> 文件。例如,PostgreSQL 使用 <code>pg_dump</code> 导出模式。如果使用 MySQL,<code>db/structure.sql</code> 文件中会出现多个 <code>SHOW CREATE TABLE</code> 用来创建数据表的语句。</p><p>加载模式时,只要执行其中的 SQL 语句即可。按预期,导入后会创建一个完整的数据库结构。使用 <code>:sql</code> 格式,就不能把模式导入其他类型的数据库中了。</p><h4 id="模式导出和版本控制">6.3 模式导出和版本控制</h4><p>因为导出的模式文件是数据库模式的可信源,强烈推荐将其纳入版本控制。</p><h3 id="active-record-和引用完整性">7 Active Record 和引用完整性</h3><p>Active Record 在模型中,而不是数据库中设置关联。因此,需要在数据库中实现的功能,例如触发器、外键约束,不太常用。</p><p><code>validates :foreign_key, uniqueness: true</code> 这个验证是模型保证数据完整性的一种方法。在关联中设置 <code>:dependent</code> 选项,可以保证父对象删除后,子对象也会被删除。和任何一种程序层的操作一样,这些无法完全保证引用完整性,所以很多人还是会在数据库中使用外键约束。</p><p>Active Record 并没有为使用这些功能提供任何工具,不过 <code>execute</code> 方法可以执行任意的 SQL 语句。还可以使用 <a href="https://github.com/matthuhiggins/foreigner">foreigner</a> 等 gem,为 Active Record 添加外键支持(还能把外键导出到 <code>db/schema.rb</code> 文件)。</p><h3 id="迁移和种子数据">8 迁移和种子数据</h3><p>有些人使用迁移把数据存入数据库:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class AddInitialProducts < ActiveRecord::Migration
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
</pre>
</div>
<p>Rails 提供了“种子”功能,可以把初始化数据存入数据库。这个功能用起来很简单,在 <code>db/seeds.rb</code> 文件中写一些 Ruby 代码,然后执行 <code>rake db:seed</code> 命令即可:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
</pre>
</div>
<p>填充新建程序的数据库,使用这种方法操作起来简洁得多。</p>
<h3>反馈</h3>
<p>
欢迎帮忙改善指南质量。
</p>
<p>
如发现任何错误,欢迎修正。开始贡献前,可先行阅读<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">贡献指南:文档</a>。
</p>
<p>翻译如有错误,深感抱歉,欢迎 <a href="https://github.com/ruby-china/guides/fork">Fork</a> 修正,或至此处<a href="https://github.com/ruby-china/guides/issues/new">回报</a>。</p>
<p>
文章可能有未完成或过时的内容。请先检查 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 来确定问题在 master 是否已经修掉了。再上 master 补上缺少的文件。内容参考 <a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a>来了解行文风格。
</p>
<p>最后,任何关于 Ruby on Rails 文档的讨论,欢迎到 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 邮件群组</a>。
</p>
</div>
</div>
</div>
<hr class="hide" />
<div id="footer">
<div class="wrapper">
<p>本著作采用<a href="https://creativecommons.org/licenses/by-sa/4.0/">创用 CC 姓名标示-相同方式分享 4.0 国际授权条款</a>授权。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 为 David Heinemeier Hansson 的商标。版权所有。</p>
</div>
</div>
<script type="text/javascript" src="javascripts/jquery.min.js"></script>
<script type="text/javascript" src="javascripts/responsive-tables.js"></script>
<script type="text/javascript" src="javascripts/guides.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shCore.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script>
<script type="text/javascript">
SyntaxHighlighter.all();
$(guidesIndex.bind);
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
// ga('create', '', 'ruby-china.github.io');
ga('require', 'displayfeatures');
ga('send', 'pageview');
</script>
</body>
</html>