-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathactive_record_querying.html
1739 lines (1513 loc) · 89.9 KB
/
active_record_querying.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>如何使用各种方法查找满足条件的记录;</li>
<li>如何指定查找记录的排序方式,获取哪些属性,分组等;</li>
<li>获取数据时如何使用按需加载介绍数据库查询数;</li>
<li>如何使用动态查询方法;</li>
<li>如何检查某个记录是否存在;</li>
<li>如何在 Active Record 模型中做各种计算;</li>
<li>如何执行 EXPLAIN 命令;</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li>
<a href="#%E4%BB%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%AD%E8%8E%B7%E5%8F%96%E5%AF%B9%E8%B1%A1">从数据库中获取对象</a>
<ul>
<li><a href="#%E8%8E%B7%E5%8F%96%E5%8D%95%E4%B8%AA%E5%AF%B9%E8%B1%A1">获取单个对象</a></li>
<li><a href="#%E8%8E%B7%E5%8F%96%E5%A4%9A%E4%B8%AA%E5%AF%B9%E8%B1%A1">获取多个对象</a></li>
<li><a href="#%E6%89%B9%E9%87%8F%E8%8E%B7%E5%8F%96%E5%A4%9A%E4%B8%AA%E5%AF%B9%E8%B1%A1">批量获取多个对象</a></li>
</ul>
</li>
<li>
<a href="#%E6%9D%A1%E4%BB%B6%E6%9F%A5%E8%AF%A2">条件查询</a>
<ul>
<li><a href="#%E7%BA%AF%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9D%A1%E4%BB%B6">纯字符串条件</a></li>
<li><a href="#%E6%95%B0%E7%BB%84%E6%9D%A1%E4%BB%B6">数组条件</a></li>
<li><a href="#hash-%E6%9D%A1%E4%BB%B6">Hash 条件</a></li>
<li><a href="#not-%E6%9D%A1%E4%BB%B6"><code>NOT</code> 条件</a></li>
</ul>
</li>
<li><a href="#%E6%8E%92%E5%BA%8F">排序</a></li>
<li><a href="#%E6%9F%A5%E8%AF%A2%E6%8C%87%E5%AE%9A%E5%AD%97%E6%AE%B5">查询指定字段</a></li>
<li><a href="#%E9%99%90%E9%87%8F%E5%92%8C%E5%81%8F%E7%A7%BB">限量和偏移</a></li>
<li><a href="#%E5%88%86%E7%BB%84">分组</a></li>
<li><a href="#%E5%88%86%E7%BB%84%E7%AD%9B%E9%80%89">分组筛选</a></li>
<li>
<a href="#%E6%9D%A1%E4%BB%B6%E8%A6%86%E7%9B%96">条件覆盖</a>
<ul>
<li><a href="#unscope"><code>unscope</code></a></li>
<li><a href="#only"><code>only</code></a></li>
<li><a href="#reorder"><code>reorder</code></a></li>
<li><a href="#reverse_order"><code>reverse_order</code></a></li>
<li><a href="#rewhere"><code>rewhere</code></a></li>
</ul>
</li>
<li><a href="#%E7%A9%BA%E5%85%B3%E7%B3%BB">空关系</a></li>
<li><a href="#%E5%8F%AA%E8%AF%BB%E5%AF%B9%E8%B1%A1">只读对象</a></li>
<li>
<a href="#%E6%9B%B4%E6%96%B0%E6%97%B6%E9%94%81%E5%AE%9A%E8%AE%B0%E5%BD%95">更新时锁定记录</a>
<ul>
<li><a href="#%E4%B9%90%E8%A7%82%E9%94%81%E5%AE%9A">乐观锁定</a></li>
<li><a href="#%E6%82%B2%E8%A7%82%E9%94%81%E5%AE%9A">悲观锁定</a></li>
</ul>
</li>
<li>
<a href="#%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E8%A1%A8">连接数据表</a>
<ul>
<li><a href="#%E4%BD%BF%E7%94%A8%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%BD%A2%E5%BC%8F%E7%9A%84-sql-%E8%AF%AD%E5%8F%A5">使用字符串形式的 SQL 语句</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%95%B0%E7%BB%84%E6%88%96-hash-%E6%8C%87%E5%AE%9A%E5%85%B7%E5%90%8D%E5%85%B3%E8%81%94">使用数组或 Hash 指定具名关联</a></li>
<li><a href="#%E6%8C%87%E5%AE%9A%E7%94%A8%E4%BA%8E%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E8%A1%A8%E4%B8%8A%E7%9A%84%E6%9D%A1%E4%BB%B6">指定用于连接数据表上的条件</a></li>
</ul>
</li>
<li>
<a href="#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD%E5%85%B3%E8%81%94">按需加载关联</a>
<ul>
<li><a href="#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD%E5%A4%9A%E4%B8%AA%E5%85%B3%E8%81%94">按需加载多个关联</a></li>
<li><a href="#%E6%8C%87%E5%AE%9A%E7%94%A8%E4%BA%8E%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD%E5%85%B3%E8%81%94%E4%B8%8A%E7%9A%84%E6%9D%A1%E4%BB%B6">指定用于按需加载关联上的条件</a></li>
</ul>
</li>
<li>
<a href="#%E4%BD%9C%E7%94%A8%E5%9F%9F">作用域</a>
<ul>
<li><a href="#%E4%BC%A0%E5%85%A5%E5%8F%82%E6%95%B0">传入参数</a></li>
<li><a href="#%E5%90%88%E5%B9%B6%E4%BD%9C%E7%94%A8%E5%9F%9F">合并作用域</a></li>
<li><a href="#%E6%8C%87%E5%AE%9A%E9%BB%98%E8%AE%A4%E4%BD%9C%E7%94%A8%E5%9F%9F">指定默认作用域</a></li>
<li><a href="#%E5%88%A0%E9%99%A4%E6%89%80%E6%9C%89%E4%BD%9C%E7%94%A8%E5%9F%9F">删除所有作用域</a></li>
</ul>
</li>
<li><a href="#%E5%8A%A8%E6%80%81%E6%9F%A5%E8%AF%A2%E6%96%B9%E6%B3%95">动态查询方法</a></li>
<li>
<a href="#%E6%9F%A5%E6%89%BE%E6%88%96%E6%9E%84%E5%BB%BA%E6%96%B0%E5%AF%B9%E8%B1%A1">查找或构建新对象</a>
<ul>
<li><a href="#find_or_create_by"><code>find_or_create_by</code></a></li>
<li><a href="#find_or_create_by-bang"><code>find_or_create_by!</code></a></li>
<li><a href="#find_or_initialize_by"><code>find_or_initialize_by</code></a></li>
</ul>
</li>
<li>
<a href="#%E4%BD%BF%E7%94%A8-sql-%E8%AF%AD%E5%8F%A5%E6%9F%A5%E8%AF%A2">使用 SQL 语句查询</a>
<ul>
<li><a href="#select_all"><code>select_all</code></a></li>
<li><a href="#pluck"><code>pluck</code></a></li>
<li><a href="#ids"><code>ids</code></a></li>
</ul>
</li>
<li><a href="#%E6%A3%80%E6%9F%A5%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8">检查对象是否存在</a></li>
<li>
<a href="#%E8%AE%A1%E7%AE%97">计算</a>
<ul>
<li><a href="#%E8%AE%A1%E6%95%B0">计数</a></li>
<li><a href="#%E5%B9%B3%E5%9D%87%E5%80%BC">平均值</a></li>
<li><a href="#%E6%9C%80%E5%B0%8F%E5%80%BC">最小值</a></li>
<li><a href="#%E6%9C%80%E5%A4%A7%E5%80%BC">最大值</a></li>
<li><a href="#%E6%B1%82%E5%92%8C">求和</a></li>
</ul>
</li>
<li>
<a href="#%E6%89%A7%E8%A1%8C-explain-%E5%91%BD%E4%BB%A4">执行 EXPLAIN 命令</a>
<ul>
<li><a href="#%E8%A7%A3%E8%AF%BB-explain-%E5%91%BD%E4%BB%A4%E7%9A%84%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C">解读 EXPLAIN 命令的输出结果</a></li>
</ul>
</li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<p>如果习惯使用 SQL 查询数据库,会发现在 Rails 中执行相同的查询有更好的方式。大多数情况下,在 Active Record 中无需直接使用 SQL。</p><p>文中的实例代码会用到下面一个或多个模型:</p><div class="info"><p>下面所有的模型除非有特别说明之外,都使用 <code>id</code> 做主键。</p></div><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Client < ActiveRecord::Base
has_one :address
has_many :orders
has_and_belongs_to_many :roles
end
</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Address < ActiveRecord::Base
belongs_to :client
end
</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
belongs_to :client, counter_cache: true
end
</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Role < ActiveRecord::Base
has_and_belongs_to_many :clients
end
</pre>
</div>
<p>Active Record 会代你执行数据库查询,可以兼容大多数数据库(MySQL,PostgreSQL 和 SQLite 等)。不管使用哪种数据库,所用的 Active Record 方法都是一样的。</p><h3 id="从数据库中获取对象">1 从数据库中获取对象</h3><p>Active Record 提供了很多查询方法,用来从数据库中获取对象。每个查询方法都接可接受参数,不用直接写 SQL 就能在数据库中执行指定的查询。</p><p>这些方法是:</p>
<ul>
<li><code>find</code></li>
<li><code>create_with</code></li>
<li><code>distinct</code></li>
<li><code>eager_load</code></li>
<li><code>extending</code></li>
<li><code>from</code></li>
<li><code>group</code></li>
<li><code>having</code></li>
<li><code>includes</code></li>
<li><code>joins</code></li>
<li><code>limit</code></li>
<li><code>lock</code></li>
<li><code>none</code></li>
<li><code>offset</code></li>
<li><code>order</code></li>
<li><code>preload</code></li>
<li><code>readonly</code></li>
<li><code>references</code></li>
<li><code>reorder</code></li>
<li><code>reverse_order</code></li>
<li><code>select</code></li>
<li><code>uniq</code></li>
<li><code>where</code></li>
</ul>
<p>上述所有方法都返回一个 <code>ActiveRecord::Relation</code> 实例。</p><p><code>Model.find(options)</code> 方法执行的主要操作概括如下:</p>
<ul>
<li>把指定的选项转换成等价的 SQL 查询语句;</li>
<li>执行 SQL 查询,从数据库中获取结果;</li>
<li>为每个查询结果实例化一个对应的模型对象;</li>
<li>如果有 <code>after_find</code> 回调,再执行 <code>after_find</code> 回调;</li>
</ul>
<h4 id="获取单个对象">1.1 获取单个对象</h4><p>在 Active Record 中获取单个对象有好几种方法。</p><h5 id="使用主键">1.1.1 使用主键</h5><p>使用 <code>Model.find(primary_key)</code> 方法可以获取指定主键对应的对象。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# Find the client with primary key (id) 10.
client = Client.find(10)
# => #<Client id: 10, first_name: "Ryan">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
</pre>
</div>
<p>如果未找到匹配的记录,<code>Model.find(primary_key)</code> 会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。</p><h5 id="获取单个对象-take">1.1.2 <code>take</code>
</h5><p><code>Model.take</code> 方法会获取一个记录,不考虑任何顺序。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.take
# => #<Client id: 1, first_name: "Lifo">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 1
</pre>
</div>
<p>如果没找到记录,<code>Model.take</code> 不会抛出异常,而是返回 <code>nil</code>。</p><div class="info"><p>获取的记录根据所用的数据库引擎会有所不同。</p></div><h5 id="获取单个对象-first">1.1.3 <code>first</code>
</h5><p><code>Model.first</code> 获取按主键排序得到的第一个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.first
# => #<Client id: 1, first_name: "Lifo">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</pre>
</div>
<p><code>Model.first</code> 如果没找到匹配的记录,不会抛出异常,而是返回 <code>nil</code>。</p><h5 id="获取单个对象-last">1.1.4 <code>last</code>
</h5><p><code>Model.last</code> 获取按主键排序得到的最后一个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.last
# => #<Client id: 221, first_name: "Russel">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</pre>
</div>
<p><code>Model.last</code> 如果没找到匹配的记录,不会抛出异常,而是返回 <code>nil</code>。</p><h5 id="find_by">1.1.5 <code>find_by</code>
</h5><p><code>Model.find_by</code> 获取满足条件的第一个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.find_by first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">
Client.find_by first_name: 'Jon'
# => nil
</pre>
</div>
<p>等价于:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(first_name: 'Lifo').take
</pre>
</div>
<h5 id="take-bang">1.1.6 <code>take!</code>
</h5><p><code>Model.take!</code> 方法会获取一个记录,不考虑任何顺序。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.take!
# => #<Client id: 1, first_name: "Lifo">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 1
</pre>
</div>
<p>如果未找到匹配的记录,<code>Model.take!</code> 会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。</p><h5 id="first-bang">1.1.7 <code>first!</code>
</h5><p><code>Model.first!</code> 获取按主键排序得到的第一个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.first!
# => #<Client id: 1, first_name: "Lifo">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</pre>
</div>
<p>如果未找到匹配的记录,<code>Model.first!</code> 会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。</p><h5 id="last-bang">1.1.8 <code>last!</code>
</h5><p><code>Model.last!</code> 获取按主键排序得到的最后一个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.last!
# => #<Client id: 221, first_name: "Russel">
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</pre>
</div>
<p>如果未找到匹配的记录,<code>Model.last!</code> 会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。</p><h5 id="find_by-bang">1.1.9 <code>find_by!</code>
</h5><p><code>Model.find_by!</code> 获取满足条件的第一个记录。如果没找到匹配的记录,会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.find_by! first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">
Client.find_by! first_name: 'Jon'
# => ActiveRecord::RecordNotFound
</pre>
</div>
<p>等价于:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(first_name: 'Lifo').take!
</pre>
</div>
<h4 id="获取多个对象">1.2 获取多个对象</h4><h5 id="使用多个主键">1.2.1 使用多个主键</h5><p><code>Model.find(array_of_primary_key)</code> 方法可接受一个由主键组成的数组,返回一个由主键对应记录组成的数组。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# Find the clients with primary keys 1 and 10.
client = Client.find([1, 10]) # Or even Client.find(1, 10)
# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
</pre>
</div>
<p>上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE (clients.id IN (1,10))
</pre>
</div>
<div class="warning"><p>只要有一个主键的对应的记录未找到,<code>Model.find(array_of_primary_key)</code> 方法就会抛出 <code>ActiveRecord::RecordNotFound</code> 异常。</p></div><h5 id="获取多个对象-take">1.2.2 take</h5><p><code>Model.take(limit)</code> 方法获取 <code>limit</code> 个记录,不考虑任何顺序:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.take(2)
# => [#<Client id: 1, first_name: "Lifo">,
#<Client id: 2, first_name: "Raf">]
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 2
</pre>
</div>
<h5 id="获取多个对象-first">1.2.3 first</h5><p><code>Model.first(limit)</code> 方法获取按主键排序的前 <code>limit</code> 个记录:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.first(2)
# => [#<Client id: 1, first_name: "Lifo">,
#<Client id: 2, first_name: "Raf">]
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY id ASC LIMIT 2
</pre>
</div>
<h5 id="获取多个对象-last">1.2.4 last</h5><p><code>Model.last(limit)</code> 方法获取按主键降序排列的前 <code>limit</code> 个记录:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.last(2)
# => [#<Client id: 10, first_name: "Ryan">,
#<Client id: 9, first_name: "John">]
</pre>
</div>
<p>和上述方法等价的 SQL 查询是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients ORDER BY id DESC LIMIT 2
</pre>
</div>
<h4 id="批量获取多个对象">1.3 批量获取多个对象</h4><p>我们经常需要遍历由很多记录组成的集合,例如给大量用户发送邮件列表,或者导出数据。</p><p>我们可能会直接写出如下的代码:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# This is very inefficient when the users table has thousands of rows.
User.all.each do |user|
NewsLetter.weekly_deliver(user)
end
</pre>
</div>
<p>但这种方法在数据表很大时就有点不现实了,因为 <code>User.all.each</code> 会一次读取整个数据表,一行记录创建一个模型对象,然后把整个模型对象数组存入内存。如果记录数非常多,可能会用完内存。</p><p>Rails 为了解决这种问题提供了两个方法,把记录分成几个批次,不占用过多内存。第一个方法是 <code>find_each</code>,获取一批记录,然后分别把每个记录传入代码块。第二个方法是 <code>find_in_batches</code>,获取一批记录,然后把整批记录作为数组传入代码块。</p><div class="info"><p><code>find_each</code> 和 <code>find_in_batches</code> 方法的目的是分批处理无法一次载入内存的巨量记录。如果只想遍历几千个记录,更推荐使用常规的查询方法。</p></div><h5 id="find_each">1.3.1 <code>find_each</code>
</h5><p><code>find_each</code> 方法获取一批记录,然后分别把每个记录传入代码块。在下面的例子中,<code>find_each</code> 获取 1000 个记录,然后把每个记录传入代码块,直到所有记录都处理完为止:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_each do |user|
NewsLetter.weekly_deliver(user)
end
</pre>
</div>
<h6 id="find_each-方法的选项">1.3.1.1 <code>find_each</code> 方法的选项</h6><p>在 <code>find_each</code> 方法中可使用 <code>find</code> 方法的大多数选项,但不能使用 <code>:order</code> 和 <code>:limit</code>,因为这两个选项是保留给 <code>find_each</code> 内部使用的。</p><p><code>find_each</code> 方法还可使用另外两个选项:<code>:batch_size</code> 和 <code>:start</code>。</p><p><strong><code>:batch_size</code></strong></p><p><code>:batch_size</code> 选项指定在把各记录传入代码块之前,各批次获取的记录数量。例如,一个批次获取 5000 个记录:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_each(batch_size: 5000) do |user|
NewsLetter.weekly_deliver(user)
end
</pre>
</div>
<p><strong><code>:start</code></strong></p><p>默认情况下,按主键的升序方式获取记录,其中主键的类型必须是整数。如果不想用最小的 ID,可以使用 <code>:start</code> 选项指定批次的起始 ID。例如,前面的批量处理中断了,但保存了中断时的 ID,就可以使用这个选项继续处理。</p><p>例如,在有 5000 个记录的批次中,只向主键大于 2000 的用户发送邮件列表,可以这么做:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_each(start: 2000, batch_size: 5000) do |user|
NewsLetter.weekly_deliver(user)
end
</pre>
</div>
<p>还有一个例子是,使用多个 worker 处理同一个进程队列。如果需要每个 worker 处理 10000 个记录,就可以在每个 worker 中设置相应的 <code>:start</code> 选项。</p><h5 id="find_in_batches">1.3.2 <code>find_in_batches</code>
</h5><p><code>find_in_batches</code> 方法和 <code>find_each</code> 类似,都获取一批记录。二者的不同点是,<code>find_in_batches</code> 把整批记录作为一个数组传入代码块,而不是单独传入各记录。在下面的例子中,会把 1000 个单据一次性传入代码块,让代码块后面的程序处理剩下的单据:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# Give add_invoices an array of 1000 invoices at a time
Invoice.find_in_batches(include: :invoice_lines) do |invoices|
export.add_invoices(invoices)
end
</pre>
</div>
<div class="note"><p><code>:include</code> 选项可以让指定的关联和模型一同加载。</p></div><h6 id="find_in_batches-方法的选项">1.3.2.1 <code>find_in_batches</code> 方法的选项</h6><p><code>find_in_batches</code> 方法和 <code>find_each</code> 方法一样,可以使用 <code>:batch_size</code> 和 <code>:start</code> 选项,还可使用常规的 <code>find</code> 方法中的大多数选项,但不能使用 <code>:order</code> 和 <code>:limit</code> 选项,因为这两个选项保留给 <code>find_in_batches</code> 方法内部使用。</p><h3 id="条件查询">2 条件查询</h3><p><code>where</code> 方法用来指定限制获取记录的条件,用于 SQL 语句的 <code>WHERE</code> 子句。条件可使用字符串、数组或 Hash 指定。</p><h4 id="纯字符串条件">2.1 纯字符串条件</h4><p>如果查询时要使用条件,可以直接指定。例如 <code>Client.where("orders_count = '2'")</code>,获取 <code>orders_count</code> 字段为 <code>2</code> 的客户记录。</p><div class="warning"><p>使用纯字符串指定条件可能导致 SQL 注入漏洞。例如,<code>Client.where("first_name LIKE '%#{params[:first_name]}%'")</code>,这里的条件就不安全。推荐使用的条件指定方式是数组,请阅读下一节。</p></div><h4 id="数组条件">2.2 数组条件</h4><p>如果数字是在别处动态生成的话应该怎么处理呢?可用下面的查询:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count = ?", params[:orders])
</pre>
</div>
<p>Active Record 会先处理第一个元素中的条件,然后使用后续元素替换第一个元素中的问号(<code>?</code>)。</p><p>指定多个条件的方式如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
</pre>
</div>
<p>在这个例子中,第一个问号会替换成 <code>params[:orders]</code> 的值;第二个问号会替换成 <code>false</code> 在 SQL 中对应的值,具体的值视所用的适配器而定。</p><p>下面这种形式</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count = ?", params[:orders])
</pre>
</div>
<p>要比这种形式好</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count = #{params[:orders]}")
</pre>
</div>
<p>因为前者传入的参数更安全。直接在条件字符串中指定的条件会原封不动的传给数据库。也就是说,即使用户不怀好意,条件也会转义。如果这么做,整个数据库就处在一个危险境地,只要用户发现可以接触数据库,就能做任何想做的事。所以,千万别直接在条件字符串中使用参数。</p><div class="info"><p>关于 SQL 注入更详细的介绍,请阅读“<a href="security.html#sql-injection">Ruby on Rails 安全指南</a>”</p></div><h5 id="条件中的占位符">2.2.1 条件中的占位符</h5><p>除了使用问号占位之外,在数组条件中还可使用键值对 Hash 形式的占位符:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("created_at >= :start_date AND created_at <= :end_date",
{start_date: params[:start_date], end_date: params[:end_date]})
</pre>
</div>
<p>如果条件中有很多参数,使用这种形式可读性更高。</p><h4 id="hash-条件">2.3 Hash 条件</h4><p>Active Record 还允许使用 Hash 条件,提高条件语句的可读性。使用 Hash 条件时,传入 Hash 的键是要设定条件的字段,值是要设定的条件。</p><div class="note"><p>在 Hash 条件中只能指定相等。范围和子集这三种条件。</p></div><h5 id="相等">2.3.1 相等</h5><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(locked: true)
</pre>
</div>
<p>字段的名字还可使用字符串表示:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where('locked' => true)
</pre>
</div>
<p>在 <code>belongs_to</code> 关联中,如果条件中的值是模型对象,可用关联键表示。这种条件指定方式也可用于多态关联。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where(author: author)
Author.joins(:posts).where(posts: { author: author })
</pre>
</div>
<div class="note"><p>条件的值不能为 Symbol。例如,不能这么指定条件:<code>Client.where(status: :active)</code>。</p></div><h5 id="范围">2.3.2 范围</h5><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
</pre>
</div>
<p>指定这个条件后,会使用 SQL <code>BETWEEN</code> 子句查询昨天创建的客户:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
</pre>
</div>
<p>这段代码演示了<a href="#array-conditions">数组条件</a>的简写形式。</p><h5 id="子集">2.3.3 子集</h5><p>如果想使用 <code>IN</code> 子句查询记录,可以在 Hash 条件中使用数组:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(orders_count: [1,3,5])
</pre>
</div>
<p>上述代码生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
</pre>
</div>
<h4 id="not-条件">2.4 <code>NOT</code> 条件</h4><p>SQL <code>NOT</code> 查询可用 <code>where.not</code> 方法构建。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where.not(author: author)
</pre>
</div>
<p>也即是说,这个查询首先调用没有参数的 <code>where</code> 方法,然后再调用 <code>not</code> 方法。</p><h3 id="排序">3 排序</h3><p>要想按照特定的顺序从数据库中获取记录,可以使用 <code>order</code> 方法。</p><p>例如,想按照 <code>created_at</code> 的升序方式获取一些记录,可以这么做:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.order(:created_at)
# OR
Client.order("created_at")
</pre>
</div>
<p>还可使用 <code>ASC</code> 或 <code>DESC</code> 指定排序方式:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.order(created_at: :desc)
# OR
Client.order(created_at: :asc)
# OR
Client.order("created_at DESC")
# OR
Client.order("created_at ASC")
</pre>
</div>
<p>或者使用多个字段排序:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.order(orders_count: :asc, created_at: :desc)
# OR
Client.order(:orders_count, created_at: :desc)
# OR
Client.order("orders_count ASC, created_at DESC")
# OR
Client.order("orders_count ASC", "created_at DESC")
</pre>
</div>
<p>如果想在不同的上下文中多次调用 <code>order</code>,可以在前一个 <code>order</code> 后再调用一次:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.order("orders_count ASC").order("created_at DESC")
# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC
</pre>
</div>
<h3 id="查询指定字段">4 查询指定字段</h3><p>默认情况下,<code>Model.find</code> 使用 <code>SELECT *</code> 查询所有字段。</p><p>要查询部分字段,可使用 <code>select</code> 方法。</p><p>例如,只查询 <code>viewable_by</code> 和 <code>locked</code> 字段:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.select("viewable_by, locked")
</pre>
</div>
<p>上述查询使用的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT viewable_by, locked FROM clients
</pre>
</div>
<p>使用时要注意,因为模型对象只会使用选择的字段初始化。如果字段不能初始化模型对象,会得到以下异常:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
ActiveModel::MissingAttributeError: missing attribute: <attribute>
</pre>
</div>
<p>其中 <code><attribute></code> 是所查询的字段。<code>id</code> 字段不会抛出 <code>ActiveRecord::MissingAttributeError</code> 异常,所以在关联中使用时要注意,因为关联需要 <code>id</code> 字段才能正常使用。</p><p>如果查询时希望指定字段的同值记录只出现一次,可以使用 <code>distinct</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.select(:name).distinct
</pre>
</div>
<p>上述方法生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT DISTINCT name FROM clients
</pre>
</div>
<p>查询后还可以删除唯一性限制:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
query = Client.select(:name).distinct
# => Returns unique names
query.distinct(false)
# => Returns all names, even if there are duplicates
</pre>
</div>
<h3 id="限量和偏移">5 限量和偏移</h3><p>要想在 <code>Model.find</code> 方法中使用 SQL <code>LIMIT</code> 子句,可使用 <code>limit</code> 和 <code>offset</code> 方法。</p><p><code>limit</code> 方法指定获取的记录数量,<code>offset</code> 方法指定在返回结果之前跳过多少个记录。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.limit(5)
</pre>
</div>
<p>上述查询最大只会返回 5 各客户对象,因为没指定偏移,多以会返回数据表中的前 5 个记录。生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 5
</pre>
</div>
<p>再加上 <code>offset</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.limit(5).offset(30)
</pre>
</div>
<p>这时会从第 31 个记录开始,返回最多 5 个客户对象。生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 5 OFFSET 30
</pre>
</div>
<h3 id="分组">6 分组</h3><p>要想在查询时使用 SQL <code>GROUP BY</code> 子句,可以使用 <code>group</code> 方法。</p><p>例如,如果想获取一组订单的创建日期,可以这么做:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
</pre>
</div>
<p>上述查询会只会为相同日期下的订单创建一个 <code>Order</code> 对象。</p><p>生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders
GROUP BY date(created_at)
</pre>
</div>
<h3 id="分组筛选">7 分组筛选</h3><p>SQL 使用 <code>HAVING</code> 子句指定 <code>GROUP BY</code> 分组的条件。在 <code>Model.find</code> 方法中可使用 <code>:having</code> 选项指定 <code>HAVING</code> 子句。</p><p>例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Order.select("date(created_at) as ordered_date, sum(price) as total_price").
group("date(created_at)").having("sum(price) > ?", 100)
</pre>
</div>
<p>生成的 SQL 如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders
GROUP BY date(created_at)
HAVING sum(price) > 100
</pre>
</div>
<p>这个查询只会为同一天下的订单创建一个 <code>Order</code> 对象,而且这一天的订单总额要大于 $100。</p><h3 id="条件覆盖">8 条件覆盖</h3><h4 id="unscope">8.1 <code>unscope</code>
</h4><p>如果要删除某个条件可使用 <code>unscope</code> 方法。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where('id > 10').limit(20).order('id asc').unscope(:order)
</pre>
</div>
<p>生成的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE id > 10 LIMIT 20
# Original query without `unscope`
SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20
</pre>
</div>
<p><code>unscope</code> 还可删除 <code>WHERE</code> 子句中的条件。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "posts".* FROM "posts" WHERE trashed = 0
</pre>
</div>
<p><code>unscope</code> 还可影响合并后的查询:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.order('id asc').merge(Post.unscope(:order))
# SELECT "posts".* FROM "posts"
</pre>
</div>
<h4 id="only">8.2 <code>only</code>
</h4><p>查询条件还可使用 <code>only</code> 方法覆盖。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where('id > 10').limit(20).order('id desc').only(:order, :where)
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE id > 10 ORDER BY id DESC
# Original query without `only`
SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20
</pre>
</div>
<h4 id="reorder">8.3 <code>reorder</code>
</h4><p><code>reorder</code> 方法覆盖原来的 <code>order</code> 条件。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Post < ActiveRecord::Base
..
..
has_many :comments, -> { order('posted_at DESC') }
end
Post.find(10).comments.reorder('name')
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE id = 10 ORDER BY name
</pre>
</div>
<p>没用 <code>reorder</code> 方法时执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
</pre>
</div>
<h4 id="reverse_order">8.4 <code>reverse_order</code>
</h4><p><code>reverse_order</code> 方法翻转 <code>ORDER</code> 子句的条件。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count > 10").order(:name).reverse_order
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC
</pre>
</div>
<p>如果查询中没有使用 <code>ORDER</code> 子句,<code>reverse_order</code> 方法会按照主键的逆序查询:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where("orders_count > 10").reverse_order
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC
</pre>
</div>
<p>这个方法<strong>没有</strong>参数。</p><h4 id="rewhere">8.5 <code>rewhere</code>
</h4><p><code>rewhere</code> 方法覆盖前面的 <code>where</code> 条件。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where(trashed: true).rewhere(trashed: false)
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE `trashed` = 0
</pre>
</div>
<p>如果不使用 <code>rewhere</code> 方法,写成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.where(trashed: true).where(trashed: false)
</pre>
</div>
<p>执行的 SQL 语句如下:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0
</pre>
</div>
<h3 id="空关系">9 空关系</h3><p><code>none</code> 返回一个可链接的关系,没有相应的记录。<code>none</code> 方法返回对象的后续条件查询,得到的还是空关系。如果想以可链接的方式响应可能无返回结果的方法或者作用域,可使用 <code>none</code> 方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Post.none # returns an empty Relation and fires no queries.
</pre>
</div>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# The visible_posts method below is expected to return a Relation.
@posts = current_user.visible_posts.where(name: params[:name])
def visible_posts
case role
when 'Country Manager'
Post.where(country: country)
when 'Reviewer'
Post.published
when 'Bad User'
Post.none # => returning [] or nil breaks the caller code in this case
end
end
</pre>
</div>
<h3 id="只读对象">10 只读对象</h3><p>Active Record 提供了 <code>readonly</code> 方法,禁止修改获取的对象。试图修改只读记录的操作不会成功,而且会抛出 <code>ActiveRecord::ReadOnlyRecord</code> 异常。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.readonly.first
client.visits += 1
client.save
</pre>
</div>
<p>因为把 <code>client</code> 设为了只读对象,所以上述代码调用 <code>client.save</code> 方法修改 <code>visits</code> 的值时会抛出 <code>ActiveRecord::ReadOnlyRecord</code> 异常。</p><h3 id="更新时锁定记录">11 更新时锁定记录</h3><p>锁定可以避免更新记录时的条件竞争,也能保证原子更新。</p><p>Active Record 提供了两种锁定机制:</p>
<ul>
<li>乐观锁定</li>
<li>悲观锁定</li>
</ul>
<h4 id="乐观锁定">11.1 乐观锁定</h4><p>乐观锁定允许多个用户编辑同一个记录,假设数据发生冲突的可能性最小。Rails 会检查读取记录后是否有其他程序在修改这个记录。如果检测到有其他程序在修改,就会抛出 <code>ActiveRecord::StaleObjectError</code> 异常,忽略改动。</p><p><strong>乐观锁定字段</strong></p><p>为了使用乐观锁定,数据表中要有一个类型为整数的 <code>lock_version</code> 字段。每次更新记录时,Active Record 都会增加 <code>lock_version</code> 字段的值。如果更新请求中的 <code>lock_version</code> 字段值比数据库中的 <code>lock_version</code> 字段值小,会抛出 <code>ActiveRecord::StaleObjectError</code> 异常,更新失败。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
c1 = Client.find(1)
c2 = Client.find(1)
c1.first_name = "Michael"
c1.save
c2.name = "should fail"
c2.save # Raises an ActiveRecord::StaleObjectError
</pre>
</div>
<p>抛出异常后,你要负责处理冲突,可以回滚操作、合并操作或者使用其他业务逻辑处理。</p><p>乐观锁定可以使用 <code>ActiveRecord::Base.lock_optimistically = false</code> 关闭。</p><p>要想修改 <code>lock_version</code> 字段的名字,可以使用 <code>ActiveRecord::Base</code> 提供的 <code>locking_column</code> 类方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Client < ActiveRecord::Base
self.locking_column = :lock_client_column
end
</pre>
</div>
<h4 id="悲观锁定">11.2 悲观锁定</h4><p>悲观锁定使用底层数据库提供的锁定机制。使用 <code>lock</code> 方法构建的关系在所选记录上生成一个“互斥锁”(exclusive lock)。使用 <code>lock</code> 方法构建的关系一般都放入事务中,避免死锁。</p><p>例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">