-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathassociation_basics.html
2047 lines (1836 loc) · 112 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 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 中的关联功能。</p><p>读完本文,你将学到:</p>
<ul>
<li>如何声明 Active Record 模型间的关联;</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="#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%BD%BF%E7%94%A8%E5%85%B3%E8%81%94">为什么要使用关联</a></li>
<li>
<a href="#%E5%85%B3%E8%81%94%E7%9A%84%E7%B1%BB%E5%9E%8B">关联的类型</a>
<ul>
<li><a href="#belongs_to-%E5%85%B3%E8%81%94"><code>belongs_to</code> 关联</a></li>
<li><a href="#has_one-%E5%85%B3%E8%81%94"><code>has_one</code> 关联</a></li>
<li><a href="#has_many-%E5%85%B3%E8%81%94"><code>has_many</code> 关联</a></li>
<li><a href="#has_many-:through-%E5%85%B3%E8%81%94"><code>has_many :through</code> 关联</a></li>
<li><a href="#has_one-:through-%E5%85%B3%E8%81%94"><code>has_one :through</code> 关联</a></li>
<li><a href="#has_and_belongs_to_many-%E5%85%B3%E8%81%94"><code>has_and_belongs_to_many</code> 关联</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-belongs_to-%E8%BF%98%E6%98%AF-has_one">使用 <code>belongs_to</code> 还是 <code>has_one</code></a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-has_many-:through-%E8%BF%98%E6%98%AF-has_and_belongs_to_many">使用 <code>has_many :through</code> 还是 <code>has_and_belongs_to_many</code></a></li>
<li><a href="#%E5%A4%9A%E6%80%81%E5%85%B3%E8%81%94">多态关联</a></li>
<li><a href="#%E8%87%AA%E8%BF%9E%E6%8E%A5">自连接</a></li>
</ul>
</li>
<li>
<a href="#%E5%B0%8F%E6%8A%80%E5%B7%A7%E5%92%8C%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">小技巧和注意事项</a>
<ul>
<li><a href="#%E7%BC%93%E5%AD%98%E6%8E%A7%E5%88%B6">缓存控制</a></li>
<li><a href="#%E9%81%BF%E5%85%8D%E5%91%BD%E5%90%8D%E5%86%B2%E7%AA%81">避免命名冲突</a></li>
<li><a href="#%E6%9B%B4%E6%96%B0%E6%A8%A1%E5%BC%8F">更新模式</a></li>
<li><a href="#%E6%8E%A7%E5%88%B6%E5%85%B3%E8%81%94%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F">控制关联的作用域</a></li>
<li><a href="#%E5%8F%8C%E5%90%91%E5%85%B3%E8%81%94">双向关联</a></li>
</ul>
</li>
<li>
<a href="#%E5%85%B3%E8%81%94%E8%AF%A6%E8%A7%A3">关联详解</a>
<ul>
<li><a href="#belongs_to-%E5%85%B3%E8%81%94%E8%AF%A6%E8%A7%A3"><code>belongs_to</code> 关联详解</a></li>
<li><a href="#has_one-%E5%85%B3%E8%81%94%E8%AF%A6%E8%A7%A3"><code>has_one</code> 关联详解</a></li>
<li><a href="#has_many-%E5%85%B3%E8%81%94%E8%AF%A6%E8%A7%A3"><code>has_many</code> 关联详解</a></li>
<li><a href="#has_and_belongs_to_many-%E5%85%B3%E8%81%94%E8%AF%A6%E8%A7%A3"><code>has_and_belongs_to_many</code> 关联详解</a></li>
<li><a href="#%E5%85%B3%E8%81%94%E5%9B%9E%E8%B0%83">关联回调</a></li>
<li><a href="#%E5%85%B3%E8%81%94%E6%89%A9%E5%B1%95">关联扩展</a></li>
</ul>
</li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="为什么要使用关联">1 为什么要使用关联</h3><p>模型之间为什么要有关联?因为关联让常规操作更简单。例如,在一个简单的 Rails 程序中,有一个顾客模型和一个订单模型。每个顾客可以下多个订单。没用关联的模型定义如下:</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 这两个模型是有一定联系的,就可以把这些操作连在一起。下面使用关联重新定义顾客和订单模型:</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>学习更多关联类型,请阅读下一节。下一节介绍了一些使用关联时的小技巧,然后列出了关联添加的所有方法和选项。</p><h3 id="关联的类型">2 关联的类型</h3><p>在 Rails 中,关联是两个 Active Record 模型之间的关系。关联使用宏的方式实现,用声明的形式为模型添加功能。例如,声明一个模型属于(<code>belongs_to</code>)另一个模型后,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> 关联创建两个模型之间一对一的关系,声明所在的模型实例属于另一个模型的实例。例如,如果程序中有顾客和订单两个模型,每个订单只能指定给一个顾客,就要这么声明订单模型:</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 关联"></p><div class="note"><p>在 <code>belongs_to</code> 关联声明中必须使用单数形式。如果在上面的代码中使用复数形式,程序会报错,提示未初始化常量 <code>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
end
create_table :orders do |t|
t.belongs_to :customer
t.datetime :order_date
t.timestamps
end
end
end
</pre>
</div>
<h4 id="has_one-关联">2.2 <code>has_one</code> 关联</h4><p><code>has_one</code> 关联也会建立两个模型之间的一对一关系,但语义和结果有点不一样。这种关联表示模型的实例包含或拥有另一个模型的实例。例如,在程序中,每个供应商只有一个账户,可以这么定义供应商模型:</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 关联"></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
end
create_table :accounts do |t|
t.belongs_to :supplier
t.string :account_number
t.timestamps
end
end
end
</pre>
</div>
<h4 id="has_many-关联">2.3 <code>has_many</code> 关联</h4><p><code>has_many</code> 关联建立两个模型之间的一对多关系。在 <code>belongs_to</code> 关联的另一端经常会使用这个关联。<code>has_many</code> 关联表示模型的实例有零个或多个另一个模型的实例。例如,在程序中有顾客和订单两个模型,顾客模型可以这么定义:</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> 关联时,另一个模型使用复数形式。</p></div><p><img src="images/has_many.png" alt="has_many 关联"></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
end
create_table :orders do |t|
t.belongs_to :customer
t.datetime :order_date
t.timestamps
end
end
end
</pre>
</div>
<h4 id="has_many-:through-关联">2.4 <code>has_many :through</code> 关联</h4><p><code>has_many :through</code> 关联经常用来建立两个模型之间的多对多关联。这种关联表示一个模型的实例可以借由第三个模型,拥有零个和多个另一个模型的实例。例如,在看病过程中,病人要和医生预约时间。这中间的关联声明如下:</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 关联"></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
end
create_table :patients do |t|
t.string :name
t.timestamps
end
create_table :appointments do |t|
t.belongs_to :physician
t.belongs_to :patient
t.datetime :appointment_date
t.timestamps
end
end
end
</pre>
</div>
<p>连接模型中的集合可以使用 API 关联。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
physician.patients = patients
</pre>
</div>
<p>会为新建立的关联对象创建连接模型实例,如果其中一个对象删除了,相应的记录也会删除。</p><div class="warning"><p>自动删除连接模型的操作直接执行,不会触发 <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>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> 关联建立两个模型之间的一对一关系。这种关联表示一个模型通过第三个模型拥有另一个模型的实例。例如,每个供应商只有一个账户,而且每个账户都有一个历史账户,那么可以这么定义模型:</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 关联"></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
end
create_table :accounts do |t|
t.belongs_to :supplier
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
t.belongs_to :account
t.integer :credit_rating
t.timestamps
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> 关联之间建立两个模型之间的多对多关系,不借由第三个模型。例如,程序中有装配体和零件两个模型,每个装配体中有多个零件,每个零件又可用于多个装配体,这时可以按照下面的方式定义模型:</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 关联"></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
end
create_table :parts do |t|
t.string :part_number
t.timestamps
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly
t.belongs_to :part
end
end
end
</pre>
</div>
<h4 id="使用-belongs_to-还是-has_one">2.7 使用 <code>belongs_to</code> 还是 <code>has_one</code>
</h4><p>如果想建立两个模型之间的一对一关系,可以在一个模型中声明 <code>belongs_to</code>,然后在另一模型中声明 <code>has_one</code>。但是怎么知道在哪个模型中声明哪种关联?</p><p>不同的声明方式带来的区别是外键放在哪个模型对应的数据表中(外键在声明 <code>belongs_to</code> 关联所在模型对应的数据表中)。不过声明时要考虑一下语义,<code>has_one</code> 的意思是某样东西属于我。例如,说供应商有一个账户,比账户拥有供应商更合理,所以正确的关联应该这么声明:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Supplier < ActiveRecord::Base
has_one :account
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
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
t.timestamps
end
end
end
</pre>
</div>
<div class="note"><p><code>t.integer :supplier_id</code> 更明确的表明了外键的名字。在目前的 Rails 版本中,可以抽象实现的细节,使用 <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>,但无法直接建立关联,要通过第三个模型:</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>根据经验,如果关联的第三个模型要作为独立实体使用,要用 <code>has_many :through</code> 关联;如果不需要使用第三个模型,用简单的 <code>has_and_belongs_to_many</code> 关联即可(不过要记得在数据库中创建连接数据表)。</p><p>如果需要做数据验证、回调,或者连接模型上要用到其他属性,此时就要使用 <code>has_many :through</code> 关联。</p><h4 id="多态关联">2.9 多态关联</h4><p>关联还有一种高级用法,“多态关联”。在多态关联中,在同一个关联中,模型可以属于其他多个模型。例如,图片模型可以属于雇员模型或者产品模型,模型的定义如下:</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> 中指定使用多态,可以理解成创建了一个接口,可供任何一个模型使用。在 <code>Employee</code> 模型实例上,可以使用 <code>@employee.pictures</code> 获取图片集合。类似地,可使用 <code>@product.pictures</code> 获取产品的图片。</p><p>在 <code>Picture</code> 模型的实例上,可以使用 <code>@picture.imageable</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.integer :imageable_id
t.string :imageable_type
t.timestamps
end
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
t.timestamps
end
end
end
</pre>
</div>
<p><img src="images/polymorphic.png" alt="多态关联"></p><h4 id="自连接">2.10 自连接</h4><p>设计数据模型时会发现,有时模型要和自己建立关联。例如,在一个数据表中保存所有雇员的信息,但要建立经理和下属之间的关系。这种情况可以使用自连接关联解决:</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
end
end
end
</pre>
</div>
<h3 id="小技巧和注意事项">3 小技巧和注意事项</h3><p>在 Rails 程序中高效地使用 Active Record 关联,要了解以下几个知识:</p>
<ul>
<li>缓存控制</li>
<li>避免命名冲突</li>
<li>更新模式</li>
<li>控制关联的作用域</li>
<li>Bi-directional associations</li>
</ul>
<h4 id="缓存控制">3.1 缓存控制</h4><p>关联添加的方法都会使用缓存,记录最近一次查询结果,以备后用。缓存还会在方法之间共享。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
customer.orders # retrieves orders from the database
customer.orders.size # uses the cached copy of orders
customer.orders.empty? # uses the cached copy of orders
</pre>
</div>
<p>程序的其他部分会修改数据,那么应该怎么重载缓存呢?调用关联方法时传入 <code>true</code> 参数即可:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
customer.orders # retrieves orders from the database
customer.orders.size # uses the cached copy of orders
customer.orders(true).empty? # discards the cached copy of orders
# and goes back to the database
</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> 关联后,要创建相应的外键。例如,有下面这个模型:</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
end
end
</pre>
</div>
<p>如果声明关联之前已经定义了模型,则要在迁移中使用 <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 会按照类名出现在字典中的顺序为数据表起名字。那么,顾客和订单模型使用的连接数据表默认名为“customers_orders”,因为在字典中,“c”在“o”前面。</p><div class="warning"><p>模型名的顺序使用字符串的 <code><</code> 操作符确定。所以,如果两个字符串的长度不同,比较最短长度时,两个字符串是相等的,但长字符串的排序比短字符串靠前。例如,你可能以为“"paper_boxes”和“papers”这两个表生成的连接表名为“papers_paper_boxes”,因为“paper_boxes”比“papers”长。其实生成的连接表名为“paper_boxes_papers”,因为在一般的编码方式中,“_”比“s”靠前。</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
end
end
</pre>
</div>
<p>我们把 <code>id: false</code> 选项传给 <code>create_table</code> 方法,因为这个表不对应模型。只有这样,关联才能正常建立。如果在使用 <code>has_and_belongs_to_many</code> 关联时遇到奇怪的表现,例如提示模型 ID 损坏,或 ID 冲突,有可能就是因为创建了主键。</p><h4 id="控制关联的作用域">3.4 控制关联的作用域</h4><p>默认情况下,关联只会查找当前模块作用域中的对象。如果在模块中定义 Active Record 模型,知道这一点很重要。例如:</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>要想让处在不同命名空间中的模型正常建立关联,声明关联时要指定完整的类名:</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>这么修改之后,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> 选项(根据关联的名字)。使用标准名字的关联都有这种功能。但是,如果在关联中设置了下面这些选项,将无法自动设置 <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> 关联创建一个模型与另一个模型之间的一对一关系。用数据库的行话来说,就是这个类中包含了外键。如果外键在另一个类中,就应该使用 <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> 模型实例都获得了这些方法:</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>association.build</code> 方法是在 <code>has_many</code> 和 <code>has_and_belongs_to_many</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> 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,但关联对象不会存入数据库。</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> 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,只要能通过所有数据验证,就会把关联对象存入数据库。</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>,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。</p><h6 id="belongs_to-方法的选项-:class_name">4.1.2.2 <code>:class_name</code>
</h6><p>如果另一个模型无法从关联的名字获取,可以使用 <code>:class_name</code> 选项指定模型名。例如,如果订单属于顾客,但表示顾客的模型是 <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> 关联的模型中加入计数缓存功能:</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> 关联的模型中设置,但实际使用的字段要添加到关联的模型中。针对上面的例子,要把 <code>orders_count</code> 字段加入 <code>Customer</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>计数缓存字段通过 <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>
<code>:delete</code>:销毁对象时,关联的对象不会调用 <code>destroy</code> 方法,而是直接从数据库中删除;</li>
</ul>
<div class="warning"><p>在 <code>belongs_to</code> 关联和 <code>has_many</code> 关联配对时,不应该设置这个选项,否则会导致数据库中出现孤儿记录。</p></div><h6 id="belongs_to-方法的选项-:foreign_key">4.1.2.5 <code>:foreign_key</code>
</h6><p>按照约定,用来存储外键的字段名是关联名后加 <code>_id</code>。<code>:foreign_key</code> 选项可以设置要使用的外键名:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :customer, class_name: "Patron",
foreign_key: "patron_id"
end
</pre>
</div>
<div class="info"><p>不管怎样,Rails 都不会自动创建外键字段,你要自己在迁移中创建。</p></div><h6 id="belongs_to-方法的选项-:inverse_of">4.1.2.6 <code>:inverse_of</code>
</h6><p><code>:inverse_of</code> 选项指定 <code>belongs_to</code> 关联另一端的 <code>has_many</code> 和 <code>has_one</code> 关联名。不能和 <code>:polymorphic</code> 选项一起使用。</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>
<h6 id=":polymorphic">4.1.2.7 <code>:polymorphic</code>
</h6><p><code>:polymorphic</code> 选项为 <code>true</code> 时表明这是个多态关联。<a href="#polymorphic-associations">前文</a>已经详细介绍过多态关联。</p><h6 id=":touch">4.1.2.8 <code>:touch</code>
</h6><p>如果把 <code>:touch</code> 选项设为 <code>true</code>,保存或销毁对象时,关联对象的 <code>updated_at</code> 或 <code>updated_on</code> 字段会自动设为当前时间戳。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base