-
Notifications
You must be signed in to change notification settings - Fork 0
/
association_basics.html
2117 lines (1894 loc) · 117 KB
/
association_basics.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
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!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>本篇介紹 Active Record 的關聯功能。</p><p>讀完本篇,您將了解:</p>
<ul>
<li>如何宣告 Active Record Model 之間的關聯。</li>
<li>如何理解 Active Record 的各種關聯。</li>
<li>如何使用關聯加入的方法。</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E7%82%BA%E4%BB%80%E9%BA%BC%E9%9C%80%E8%A6%81%E9%97%9C%E8%81%AF%EF%BC%9F">為什麼需要關聯?</a></li>
<li>
<a href="#%E9%97%9C%E8%81%AF%E7%A8%AE%E9%A1%9E">關聯種類</a>
<ul>
<li><a href="#belongs-to-%E9%97%9C%E8%81%AF"><code>belongs_to</code> 關聯</a></li>
<li><a href="#has-one-%E9%97%9C%E8%81%AF"><code>has_one</code> 關聯</a></li>
<li><a href="#has-many-%E9%97%9C%E8%81%AF"><code>has_many</code> 關聯</a></li>
<li><a href="#has-many-through-%E9%97%9C%E8%81%AF"><code>has_many :through</code> 關聯</a></li>
<li><a href="#has-one-through-%E9%97%9C%E8%81%AF"><code>has_one :through</code> 關聯</a></li>
<li><a href="#has-and-belongs-to-many-%E9%97%9C%E8%81%AF"><code>has_and_belongs_to_many</code> 關聯</a></li>
<li><a href="#belongs-to-%E8%88%87-has-one-%E7%9A%84%E6%87%89%E7%94%A8%E5%A0%B4%E6%99%AF"><code>belongs_to</code> 與 <code>has_one</code> 的應用場景</a></li>
<li><a href="#has-many-through-%E8%88%87-has-and-belongs-to-many-%E7%9A%84%E6%87%89%E7%94%A8%E5%A0%B4%E6%99%AF"><code>has_many :through</code> 與 <code>has_and_belongs_to_many</code> 的應用場景</a></li>
<li><a href="#%E5%A4%9A%E5%9E%8B%E9%97%9C%E8%81%AF">多型關聯</a></li>
<li><a href="#%E8%87%AA%E9%80%A3%E6%8E%A5">自連接</a></li>
</ul>
</li>
<li>
<a href="#%E7%A7%98%E8%A8%A3%E3%80%81%E6%8A%80%E5%B7%A7%E8%88%87%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85">秘訣、技巧與注意事項</a>
<ul>
<li><a href="#%E6%8E%A7%E5%88%B6%E5%BF%AB%E5%8F%96">控制快取</a></li>
<li><a href="#%E9%81%BF%E5%85%8D%E5%91%BD%E5%90%8D%E8%A1%9D%E7%AA%81">避免命名衝突</a></li>
<li><a href="#%E6%9B%B4%E6%96%B0%E8%B3%87%E6%96%99%E5%BA%AB%E7%B6%B1%E8%A6%81">更新資料庫綱要</a></li>
<li><a href="#%E6%8E%A7%E5%88%B6%E9%97%9C%E8%81%AF%E4%BD%9C%E7%94%A8%E5%9F%9F">控制關聯作用域</a></li>
<li><a href="#%E9%9B%99%E5%90%91%E9%97%9C%E8%81%AF">雙向關聯</a></li>
</ul>
</li>
<li>
<a href="#%E9%97%9C%E8%81%AF%E5%AE%8C%E6%95%B4%E5%8F%83%E8%80%83%E6%89%8B%E5%86%8A">關聯完整參考手冊</a>
<ul>
<li><a href="#belongs-to-%E9%97%9C%E8%81%AF%E5%8F%83%E8%80%83%E6%89%8B%E5%86%8A"><code>belongs_to</code> 關聯參考手冊</a></li>
<li><a href="#has-one-%E9%97%9C%E8%81%AF%E5%8F%83%E8%80%83%E6%89%8B%E5%86%8A"><code>has_one</code> 關聯參考手冊</a></li>
<li><a href="#has-many-%E9%97%9C%E8%81%AF%E5%8F%83%E8%80%83%E6%89%8B%E5%86%8A"><code>has_many</code> 關聯參考手冊</a></li>
<li><a href="#has-and-belongs-to-many-%E9%97%9C%E8%81%AF%E5%8F%83%E8%80%83%E6%89%8B%E5%86%8A"><code>has_and_belongs_to_many</code> 關聯參考手冊</a></li>
<li><a href="#%E9%97%9C%E8%81%AF%E5%9B%9E%E5%91%BC">關聯回呼</a></li>
<li><a href="#%E6%93%B4%E5%85%85%E9%97%9C%E8%81%AF">擴充關聯</a></li>
</ul>
</li>
<li><a href="#%E5%96%AE%E8%A1%A8%E7%B9%BC%E6%89%BF">單表繼承</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="為什麼需要關聯?">1 為什麼需要關聯?</h3><p>為什麼 Model 之間要有關聯?關聯簡化了常見的操作,程式碼撰寫起來更簡單。比如,一個簡單的 Rails 應用程式,有顧客與訂單 Model。每個顧客可以有多筆訂單。若沒有關聯功能,則 Model 看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Customer < ActiveRecord::Base
end
class Order < ActiveRecord::Base
end
</pre>
</div>
<p>為顧客新增訂單:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@order = Order.create(order_date: Time.now, customer_id: @customer.id)
</pre>
</div>
<p>刪除顧客以及顧客的所有訂單:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@orders = Order.where(customer_id: @customer.id)
@orders.each do |order|
order.destroy
end
@customer.destroy
</pre>
</div>
<p>有了 Active Record 關聯,可以告訴 Rails Model 之間的關聯,來精簡上例。以下是簡化後的程式碼:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Customer < ActiveRecord::Base
has_many :orders, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
</pre>
</div>
<p>改寫成這樣後,給顧客建新訂單變得簡單許多:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@order = @customer.orders.create(order_date: Time.now)
</pre>
</div>
<p>刪除顧客以及顧客的所有訂單簡單多了:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@customer.destroy
</pre>
</div>
<p>要了解各種關聯的用途,請閱讀下一節。下一節介紹關聯種類、各種關聯的秘訣與小技巧。本篇最後一節是 Rails 關聯的可用選項與方法的完整參考手冊。</p><h3 id="關聯種類">2 關聯種類</h3><p>在 Rails 的世界裡,<strong>關聯</strong>連結了兩個 Active Record Model。關聯使用宏風格(macro-style)的語法來呼叫,以宣告的形式來加入功能到 Model。舉例來說,透過宣告一個 Model 屬於另一個,來告訴 Rails 如何維護兩者之間的主外鍵,同時獲得許多實用的方法。Rails 支援以下六種關聯:</p>
<ul>
<li><code>belongs_to</code></li>
<li><code>has_one</code></li>
<li><code>has_many</code></li>
<li><code>has_many :through</code></li>
<li><code>has_one :through</code></li>
<li><code>has_and_belongs_to_many</code></li>
</ul>
<p>本篇之後細講如何使用各種關聯,首先介紹各種關聯的應用場景。</p><h4 id="belongs-to-關聯">2.1 <code>belongs_to</code> 關聯</h4><p><code>belongs_to</code> 關聯建立兩個 Model 之間的一對一關係。<code>belongs_to</code> 關聯宣告一個 Model 實體,屬於另一個 Model 實體。舉例來說,應用程式有顧客與訂單兩個 Model,每筆訂單只屬於一位顧客,訂單 Model 便如此宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer
end
</pre>
</div>
<p><img src="images/belongs_to.png" alt="belongs_to Association Diagram"></p><div class="note"><p><code>belongs_to</code> 宣告<strong>必須</strong>使用單數形式。上例若使用複數形式,會報 <code>"uninitialized constant Order::Customers"</code> 錯誤。這是因為 Rails 使用關聯名稱來推出類別名稱。關聯名稱錯用複數,推斷出來的類別名稱自然也錯了。</p></div><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateOrders < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
t.timestamps null: false
end
create_table :orders do |t|
t.belongs_to :customer, index: true
t.datetime :order_date
t.timestamps null: false
end
end
end
</pre>
</div>
<h4 id="has-one-關聯">2.2 <code>has_one</code> 關聯</h4><p><code>has_one</code> 關聯建立兩個 Model 之間的一對一關係,但語義和結果與 <code>belongs_to</code> 不同。<code>has_one</code> 關聯宣告一個 Model 實體,含有(或持有)另一個 Model 實體。舉例來說,每個供應商在應用程式裡只有一個帳號,供應商 Model 便如此宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Supplier < ActiveRecord::Base
has_one :account
end
</pre>
</div>
<p><img src="images/has_one.png" alt="has_one Association Diagram"></p><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps null: false
end
end
end
</pre>
</div>
<h4 id="has-many-關聯">2.3 <code>has_many</code> 關聯</h4><p><code>has_many</code> 關聯建立兩個 Model 之間的一對多關係。通常 <code>has_many</code> 另一邊對應的是 <code>belongs_to</code> 關聯。<code>has_many</code> 關聯宣告一個 Model 實體,有零個或多個另一個 Model 實體。舉例來說,應用程式有顧客與訂單兩個 Model,顧客可有多筆訂單,訂單 Model 便如此宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Customer < ActiveRecord::Base
has_many :orders
end
</pre>
</div>
<div class="note"><p>宣告 <code>has_many</code> 關聯名稱採<strong>複數</strong>。</p></div><p><img src="images/has_many.png" alt="has_many Association Diagram"></p><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
t.timestamps null: false
end
create_table :orders do |t|
t.belongs_to :customer, index: true
t.datetime :order_date
t.timestamps null: false
end
end
end
</pre>
</div>
<h4 id="has-many-through-關聯">2.4 <code>has_many :through</code> 關聯</h4><p><code>has_many :through</code> 關聯通常用來建立兩個 Model 之間的多對多關係。<code>has_many :through</code> 關聯<strong>透過(through)</strong>第三個 Model,宣告一個 Model 實體,可有零個或多個另一個 Model 實體。舉個醫療的例子,“病患”需要<strong>透過</strong>“預約”來見“物理治療師”。相對應的宣告如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, through: :appointments
end
</pre>
</div>
<p><img src="images/has_many_through.png" alt="has_many :through Association Diagram"></p><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateAppointments < ActiveRecord::Migration
def change
create_table :physicians do |t|
t.string :name
t.timestamps null: false
end
create_table :patients do |t|
t.string :name
t.timestamps null: false
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps null: false
end
end
end
</pre>
</div>
<p>連接 Model(Join Model)的集合可以用 API 關聯。比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
physician.patients = patients
</pre>
</div>
<p>會為新建立的關聯物件建立連接 Model,如果刪除了其中一個物件,也會刪除對應的資料庫記錄。</p><div class="warning"><p>連接 Model 會自動刪除、直接執行,不會觸發任何 <code>destroy</code> 回呼。</p></div><p><code>has_many :through</code> 關聯在簡化嵌套的 <code>has_many</code> 關聯很有用。比如文件有多個章節、段落。想要簡單地從文件取得所有段落,可以這麼寫:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Document < ActiveRecord::Base
has_many :sections
has_many :paragraphs, through: :sections
end
class Section < ActiveRecord::Base
belongs_to :document
has_many :paragraphs
end
class Paragraph < ActiveRecord::Base
belongs_to :section
end
</pre>
</div>
<p>指定了 <code>has_many :paragraphs, through: :sections</code> 之後,Rails 便懂得如何透過章節,從文件中取得段落:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@document.paragraphs
</pre>
</div>
<h4 id="has-one-through-關聯">2.5 <code>has_one :through</code> 關聯</h4><p><code>has_one :through</code> 關聯建立兩個 Model 之間的一對一關係。<code>has_one :through</code> 關聯<strong>透過(through)</strong>第三個 Model,宣告一個 Model 實體,可有另一個 Model 實體。舉例來說,供應商有一個帳號,每個帳號有帳號歷史,相對應的宣告如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, through: :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
</pre>
</div>
<p><img src="images/has_one_through.png" alt="has_one :through Association Diagram"></p><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateAccountHistories < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps null: false
end
create_table :account_histories do |t|
t.belongs_to :account, index: true
t.integer :credit_rating
t.timestamps null: false
end
end
end
</pre>
</div>
<h4 id="has-and-belongs-to-many-關聯">2.6 <code>has_and_belongs_to_many</code> 關聯</h4><p><code>has_and_belongs_to_many</code> 關聯建立兩個 Model 之間,<strong>直接的</strong>多對多關係。舉例來說,應用程式有組件(Assembly),組件下有部件(Part),可以如此宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
</pre>
</div>
<p><img src="images/habtm.png" alt="has_and_belongs_to_many Association Diagram"></p><p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateAssembliesAndParts < ActiveRecord::Migration
def change
create_table :assemblies do |t|
t.string :name
t.timestamps null: false
end
create_table :parts do |t|
t.string :part_number
t.timestamps null: false
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly, index: true
t.belongs_to :part, index: true
end
end
end
</pre>
</div>
<h4 id="belongs-to-與-has-one-的應用場景">2.7 <code>belongs_to</code> 與 <code>has_one</code> 的應用場景</h4><p>如果想建立兩個 Model 之間的一對一關係,一邊宣告 <code>belongs_to</code>,另一邊宣告 <code>has_one</code>。怎麼知道那個要寫那個?</p><p>差異在於外鍵放在那個 Model(<strong>外鍵放在宣告 <code>belongs_to</code> 的關聯的資料表</strong>)。但應該要考慮實際的語義。比如 <code>has_one</code> 關聯表示某物屬於你,也就是供應商有一個帳號,比帳號擁有供應商合理。所以正確的關聯應這麼宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Supplier < ActiveRecord::Base
end
class Account < ActiveRecord::Base
belongs_to :supplier
end
</pre>
</div>
<p>上例對應的遷移看起來會像是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
t.timestamps null: false
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
t.timestamps null: false
end
add_index :accounts, :supplier_id
end
end
</pre>
</div>
<div class="note"><p>使用 <code>t.integer :supplier_id</code> 讓外鍵看起來更明確。這種寫法可以使用 <code>t.references :supplier</code> 抽象掉實作細節。</p></div><h4 id="has-many-through-與-has-and-belongs-to-many-的應用場景">2.8 <code>has_many :through</code> 與 <code>has_and_belongs_to_many</code> 的應用場景</h4><p>Rails 提供兩種方式來宣告多對多關係。簡單的方法是使用 <code>has_and_belongs_to_many</code> 來直接建立多對多關聯:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
</pre>
</div>
<p>第二種建立多對多關係的方式是使用 <code>has_many :through</code>。這透過連接的 Model,間接建立出多對多關聯:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, through: :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, through: :manifests
end
</pre>
</div>
<p>最簡單的經驗法則表示,當多對多關係中間的 Model 要獨立使用時,使用 <code>has_many :through</code>;不需要對多對多關係中間的 Model 做任何事時,保持簡單使用 <code>has_and_belongs_to_many</code>(但要記得在資料庫建立連接的資料表)。</p><p>若是連接的資料表需要驗證、回呼或其他屬性時,使用 <code>has_many :through</code>。</p><h4 id="多型關聯">2.9 多型關聯</h4><p>一種更進階的關聯用法是<strong>多型關聯</strong>。使用多型關聯,單個關聯裡,Model 可屬於多個 Model。舉例來說,圖片 Model 可屬於員工或產品 Model。相對應的宣告如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Employee < ActiveRecord::Base
has_many :pictures, as: :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, as: :imageable
end
</pre>
</div>
<p>可以把多型的 <code>belongs_to</code> 宣告想成是一個介面,任何 Model 皆可使用的介面。在 <code>Employee</code> Model,可以透過 <code>@employee.pictures</code> 來取出所有圖片。同樣的,在 <code>Product</code> Model 亦然:<code>@product.pictures</code>。</p><p>如果有一個 <code>Picture</code> Model 的實體,可以使用 <code>@picture.imageable</code> 看擁有這張圖片的是誰(父物件)。但首先需要先在遷移裡,加入外鍵(<code>*_id</code>)與類型(<code>*_type</code>)欄位。<code>*_type</code> 類型欄位用來宣告此 Model 擁有多型介面:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
t.timestamps null: false
end
add_index :pictures, :imageable_id
end
end
</pre>
</div>
<p>上例遷移可用 <code>t.references</code> 形式簡化:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
t.timestamps null: false
end
end
end
</pre>
</div>
<p><img src="images/polymorphic.png" alt="Polymorphic Association Diagram"></p><h4 id="自連接">2.10 自連接</h4><p>在設計資料 Model 時會發現,有時會需要自己與自己有關係的 Model。舉例來說,可能會想把員工資料通通存在一張資料表,但又要能夠追蹤像是經理或下屬之間的關係。這種情況可以使用自連接(Self join)關聯:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee"
end
</pre>
</div>
<p>這麼設定好後,可以使用 <code>@employee.subordinates</code> 與 <code>@employee.manager</code> 來取出經理與下屬。</p><p>在遷移裡則是需要加入參照自己的欄位:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.references :manager
t.timestamps null: false
end
end
end
</pre>
</div>
<h3 id="秘訣、技巧與注意事項">3 秘訣、技巧與注意事項</h3><p>以下是在 Rails 裡有效使用 Active Record 關聯所需要知道的二三事:</p>
<ul>
<li>控制快取</li>
<li>避免命名衝突</li>
<li>更新資料庫綱要</li>
<li>控制關聯作用域</li>
<li>雙向關聯</li>
</ul>
<h4 id="控制快取">3.1 控制快取</h4><p>所有關聯新增的方法皆圍繞著快取打轉。這些方法會保留最近的查詢結果,供之後的查詢使用。快取甚至可在方法之間共享,比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
customer.orders # 從資料庫取出訂單,快取之。
customer.orders.size # 使用快取的訂單查詢數量
customer.orders.empty? # 使用快取的訂單檢查是否為空
</pre>
</div>
<p>但要是應用程式某部分更新了資料,想重載快取呢?呼叫關聯方法時傳入 <code>true</code> 即可:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
customer.orders # 從資料庫取出訂單,快取。
customer.orders.size # 使用快取的訂單查詢數量
customer.orders(true).empty? # 捨棄快取的訂單,重新去資料庫取出訂單,檢查是否為空。
</pre>
</div>
<h4 id="避免命名衝突">3.2 避免命名衝突</h4><p>關聯名稱不可隨意使用。因為在建立關聯時,會新增與關聯名稱相同的方法。若是關聯名稱與 <code>ActiveRecord::Base</code> 的實體方法相同時,關聯新增的方法會覆蓋掉 <code>ActiveRecord::Base</code> 的實體方法。比如 <code>attributes</code> 或 <code>connection</code> 是不好的關聯名稱。</p><h4 id="更新資料庫綱要">3.3 更新資料庫綱要</h4><p>關聯非常非常有用,但沒什麼神奇的。為關聯維護對應的資料庫綱要是開發者的責任。不同關聯需要做的事不同。對於 <code>belongs_to</code> 關聯來說,需要建立外鍵;對於 <code>has_and_belongs_to_many</code> 則需要建立適當的連接資料表。</p><h5 id="為-belongs-to-關聯建立外鍵">3.3.1 為 <code>belongs_to</code> 關聯建立外鍵</h5><p>當宣告了 <code>belongs_to</code> 關聯時,需要建立外鍵。看看下面這個 Model:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer
end
</pre>
</div>
<p>仍需要在訂單資料表,建立適當的外鍵才有效:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.datetime :order_date
t.string :order_number
t.integer :customer_id
end
add_index :orders, :customer_id
end
end
</pre>
</div>
<p>若在建立 Model 之後才宣告關聯,記得使用 <code>add_column</code> 遷移,來提供所需的外鍵。</p><h5 id="為-has-and-belongs-to-many-關聯建立連接資料表">3.3.2 為 <code>has_and_belongs_to_many</code> 關聯建立連接資料表</h5><p>如果建立了 <code>has_and_belongs_to_many</code> 關聯,需要明確的建一張連接表。除非資料表已在 <code>:join_table</code> 選項中指定,否則 Active Record 會以關聯的類別名稱,依照詞法先後順序來命名這張連接資料表。假設有 <code>Customer</code> 與 <code>Order</code> Model ,則預設的連接表名稱是 <code>customers_orders</code>,因為在詞法順序當中,<code>c</code> 的地位高於 <code>o</code>。</p><div class="warning"><p>Model 名稱的優先順序使用 <code>String</code> 的 <code><</code> 來計算。若字串不一樣長,比較最短長度時,兩個字串是相等的。但長字串詞法地位高於短字串。舉例來說,你可能認為 <code>paper_boxes</code> 與 <code>papers</code> 這兩個資料表產生的連接表名稱是 <code>papers_paper_boxes</code>,因為 <code>paper_boxes</code> 比 <code>papers</code> 長。但實際上是 <code>paper_boxes_papers</code>,因為在常見的編碼裡,<code>_</code> 的詞法地位高於 <code>s</code>。</p></div><p>不論名稱為何,必須要在適當的遷移中,手動產生連接表。考慮下面的關聯範例:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
</pre>
</div>
<p>關聯要有效,還需寫一個遷移來建立 <code>assemblies_parts</code> 資料表。並且此表無主鍵:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
t.integer :part_id
end
add_index :assemblies_parts, :assembly_id
add_index :assemblies_parts, :part_id
end
end
</pre>
</div>
<p><code>create_table</code> 傳入 <code>id: false</code> 是因為,資料表無需表示一個 Model。這張資料表只是為了讓關聯可以正常工作。如果發現 <code>has_and_belongs_to_many</code> 關聯,出現任何奇怪的行為,像是 ID 錯位、ID 衝突,很可能就是因為忘記去掉主鍵。</p><h4 id="控制關聯作用域">3.4 控制關聯作用域</h4><p>預設關聯只會在目前模組的作用域裡尋找物件。這在模組裡宣告 Active Record Model 時很重要,比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
end
end
end
</pre>
</div>
<p>這沒什麼問題,因為 <code>Supplier</code> 與 <code>Account</code> 在相同的作用域裡定義。但以下不會正常工作,因為 <code>Supplier</code> 與 <code>Account</code> 定義在不同的作用域裡。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account
end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :supplier
end
end
end
</pre>
</div>
<p>要將不同命名空間下的 Model 關聯起來,可以在宣告關聯時,指定完整的類別名稱:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account,
class_name: "MyApplication::Billing::Account"
end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :supplier,
class_name: "MyApplication::Business::Supplier"
end
end
end
</pre>
</div>
<h4 id="雙向關聯">3.5 雙向關聯</h4><p>關聯兩邊都可以工作是很常見的需求,這需要在兩邊都宣告:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Customer < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :customer
end
</pre>
</div>
<p>Active Record 預設不知道這些關聯的連結關係。這可能會導致複製一個物件的不同步:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
c = Customer.first
o = c.orders.first
c.first_name == o.customer.first_name # => true
c.first_name = 'Manny'
c.first_name == o.customer.first_name # => false
</pre>
</div>
<p>之所以會這樣的原因是,<code>c</code> 與 <code>o.customer</code> 在記憶體裡是表示相同資料的兩種表示,改了一個不會自動改另一個。Active Record 提供了 <code>inverse_of</code> 選項,用來通知 Rails 關聯之間的關係:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Customer < ActiveRecord::Base
has_many :orders, inverse_of: :customer
end
class Order < ActiveRecord::Base
belongs_to :customer, inverse_of: :orders
end
</pre>
</div>
<p>加上了 <code>inverse_of</code> 後,Active Record 只會載入一個顧客物件,除了避免資料的不一致,還能提高應用程式的效率:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
c = Customer.first
o = c.orders.first
c.first_name == o.customer.first_name # => true
c.first_name = 'Manny'
c.first_name == o.customer.first_name # => true
</pre>
</div>
<p><code>inverse_of</code> 有幾點限制:</p>
<ul>
<li>不能與 <code>:through</code> 關聯同時使用。</li>
<li>不能與 <code>:polymorphic</code> 關聯同時使用。</li>
<li>不能與 <code>:as</code> 選項同時使用。</li>
<li>對 <code>belongs_to</code> 關聯,會忽略 <code>has_many</code> 所設定的 <code>inverse_of</code>。</li>
</ul>
<p>每種關聯皆會試著自動找到對應的關聯,並根據關聯名稱來合理地設定 <code>:inverse_of</code> 選項。多數使用標準名稱的關聯都會自動設定。但使用了以下選項的關聯,則無法自動設定:</p>
<ul>
<li><code>:conditions</code></li>
<li><code>:through</code></li>
<li><code>:polymorphic</code></li>
<li><code>:foreign_key</code></li>
</ul>
<h3 id="關聯完整參考手冊">4 關聯完整參考手冊</h3><p>以下小節將完整給出每種關聯的細節,關聯新增的方法、宣告時可用的選項。</p><h4 id="belongs-to-關聯參考手冊">4.1 <code>belongs_to</code> 關聯參考手冊</h4><p><code>belongs_to</code> 關聯建立兩個 Model 之間的一對一關係。用資料庫的術語解釋,宣告 <code>belongs_to</code> 的這個類別有外鍵。若外鍵在另個類別,則應該使用 <code>has_one</code> 才是。</p><h5 id="belongs-to-關聯新增的方法">4.1.1 <code>belongs_to</code> 關聯新增的方法</h5><p>宣告 <code>belongs_to</code> 關聯時,宣告的類別獲得五個關聯方法:</p>
<ul>
<li><code>association(force_reload = false)</code></li>
<li><code>association=(associate)</code></li>
<li><code>build_association(attributes = {})</code></li>
<li><code>create_association(attributes = {})</code></li>
<li><code>create_association!(attributes = {})</code></li>
</ul>
<p>以上所有方法,<code>association</code> 會換成作為第一個參數傳給 <code>belongs_to</code> 的符號。比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer
end
</pre>
</div>
<p>現在每個 <code>Order</code> Model 的實體會有這些方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
customer
customer=
build_customer
create_customer
create_customer!
</pre>
</div>
<div class="note"><p>在初始化 <code>has_one</code> 或 <code>belongs_to</code> 關聯時,必須使用 <code>build_</code> 前綴的方法來新建關聯,而不是使用 <code>has_many</code> 或 <code>has_and_belongs_to_many</code> 關聯的 <code>association.build</code> 方法。要建立並存入資料庫,則使用 <code>create_</code> 前綴的方法。</p></div><h6 id="belongs-to-關聯新增的方法-association-force-reload-false">4.1.1.1 <code>association(force_reload = false)</code>
</h6><p>關聯物件存在時,<code>association</code> 方法回傳關聯物件。沒有找到關聯物件時,回傳 <code>nil</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@customer = @order.customer
</pre>
</div>
<p>如果關聯物件已從資料庫取出,則會回傳此物件的快取版本。要強制重新從資料庫讀取,將 <code>force_reload</code> 參數設為 <code>true</code>。</p><h6 id="belongs-to-關聯新增的方法-association-associate">4.1.1.2 <code>association=(associate)</code>
</h6><p><code>association=</code> 方法指定關聯的物件。背後的工作原理是,把物件的外鍵欄位設成關聯物件的主鍵。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@order.customer = @customer
</pre>
</div>
<h6 id="belongs-to-關聯新增的方法-build-association-attributes">4.1.1.3 <code>build_association(attributes = {})</code>
</h6><p><code>build_association</code> 方法回傳關聯類型的新物件。這個物件透過傳入的屬性來初始化,同時會自動設定外鍵。但關聯物件<strong>仍未儲存至資料庫</strong>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@customer = @order.build_customer(customer_number: 123,
customer_name: "John Doe")
</pre>
</div>
<h6 id="belongs-to-關聯新增的方法-create-association-attributes">4.1.1.4 <code>create_association(attributes = {})</code>
</h6><p><code>create_association</code> 方法回傳關聯類型的新物件。 這個物件透過傳入的屬性來初始化,同時會自動設定外鍵。一旦通過所有 Model 的驗證規則時,便把此關聯物件存入資料庫。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@customer = @order.create_customer(customer_number: 123,
customer_name: "John Doe")
</pre>
</div>
<h6 id="belongs-to-關聯新增的方法-create-association-bang-attributes">4.1.1.5 <code>create_association!(attributes = {})</code>
</h6><p>與 <code>create_association</code> 方法相同,但在驗證失敗時會拋出 <code>ActiveRecord::RecordInvalid</code> 異常。</p><h5 id="belongs-to-關聯可用選項">4.1.2 <code>belongs_to</code> 關聯可用選項</h5><p>Rails 聰明的預設設定足夠應付多數場景,但總會有需要客製化 <code>belongs_to</code> 關聯行為的時候。這種時候透過傳入選項,以及建立關聯時傳入作用域區塊便可輕易完成。舉例來說,下面的關聯使用了兩個選項:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer, dependent: :destroy,
counter_cache: true
end
</pre>
</div>
<p><code>belongs_to</code> 關聯支援以下選項:</p>
<ul>
<li><code>:autosave</code></li>
<li><code>:class_name</code></li>
<li><code>:counter_cache</code></li>
<li><code>:dependent</code></li>
<li><code>:foreign_key</code></li>
<li><code>:inverse_of</code></li>
<li><code>:polymorphic</code></li>
<li><code>:touch</code></li>
<li><code>:validate</code></li>
</ul>
<h6 id="belongs-to-關聯可用選項-autosave">4.1.2.1 <code>:autosave</code>
</h6><p>若 <code>autosave</code> 選項為 <code>true</code>,Rails 會在儲存父物件時,自動保存子物件。如子物件標記為刪除,也會在儲存時自動刪除。</p><h6 id="belongs-to-關聯可用選項-class-name">4.1.2.2 <code>:class_name</code>
</h6><p>如果關聯 Model 名稱推論不出來時,可以使用 <code>:class_name</code> 選項來指定。舉例來說,訂單屬於顧客,但顧客的 Model 名是 <code>Patron</code>,則可以這麼指定:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer, class_name: "Patron"
end
</pre>
</div>
<h6 id="counter-cache">4.1.2.3 <code>:counter_cache</code>
</h6><p><code>:counter_cache</code> 選項可以更有效的找出所屬物件的數量。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer
end
class Customer < ActiveRecord::Base
has_many :orders
end
</pre>
</div>
<p>如上宣告關聯後,詢問 <code>@customer.orders.size</code> 需要對資料庫下一條 <code>COUNT(*)</code> 查詢。要避免此操作,可以在 <code>belongs_to</code> 的 Model 加上 <code>counter_cache: true</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
</pre>
</div>
<p>如此一來 Rails 確保快取是最新的,並對 <code>size</code> 方法回傳快取的值。</p><p>雖然 <code>:counter_cache</code> 在 <code>belongs_to</code> 的 Model 裡指定。但實際的欄位必須加在關聯的 Model。上例則是需要在 <code>Customer</code> Model 加入 <code>orders_count</code> 欄位。欄位名稱需要與預設不同的的話可以:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
has_many :orders
end
</pre>
</div>
<p>Counter Cache 欄位透過 <code>attr_readonly</code> 加到關聯模型的唯讀列表裡。</p><h6 id="belongs-to-關聯可用選項-dependent">4.1.2.4 <code>:dependent</code>
</h6><p><code>:dependent</code> 選項可設為:</p>
<ul>
<li>
<code>:destroy</code>:物件刪除時,會對關聯物件呼叫 <code>destroy</code>。</li>
<li>