-
Notifications
You must be signed in to change notification settings - Fork 0
/
active_record_migrations.html
960 lines (867 loc) · 57 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
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Active Record 遷移 — Ruby on Rails 指南</title>
<meta name="description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)" >
<meta name="keywords" content="Ruby on Rails Guides 指南 中文 學習 免費 網路 Web 開發" >
<meta name="author" content="http://git.io/G_R1sA">
<meta property="fb:admins" content="1340181291">
<meta property="og:title" content="Active Record 遷移 — Ruby on Rails 指南" >
<meta property="og:site_name" content="Ruby on Rails 指南">
<meta property="og:image" content="http://rails.ruby.tw/images/rails_guides_cover.jpg">
<meta property="og:url" content="http://rails.ruby.tw/">
<meta property="og:type" content="article">
<meta property="og:description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)">
<link rel="stylesheet" href="stylesheets/application.css">
<link href="http://fonts.googleapis.com/css?family=Noto+Sans:400,700|Noto+Serif:700|Source+Code+Pro" rel="stylesheet">
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon">
</head>
<body class="guide">
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/zh-TW/sdk.js#xfbml=1&appId=837401439623727&version=v2.0";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<script type="text/javascript">
window.twttr=(function(d,s,id){var t,js,fjs=d.getElementsByTagName(s)[0];if(d.getElementById(id)){return}js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);return window.twttr||(t={_e:[],ready:function(f){t._e.push(f)}})}(document,"script","twitter-wjs"));
</script>
<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>Models</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>Views</dt>
<dd><a href="layouts_and_rendering.html">Rails 算繪與版型</a></dd>
<dd><a href="form_helpers.html">Action View 表單輔助方法</a></dd>
<dt>Controllers</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">Rake 任務與 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>
<dt>Rails 指南翻譯術語</dt>
<dd><a href="translation_terms.html">翻譯術語</a></dd>
</dl>
</div>
</li>
<li><a class="nav-item" href="//github.com/docrails-tw/guides">貢獻翻譯</a></li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">貢獻</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="Models">
<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="Views">
<option value="layouts_and_rendering.html">Rails 算繪與版型</option>
<option value="form_helpers.html">Action View 表單輔助方法</option>
</optgroup>
<optgroup label="Controllers">
<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">Rake 任務與 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>
<optgroup label="Rails 指南翻譯術語">
<option value="translation_terms.html">翻譯術語</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide">
<div id="feature">
<div class="wrapper">
<h2>Active Record 遷移</h2><p>Migration,遷移。Active Record 眾多功能之一,可與時俱進的<a href="http://en.wikipedia.org/wiki/Schema_migration">管理資料庫綱要</a>。最棒的是遷移提供了簡潔的 Ruby DSL,無需寫純 SQL,便能變更資料表。</p><p>讀完本篇,您將了解:</p>
<ul>
<li>使用產生遷移的產生器。</li>
<li>Active Record 提供用來操作資料庫的方法。</li>
<li>撰寫 Rake 任務來管理資料庫綱要與遷移檔案。</li>
<li>遷移與 <code>db/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="#%E7%B6%9C%E8%A6%BD">綜覽</a></li>
<li>
<a href="#%E5%BB%BA%E7%AB%8B%E9%81%B7%E7%A7%BB">建立遷移</a>
<ul>
<li><a href="#%E6%96%B0%E5%BB%BA%E7%8D%A8%E7%AB%8B%E7%9A%84%E9%81%B7%E7%A7%BB">新建獨立的遷移</a></li>
<li><a href="#model-%E7%94%A2%E7%94%9F%E5%99%A8">Model 產生器</a></li>
<li><a href="#%E5%82%B3%E5%85%A5%E9%A1%9E%E5%9E%8B%E4%BF%AE%E9%A3%BE%E7%AC%A6">傳入類型修飾符</a></li>
</ul>
</li>
<li>
<a href="#%E6%92%B0%E5%AF%AB%E9%81%B7%E7%A7%BB">撰寫遷移</a>
<ul>
<li><a href="#%E5%BB%BA%E7%AB%8B%E8%B3%87%E6%96%99%E8%A1%A8">建立資料表</a></li>
<li><a href="#%E5%BB%BA%E7%AB%8B%E9%80%A3%E6%8E%A5%E8%B3%87%E6%96%99%E8%A1%A8">建立連接資料表</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9%E8%B3%87%E6%96%99%E8%A1%A8">修改資料表</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9%E6%AC%84%E4%BD%8D">修改欄位</a></li>
<li><a href="#%E6%AC%84%E4%BD%8D%E4%BF%AE%E9%A3%BE%E7%AC%A6">欄位修飾符</a></li>
<li><a href="#%E5%A4%96%E9%8D%B5">外鍵</a></li>
<li><a href="#%E8%BC%94%E5%8A%A9%E6%96%B9%E6%B3%95%E4%B8%8D%E5%A4%A0%E7%94%A8%E6%80%8E%E9%BA%BC%E8%BE%A6">輔助方法不夠用怎麼辦</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">使用 <code>reversible</code></a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-up%E3%80%81down-%E6%96%B9%E6%B3%95">使用 <code>up</code>、<code>down</code> 方法</a></li>
<li><a href="#%E5%8F%96%E6%B6%88%E4%B9%8B%E5%89%8D%E7%9A%84%E9%81%B7%E7%A7%BB">取消之前的遷移</a></li>
</ul>
</li>
<li>
<a href="#%E5%9F%B7%E8%A1%8C%E9%81%B7%E7%A7%BB">執行遷移</a>
<ul>
<li><a href="#%E5%9B%9E%E6%BB%BE">回滾</a></li>
<li><a href="#%E8%A8%AD%E5%AE%9A%E8%B3%87%E6%96%99%E5%BA%AB">設定資料庫</a></li>
<li><a href="#%E9%87%8D%E7%BD%AE%E8%B3%87%E6%96%99%E5%BA%AB">重置資料庫</a></li>
<li><a href="#%E5%9F%B7%E8%A1%8C%E7%89%B9%E5%AE%9A%E7%9A%84%E9%81%B7%E7%A7%BB">執行特定的遷移</a></li>
<li><a href="#%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%92%B0%E5%A2%83%E4%B8%8B%E5%9F%B7%E8%A1%8C%E9%81%B7%E7%A7%BB">在不同環境下執行遷移</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9%E9%81%B7%E7%A7%BB%E5%9F%B7%E8%A1%8C%E4%B8%AD%E7%9A%84%E8%BC%B8%E5%87%BA">修改遷移執行中的輸出</a></li>
</ul>
</li>
<li><a href="#%E4%BF%AE%E6%94%B9%E7%8F%BE%E6%9C%89%E7%9A%84%E9%81%B7%E7%A7%BB">修改現有的遷移</a></li>
<li>
<a href="#%E5%B0%8E%E5%87%BA%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81">導出資料庫綱要</a>
<ul>
<li><a href="#%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81%E6%AA%94%E6%A1%88%E6%9C%89%E4%BB%80%E9%BA%BC%E7%94%A8">資料庫綱要檔案有什麼用</a></li>
<li><a href="#%E5%B0%8E%E5%87%BA%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81%E7%9A%84%E7%A8%AE%E9%A1%9E">導出資料庫綱要的種類</a></li>
<li><a href="#%E5%B0%8E%E5%87%BA%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81%E8%88%87%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86">導出資料庫綱要與版本管理</a></li>
</ul>
</li>
<li><a href="#active-record-%E8%88%87%E5%8F%83%E7%85%A7%E5%AE%8C%E6%95%B4%E6%80%A7">Active Record 與參照完整性</a></li>
<li><a href="#%E9%81%B7%E7%A7%BB%E8%88%87%E7%A8%AE%E5%AD%90%E8%B3%87%E6%96%99">遷移與種子資料</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="綜覽">1 綜覽</h3><p>遷移是一種簡單、一致、方便<a href="http://en.wikipedia.org/wiki/Schema_migration">與時俱進管理資料庫綱要</a>的方法。遷移使用 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 null: false
end
end
end
</pre>
</div>
<p><code>create_table :products do |t|</code> 新增了一張 <code>products</code> 的資料表,有 <code>name</code> (類型是字串)、<code>description</code>(類型是 text)的欄位。也會自動新增主鍵(<code>id</code>)(遷移裡看不到),所有 Active Record Model 的預設主鍵名稱都叫做 <code>id</code>。<code>timestamps</code> 宏新稱了兩個欄位, <code>created_at</code> 與 <code>updated_at</code>,Active Record 會負責處理特殊欄位(主鍵、時間戳章),無需自己處理。</p><p>注意到我們定義了一個 <code>change</code> 方法,內容填入時間往前時期望的變動。在這筆遷移執行之前,資料庫裡還沒有資料表。遷移之後資料表便建出來了。Active Record 知道如何倒回這筆遷移:若我們回滾這筆遷移,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>up</code>、<code>down</code> 來取代 <code>change</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> 形式命名:</p><p><code>YYYYMMDDHHMMSS_migration_name.rb</code>,前面的 <code>YYYYMMDDHHMMSS</code> 是 UTC 格式的時間戳章,接著是底線,底線後面是該筆遷移的名稱。遷移類別以駝峰形式命名,會對應到 <code>_migration_name</code>。舉例來說 <code>20140916204300_create_products.rb</code> 會定義出 <code>CreateProducts</code> 這樣的類別名稱。而 <code>20121027111111_add_details_to_products.rb</code> 則會定義出 <code>AddDetailsToProducts</code> 這樣的類別名稱。Rails 根據時間戳章決定執行的先後順序。若是從別的應用程式複製過來的遷移檔案,或是自己產生的遷移,要注意執行的順序。</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>在命令列輸入的遷移名稱若是 <code>AddXXXToYYY</code> 或 <code>RemoveXXXFromYYY</code>,之後接一系列的欄位名稱與類型。則會自動產生 <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>給欄位加上索引(index)也是很簡單的:</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>剛剛已經看過兩種常見的遷移命名形式:<code>AddXXXToYYY</code>、<code>RemoveXXXFromYYY</code>,還有 <code>CreateXXX</code> 這種,後面接欄位名與類型:</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>則會新建 table 及欄位:</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>Rails 產生的遷移檔案不過是個開始,可以透過編輯 <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
# 等同於
$ rails generate migration AddUserRefToProducts user:belongs_to
</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>會給 Product 資料表,產生一個 <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="model-產生器">2.2 Model 產生器</h4><p>Model 與鷹架產生器新建 Model 時,也會建立遷移。這個遷移檔案會包含建立相關資料表的步驟。若進一步告訴 Rails 所需的欄位,欄位也會加入至遷移檔案裡。舉例來說,執行:</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 null: false
end
end
end
</pre>
</div>
<p>可以接著給產生出來的遷移檔案新增欄位。</p><h4 id="傳入類型修飾符">2.3 傳入類型修飾符</h4><p>某些常用的<a href="#column-modifiers">類型修飾符</a>可以直接在命令列指定。這些修飾符在欄位類型之後指定,用大括號包起來:</p><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>
<div class="info"><p>看看產生器的說明文字來了解更多細節。</p></div><h3 id="撰寫遷移">3 撰寫遷移</h3><p>使用產生器建立出遷移檔案之後,開工的時候到了!</p><h4 id="建立資料表">3.1 建立資料表</h4><p><code>create_table</code> 是最基礎的方法之一,通常 <code>rails generate model</code> 或 <code>rails generate scaffold</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> 選項來修改主鍵的名字(記得更新對應的 Model)。或者是完全不要主鍵,可以傳入 <code>id: false</code> 選項。資料庫特定的選項,可以傳給 <code>:options</code></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><p>更多細節可查閱 <a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table">create_table</a> API。</p><h4 id="建立連接資料表">3.2 建立連接資料表</h4><p><code>create_join_table</code> 會建立一張 HABTM (<code>has_and_belongs_to_many</code>)連接表。常見的應用場景:</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: 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: 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> 資料表,一樣有 <code>category_id</code> 與 <code>product_id</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>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> 欄位重新命名為 <code>upc_code</code>。</p><h4 id="修改欄位">3.4 修改欄位</h4><p>就像 Rails 有 <code>remove_column</code> 與 <code>add_column</code>,Rails 也提供了 <code>change_column</code> 方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
change_column :products, :part_number, :text
</pre>
</div>
<p>會更改 <code>products</code> 資料表裡的 <code>part_number</code> 欄位,類型改為 <code>:text</code>。</p><p>除了 <code>change_column</code> 之外,還有 <code>change_column_null</code> 與 <code>change_column_default</code> 方法,專門用來修改欄位的預設值。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
change_column_null :products, :name, false
change_column_default :products, :approved, false
</pre>
</div>
<p>上例程式會把 <code>products</code> 資料表的 <code>:name</code> 欄位設為 <code>NOT NULL</code>;而 <code>:approved</code> 欄位預設設為 <code>false</code>。</p><div class="info"><p>和 <code>change_column</code>(以及 <code>change_column_default</code>)不同,<code>change_column_null</code>
是可逆的。</p></div><h4 id="欄位修飾符">3.5 欄位修飾符</h4><p>欄位修飾符可在新建或修改欄位時使用:</p>
<table>
<thead>
<tr>
<th style="text-align: left">修飾符</th>
<th style="text-align: left">說明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code>:limit</code></td>
<td style="text-align: left">設定 <code>string/text/binary/integer</code> 欄位的最大值。</td>
</tr>
<tr>
<td style="text-align: left"><code>:precision</code></td>
<td style="text-align: left">定義 <code>decimal</code> 欄位的精度,含小數點可以有幾個數字。</td>
</tr>
<tr>
<td style="text-align: left"><code>:scale</code></td>
<td style="text-align: left">定義 <code>decimal</code> 欄位的位數,小數點可以有幾位。</td>
</tr>
<tr>
<td style="text-align: left"><code>:polymorphic</code></td>
<td style="text-align: left">給 <code>belongs_to</code> association 加上 <code>type</code> 欄位。</td>
</tr>
<tr>
<td style="text-align: left"><code>:null</code></td>
<td style="text-align: left">欄位允不允許 <code>NULL</code> 值。</td>
</tr>
<tr>
<td style="text-align: left"><code>:default</code></td>
<td style="text-align: left">允許設定欄位的預設值。注意,若使用了動態數值(譬如日期),預設值只會在第一次跑遷移時做計算(也就是跑遷移當下的日期)。</td>
</tr>
<tr>
<td style="text-align: left"><code>:index</code></td>
<td style="text-align: left">給欄位加入索引。</td>
</tr>
</tbody>
</table>
<p>某些連接器可能支援其他選項,請參考與連接器相關的 API 文件來了解更多資訊。</p><h4 id="外鍵">3.6 外鍵</h4><p>雖然不是必須的,但可能會想加入外鍵約束來保證<a href="#active-record-%E8%88%87%E5%8F%83%E7%85%A7%E5%AE%8C%E6%95%B4%E6%80%A7">參照的完整性</a>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
add_foreign_key :articles, :authors
</pre>
</div>
<p>上例會給 <code>articles</code> 資料表新增外鍵欄位:<code>author_id</code>。外鍵會使用 <code>articles</code> 資料表的主鍵 <code>id</code> 作為參照。若參照的欄位名稱不能從資料表名稱推測出來,可以使用 <code>:column</code> 與 <code>primary_key</code> 選項。</p><p>Rails 會給每個外鍵產生一個名字,由 <code>fk_rails_</code> 前綴加上 10 個隨機字元。可以用 <code>:name</code> 選項來修改。</p><div class="note"><p>Active Record 只支援單欄外鍵。需要用組合外鍵請使用 <code>execute</code> 與 <code>structure.sql</code>。</p></div><p>移除外鍵也很簡單:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# let Active Record figure out the column name
remove_foreign_key :accounts, :branches
# remove foreign key for a specific column
remove_foreign_key :accounts, column: :owner_id
# remove foreign key by name
remove_foreign_key :accounts, name: :special_fk_name
</pre>
</div>
<h4 id="輔助方法不夠用怎麼辦">3.7 輔助方法不夠用怎麼辦</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><p><a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html"><code>ActiveRecord::ConnectionAdapters::SchemaStatements</code></a>
(在 <code>change</code>, <code>up</code> and <code>down</code> 裡可用的方法有那些)</p><p><a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html"><code>ActiveRecord::ConnectionAdapters::TableDefinition</code></a>
(傳入 <code>create_table</code> 區塊物件可用的方法有那些)</p><p><a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html"><code>ActiveRecord::ConnectionAdapters::Table</code></a>
(傳入 <code>change_table</code> 區塊物件可用的方法有那些)</p><h4 id="使用-change-方法">3.8 使用 <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> (must supply a block)</li>
<li>
<code>drop_join_table</code> (must supply a block)</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_table</code> 也是可逆的,只要傳給 <code>change_table</code> 的區塊沒有呼叫 <code>change</code>、<code>change_default</code> 或是 <code>remove</code> 即可。</p><p>如果想使用其它的方法,可以使用 <code>reversible</code> 或是撰寫 <code>up</code>、<code>down</code> 方法,而不是使用 <code>change</code>。</p><h4 id="使用-reversible">3.9 使用 <code>reversible</code>
</h4><p>需要處理 Active Record 不知道怎麼變回來的複雜遷移時,可以使用 <code>reversible</code> 方法來指定遷移時要做什麼(<code>up</code>),回滾時要做什麼(<code>down</code>),比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ExampleMigration < ActiveRecord::Migration
def change
create_table :distributors do |t|
t.string :zipcode
end
reversible do |dir|
dir.up do
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5) NO INHERIT;
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
end
</pre>
</div>
<p>使用 <code>reversible</code> 會確保執行順序的正確性。上例的遷移取消時(回滾),<code>down</code> 區塊會在 <code>home_page_url</code> 欄位移除前,以及 <code>distributors</code> 資料表刪除前,執行 <code>down</code> 區塊裡的內容。</p><p>有時候遷移做了怎麼樣都不可逆的操作,比如,可能是刪除資料。這種情況下,Active Record 會在試著取消遷移時,拋出一個 <code>ActiveRecord::IrreversibleMigration</code>,表示無法恢復先前的操作。</p><h4 id="使用-up、down-方法">3.10 使用 <code>up</code>、<code>down</code> 方法</h4><p>可以不用 <code>change</code> 來撰寫遷移,而使用經典的 <code>up</code>、<code>down</code> 寫法。</p><p><code>up</code> 撰寫對資料庫綱要的變化(遷移)、<code>down</code> 撰寫取消 <code>up</code> 操作的操作(回滾)。兩個操作要可以互相抵消。舉例來說,<code>up</code> 建了一張資料表,則 <code>down</code> 便要 <code>drop</code> 該張資料表。取消遷移時最好依照遷移時的反序執行。上例使用 <code>reversible</code> 可以用 <code>up</code>+<code>down</code> 改寫:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ExampleMigration < ActiveRecord::Migration
def up
create_table :distributors do |t|
t.string :zipcode
end
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
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 distributors
DROP CONSTRAINT zipchk
SQL
drop_table :distributors
end
end
</pre>
</div>
<p>如果遷移是不可逆的操作,要在 <code>down</code> 拋出一個 <code>ActiveRecord::IrreversibleMigration</code>。這樣子別的開發者試圖要取消遷移時,便會顯示這個遷移無法取消的錯誤訊息。</p><h4 id="取消之前的遷移">3.11 取消之前的遷移</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>ExampleMigration</code> 已經遷移了,之後覺得還是用 Active Record 的驗證,在 <code>CHECK</code> 約束條件的地方來驗證郵遞區號。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration
def change
revert do
# copy-pasted code from ExampleMigration
reversible do |dir|
dir.up do
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
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>第一個相關會用到的 Rake 任務是 <code>rake db:migrate</code>。<code>rake db:migrate</code> 任務最簡單的形式,不過是尚未執行的遷移裡面的 <code>change</code> 或 <code>up</code> 方法。若所有的遷移都執行完畢了便離開,否則按照時間戳的順序進行遷移。</p><p>有點要注意的是,執行 <code>db:migrate</code> 也會執行 <code>db:schema:dump</code>,會更新 <code>db/schema.rb</code> 來反映出當下的資料庫結構。</p><p>如果指定了目標版本,Active Record 會執行目標版本之前所有的遷移。目標版本的名稱是遷移名前綴的 UTC 時間戳章,比如 <code>20080906120000</code>:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate VERSION=20080906120000
</pre>
</div>
<p>若版本 <code>20080906120000</code> 大於目前版本,則會執行 <code>change</code>(或 <code>up</code>)方法,遷移到 <code>20080906120000</code>(包含)。若版本 <code>20080906120000</code> 小於目前版本,則會對版本小於 <code>20080906120000</code> (不包含)的遷移執行 <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>會回滾前 3 次遷移。</p><p><code>db:migrate:redo</code> 用來回滾、接著再遷移一次。同樣接受 <code>STEP</code> 參數,比如往前回滾 3 次,再遷移:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake db:migrate:redo STEP=3
</pre>
</div>
<p>這些操作用 <code>db:migrate</code> 都辦得到,只是方便你使用而已,因為不用特別指定要遷移或是回滾的版本號。</p><h4 id="設定資料庫">4.2 設定資料庫</h4><p>The <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>schema.rb</code> 裡的內容來操作。如果遷移不能回滾,<code>rake db:reset</code> 也是派不上用場的。了解更多請參考<a href="#%E5%B0%8E%E5%87%BA%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81">導出資料庫綱要</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>會執行版本大於 <code>20080906120000</code> 的遷移裡面的 <code>change</code>、<code>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 style="text-align: left">方法</th>
<th style="text-align: left">目的</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">suppress_messages</td>
<td style="text-align: left">接受區塊作為參數,區塊內指名的代碼不會產生輸出。</td>
</tr>
<tr>
<td style="text-align: left">say</td>
<td style="text-align: left">接受一個訊息字串,並輸出該字串。第二個參數可以用來指定要不要縮排。</td>
</tr>
<tr>
<td style="text-align: left">say_with_time</td>
<td style="text-align: left">同上,但會附上區塊的執行時間。若區塊返回整數,會假定該整數是受影響列的數量。</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 null: false
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:rollback</code>,編輯遷移檔案,接著再次執行 <code>rake db:migrate</code>。</p><p>通常不太推薦修改現有的遷移,因為會增加同事更多工作量。尤其是遷移已經上線了(production),應該要寫個新的遷移,執行需要的修改,來達成想完成的事情。修改新建的遷移(尚未提交至版本管理)是相對無害的。</p><p><code>revert</code> 方法用來寫新的遷移,取消先前的遷移的場景很有用。參考<a href="#%E5%8F%96%E6%B6%88%E5%89%8D%E6%AC%A1%E9%81%B7%E7%A7%BB">取消之前的遷移</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> 與 SQL 是用來表示資料庫目前的狀態,兩個檔案不可以修改。</p><p>依靠重新執行所有的遷移,來部署新的應用程式,不可靠又容易出錯。最簡單的辦法是把資料庫的結構檔案,加載到資料庫裡。</p><p>舉例來說,這便是測試資料庫如何產生的過程:導出目前的開發資料庫(導出成 <code>db/schema.rb</code> 或 <code>db/structure.sql</code>),接著載入至測試資料庫。</p><p>若想了解 Active Record object 有什麼屬性。Model 裡沒有寫,屬性散佈在多個遷移檔案裡,但所有的屬性都總結在資料庫綱要檔案了。如果想要在 Model 裡看到所有的屬性資訊,有一個 <a href="https://github.com/ctran/annotate_models">annotate_models</a> RubyGem,自動在 Model 檔案最上方加註解,使用資料庫綱要檔案,來記錄每個 Model 有的屬性。</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> 不能表達資料庫特有的功能,像是觸發器(triggers)、或是儲存過程(stored procedure)。但在遷移裡可以執行任何自訂的 SQL 語句,但資料庫綱要的導出程式,無法從資料庫重建出這些 SQL 語句。若要執行自訂的 SQL,記得將資料庫綱要的導出格式設定為 <code>:sql</code>。</p><p>與其使用 Active Record 提供的資料庫綱要導出程式,可以用特定資料庫的導出工具(透過 <code>db:structure:dump</code> 任務來導出 <code>db/structure.sql</code>)。舉例來說,PostgreSQL 使用 <code>pg_dump</code> 這個工具來導出 SQL。而 MySQL 呢,資料庫綱要只不過是多張資料表的 <code>SHOW CREATE TABLE</code> 的結果。</p><p>載入這些 <code>:sql</code> 格式的綱要檔案,不過是執行裡面的 SQL 語句而已。定義上來說,會建立一份資料庫結構的完美複本。但使用 <code>:sql</code> 綱要格式的話,便不能從一種 RDBMS 資料庫,切換到另一種 RDBMS 資料庫了。</p><h4 id="導出資料庫綱要與版本管理">6.3 導出資料庫綱要與版本管理</h4><p>因為導出的資料庫綱要檔案,是資料庫結構最權威的來源,強烈建議將資料庫綱要檔案加到版本管理裡。</p><h3 id="active-record-與參照完整性">7 Active Record 與參照完整性</h3><p>Active Record 認為事情要在 model 裡處理好,而不是在資料庫。也是因為這個原因,需要在資料庫實作的功能,不常使用像是觸發器或約束條件。</p><p>像 <code>validates :foreign_key, uniqueness: true</code> 這樣的驗證,是 Model 可以增強資料整合性的一種方法。<code>:dependent</code> 選項讓 Model 可以自動刪除關聯的資料。某些人認為像是這種操作,以及所有在應用程式層級執行的操作,無法保證參照的完整性,要跟<a href="#%E5%A4%96%E9%8D%B5">外鍵約束</a>一樣,放在資料庫解決才是。</p><p>雖然 Active Record 沒有直接提供任何工具來解決這件事,但可以用 <code>execute</code> 方法來執行任何的 SQL 語句。</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 有 “seeds” 這個功能,應該這麼用這個來給資料庫新增初始資料才對。用起來非常簡單,在 <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/docrails-tw/guides/fork">Fork</a> 修正,或至此處<a href="https://github.com/docsrails-tw/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/deed.zh_TW">創用 CC 姓名標示-相同方式分享 4.0 國際授權條款</a>授權。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 為 David Heinemeier Hansson 的商標。版權所有。</p>
</div>
</div>
<script src="javascripts/jquery.min.js"></script>
<script src="javascripts/responsive-tables.js"></script>
<script src="javascripts/guides.js"></script>
<script src="javascripts/syntaxhighlighter/shCore.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script 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", "UA-49903900-1", "auto");
ga("require", "displayfeatures");
ga("send", "pageview");
</script>
</body>
</html>