-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactive_record_querying.html
1774 lines (1547 loc) · 87.8 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>
<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>如何使用各種方法與條件來取出資料庫記錄(record)。</li>
<li>如何排序、取出某幾個屬性、分組、其它用來找出資料庫記錄的特性。</li>
<li>如何使用 Eager load 來減少資料庫查詢的次數。</li>
<li>如何使用 Active Record 動態的查詢方法。</li>
<li>如何檢查特定的資料庫記錄是否存在。</li>
<li>如何在 Active Record Model 裡做各式計算。</li>
<li>如何對 Active Record Relation 使用 <code>EXPLAIN</code>。</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li>
<a href="#%E5%8F%96%E5%87%BA%E8%B3%87%E6%96%99">取出資料</a>
<ul>
<li><a href="#%E5%8F%96%E5%87%BA%E5%96%AE%E4%B8%80%E7%89%A9%E4%BB%B6">取出單一物件</a></li>
<li><a href="#%E6%89%B9%E6%AC%A1%E5%8F%96%E5%87%BA%E5%A4%9A%E7%AD%86%E8%A8%98%E9%8C%84">批次取出多筆記錄</a></li>
</ul>
</li>
<li>
<a href="#%E6%A2%9D%E4%BB%B6">條件</a>
<ul>
<li><a href="#%E5%AD%97%E4%B8%B2%E6%A2%9D%E4%BB%B6">字串條件</a></li>
<li><a href="#%E9%99%A3%E5%88%97%E6%A2%9D%E4%BB%B6">陣列條件</a></li>
<li><a href="#hash">Hash</a></li>
<li><a href="#not">NOT</a></li>
</ul>
</li>
<li><a href="#%E6%8E%92%E5%BA%8F">排序</a></li>
<li><a href="#%E9%81%B8%E5%87%BA%E7%89%B9%E5%AE%9A%E6%AC%84%E4%BD%8D">選出特定欄位</a></li>
<li><a href="#limit-%E8%88%87-offset">Limit 與 Offset</a></li>
<li>
<a href="#group">Group</a>
<ul>
<li><a href="#%E5%88%86%E7%B5%84%E9%A0%85%E7%9B%AE%E7%9A%84%E7%B8%BD%E6%95%B8">分組項目的總數</a></li>
</ul>
</li>
<li><a href="#having">Having</a></li>
<li>
<a href="#%E8%A6%86%E8%93%8B%E6%A2%9D%E4%BB%B6">覆蓋條件</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>
</ul>
</li>
<li><a href="#%E7%A9%BA-relation">空 Relation</a></li>
<li><a href="#%E5%94%AF%E8%AE%80%E7%89%A9%E4%BB%B6">唯讀物件</a></li>
<li>
<a href="#%E6%9B%B4%E6%96%B0%E6%99%82%E9%8E%96%E5%AE%9A%E8%A8%98%E9%8C%84">更新時鎖定記錄</a>
<ul>
<li><a href="#%E6%A8%82%E8%A7%80%E9%8E%96%E5%AE%9A">樂觀鎖定</a></li>
<li><a href="#%E6%82%B2%E8%A7%80%E9%8E%96%E5%AE%9A">悲觀鎖定</a></li>
</ul>
</li>
<li>
<a href="#%E9%80%A3%E6%8E%A5%E8%B3%87%E6%96%99%E8%A1%A8">連接資料表</a>
<ul>
<li><a href="#%E4%BD%BF%E7%94%A8%E5%AD%97%E4%B8%B2%E5%BD%A2%E5%BC%8F%E7%9A%84-sql-%E7%89%87%E6%AE%B5">使用字串形式的 SQL 片段</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E9%97%9C%E8%81%AF%E5%90%8D%E7%A8%B1%E7%9A%84%E9%99%A3%E5%88%97%E6%88%96-hash-%E5%BD%A2%E5%BC%8F">使用關聯名稱的陣列或 Hash 形式</a></li>
<li><a href="#%E5%B0%8D%E9%80%A3%E6%8E%A5%E7%9A%84%E8%B3%87%E6%96%99%E8%A1%A8%E6%8C%87%E5%AE%9A%E6%A2%9D%E4%BB%B6">對連接的資料表指定條件</a></li>
</ul>
</li>
<li>
<a href="#eager-loading-%E9%97%9C%E8%81%AF">Eager Loading 關聯</a>
<ul>
<li><a href="#eager-loading-%E5%A4%9A%E5%80%8B%E9%97%9C%E8%81%AF">Eager Loading 多個關聯</a></li>
<li><a href="#%E5%B0%8D-eager-loaded-%E9%97%9C%E8%81%AF%E4%B8%8B%E6%A2%9D%E4%BB%B6">對 Eager Loaded 關聯下條件</a></li>
</ul>
</li>
<li>
<a href="#%E4%BD%9C%E7%94%A8%E5%9F%9F">作用域</a>
<ul>
<li><a href="#%E5%82%B3%E5%85%A5%E5%8F%83%E6%95%B8">傳入參數</a></li>
<li><a href="#%E5%90%88%E4%BD%B5%E4%BD%9C%E7%94%A8%E5%9F%9F">合併作用域</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E9%A0%90%E8%A8%AD%E4%BD%9C%E7%94%A8%E5%9F%9F">使用預設作用域</a></li>
<li><a href="#%E7%A7%BB%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%8B%95%E6%85%8B%E6%9F%A5%E8%A9%A2%E6%96%B9%E6%B3%95">動態查詢方法</a></li>
<li>
<a href="#%E5%B0%8B%E6%89%BE%E6%88%96%E6%96%B0%E5%BB%BA%E7%89%A9%E4%BB%B6">尋找或新建物件</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="#%E7%94%A8-sql-%E6%9F%A5%E8%A9%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="#%E7%89%A9%E4%BB%B6%E5%AD%98%E5%9C%A8%E6%80%A7">物件存在性</a></li>
<li>
<a href="#%E8%A8%88%E7%AE%97">計算</a>
<ul>
<li><a href="#%E8%A8%88%E6%95%B8">計數</a></li>
<li><a href="#%E5%B9%B3%E5%9D%87">平均</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="#%E5%92%8C">和</a></li>
</ul>
</li>
<li>
<a href="#%E5%9F%B7%E8%A1%8C-explain">執行 EXPLAIN</a>
<ul>
<li><a href="#%E8%A7%A3%E8%AE%80-explain">解讀 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>本篇之後的例子都會用下列的 Model 來講解:</p><div class="info"><p>除非特別說明,否則下列 Model 都用 <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 提供了多種 Finder 方法,用來從資料庫裡取出物件。每個 Finder 方法允許傳參數,來對資料庫執行不同的查詢,而無需直接寫純 SQL。</p><p>Finder 方法有:</p>
<ul>
<li><code>bind</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>將每個查詢結果,根據適當的 Model 實體化出 Ruby 物件。</li>
<li>有 <code>after_find</code> 回呼的話,執行它們。</li>
</ul>
<h4 id="取出單一物件">1.1 取出單一物件</h4><p>Active Record 提供數種方式來取出一個物件。</p><h5 id="find">1.1.1 <code>find</code>
</h5><p>使用 <code>find</code> 來取出給定主鍵(primary key)的物件,比如:</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>find</code> 沒找到符合條件的記錄,則會拋出 <code>ActiveRecord::RecordNotFound</code> 異常。</p><p>也可以用來查詢多個物件:傳給 <code>find</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>find</code> 方法會拋出 <code>ActiveRecord::RecordNotFound</code> 異常。</p></div><h5 id="take">1.1.2 <code>take</code>
</h5><p><code>take</code> 方法取出 <code>limit</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>take</code> 則回傳 <code>nil</code>。</p><p>可以傳一個數值參數給 <code>take</code>,會回傳多筆結果。比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.take(2)
# => [
#<Client id: 1, first_name: "Lifo">,
#<Client id: 220, first_name: "Sara">
]
</pre>
</div>
<p>對應的 SQL:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM clients LIMIT 2
</pre>
</div>
<p><code>take!</code> 的行為同 <code>take</code>,但在沒找到記錄時會拋出 <code>ActiveRecord::RecordNotFound</code>。</p><div class="info"><p>取出記錄的結果可能隨資料庫引擎的不同而變化。</p></div><h5 id="取出單一物件-first">1.1.3 <code>first</code>
</h5><p><code>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 LIMIT 1
</pre>
</div>
<p>如果沒找到記錄,<code>first</code> 會回傳 <code>nil</code>,不會拋出異常。</p><p><code>first!</code> 的行為同 <code>first</code>,但在沒找到記錄時會拋出 <code>ActiveRecord::RecordNotFound</code> 異常。</p><h5 id="取出單一物件-last">1.1.4 <code>last</code>
</h5><p><code>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>last</code> 會回傳 <code>nil</code>,不會拋出異常。</p><p>可傳數值參數給 <code>last</code>,會回傳最後幾筆結果,譬如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
client = Client.last(3)
# => [
#<Client id: 219, first_name: "James">,
#<Client id: 220, first_name: "Sara">,
#<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 3
</pre>
</div>
<p><code>last!</code> 行為同 <code>last</code>,但在沒找到記錄時會拋出 <code>ActiveRecord::RecordNotFound</code> 異常。</p><h5 id="find-by">1.1.5 <code>find_by</code>
</h5><p><code>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>
<p><code>find_by!</code> 行為同 <code>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: 'does not exist'
# => ActiveRecord::RecordNotFound
</pre>
</div>
<p>等同於:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Client.where(first_name: 'does not exist').take!
</pre>
</div>
<h5 id="取出單一物件-first">1.1.6 <code>first</code>
</h5><p><code>first</code> 取出 <code>limit</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 id ASC LIMIT 2
</pre>
</div>
<h5 id="取出單一物件-last">1.1.7 <code>last</code>
</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.2 批次取出多筆記錄</h4><p>處理多筆記錄是常見的需求,比如寄信給使用者,轉出資料。</p><p>直覺可能會這麼做:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# 如果有數千個使用者,效率非常差。
User.all.each do |user|
NewsMailer.weekly(user).deliver_now
end
</pre>
</div>
<p>但在資料表很大的時候,這個方法便不實用了。由於 <code>User.all.each</code> 告訴 Active Record 一次去把整張表抓出來,再為表的每一列建出物件,最後將所有的物件放到記憶體裡。如果資料庫裡存了非常多筆記錄,可能會把記憶體用光。</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.2.1 <code>find_each</code>
</h5><p><code>find_each</code> 方法取出一批記錄,將每筆記錄傳入區塊裡。下面的例子,將以 <code>find_each</code> 來取出 1000 筆記錄(<code>find_each</code> 與 <code>find_in_batches</code> 的預設值),並傳至區塊。一次處理 1000 筆,直至記錄通通處理完畢為止:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_each do |user|
NewsMailer.weekly(user).deliver_now
end
</pre>
</div>
<p>要給 <code>find_each</code> 加上條件,可以像用 <code>where</code> 一樣連鎖使用:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.where(weekly_subscriber: true).find_each do |user|
NewsMailer.weekly(user).deliver_now
end
</pre>
</div>
<h6 id="find-each-選項">1.2.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>: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|
NewsMailer.weekly(user).deliver_now
end
</pre>
</div>
<p><strong><code>:start</code></strong></p><p>預設記錄按主鍵升序取出,主鍵類型必須是整數。批次預設從最小的 ID 開始,可用 <code>:start</code> 選項可以設定批次的起始 ID。在前次被中斷的批量處理重新開始的場景下很有用。</p><p>舉例來說,本週總共有 5000 封信要發。1-1999 已經發過了,便可以使用此選項從 2000 開始發信:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_each(start: 2000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
</pre>
</div>
<p>另個例子是想要多個 worker 處理同個佇列時。可以使用 <code>:start</code> 讓每個 worker 分別處理 10000 筆記錄。</p><h5 id="find-in-batches">1.2.2 <code>find_in_batches</code>
</h5><p><code>find_in_batches</code> 方法與 <code>find_each</code> 類似,皆用來取出記錄。差別在於 <code>find_in_batchs</code> 取出記錄放入陣列傳至區塊,而 <code>find_each</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 do |invoices|
export.add_invoices(invoices)
end
</pre>
</div>
<h6 id="find-in-batches-接受的選項">1.2.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>。</p><h3 id="條件">2 條件</h3><p><code>where</code> 方法允許取出符合條件的記錄,<code>where</code> 即代表了 SQL 語句的 <code>WHERE</code> 部分。</p><p>條件可以是字串、陣列、或是 Hash。</p><h4 id="字串條件">2.1 字串條件</h4><p>直接將要使用的條件,以字串形式傳入 <code>where</code> 即可。如 <code>Client.where("orders_count = '2'")</code> 會回傳所有 <code>orders_count</code> 是 2 的 clients。</p><div class="warning"><p>條件是純字串可能有 SQL injection 的風險。舉例來說,<code>Client.where("first_name LIKE '%#{params[:first_name]}%'")</code> 是不安全的,參考下節如何將字串條件改用陣列來處理。</p></div><h4 id="陣列條件">2.2 陣列條件</h4><p>如果我們要找的 <code>orders_count</code>,不一定固定是 2,可能是不定的數字:</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> 換成 <code>params[:orders]</code> 做查詢。也可宣告多個條件,條件式後的元素,對應到條件裡的每個 <code>?</code>。</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>?</code> 會換成 <code>params[:orders]</code>,第二個則會換成 SQL 裡的 <code>false</code> (根據不同的 adapter 而異)。</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>因為前者比較安全。直接將變數插入條件字串裡,不論變數是什麼,都會直接存到資料庫裡。這表示從惡意使用者傳來的變數,會直接存到資料庫。這麼做是把資料庫放在風險裡不管啊!一旦有人知道,可以隨意將任何字串插入資料庫裡,就可以做任何想做的事。<strong>絕對不要直接將變數插入條件字串裡。</strong></p><div class="info"><p>關於更多 SQL injection 的資料,請參考 <a href="edgeguides.rubyonrails.org/security.html#sql-injection">Ruby on Rails 安全指南</a>。</p></div><h5 id="佔位符">2.2.1 佔位符</h5><p>替換除了可以使用 <code>?</code> 之外,用符號也可以。以 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 條件時,鍵是要查詢的欄位、值為期望值。</p><div class="note"><p>只有 Equality、Range、subset 可用這種形式來寫條件。</p></div><h5 id="equality">2.3.1 Equality</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> 關係裡,關聯名稱也可以用來做查詢,<code>polymorphic</code> 關係也可以。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Address.where(client: client)
Address.joins(:clients).where(clients: {address: address})
</pre>
</div>
<p>Note: 條件的值不能用符號。比如這樣是不允許的 <code>Client.where(status: :active)</code>。</p><h5 id="range">2.3.2 Range</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="#">陣列條件</a>。</p><h5 id="subset">2.3.3 Subset</h5><p>如果要使用 SQL 的 <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 NOT</h4><p>SQL 的 <code>NOT</code> 可以使用 <code>where.not</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Article.where.not(author: author)
</pre>
</div>
<p>換句話說,先不傳參數呼叫 <code>where</code>,再使用 <code>not</code> 傳入 <code>where</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>,SQL 的 ORDER BY 順序與呼叫順序相同:</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>要小心使用 <code>select</code>。因為實體化出來的物件僅有所選欄位。如果試圖存取不存在的欄位,會得到 <code>ActiveModel::MissingAttributeError</code> 異常:</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>ActiveModel::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="limit-與-offset">5 Limit 與 Offset</h3><p>要在 <code>Model.find</code> 裡使用 SQL 的 <code>LIMIT</code>,可以對 Active Record Relation 使用 <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 位客戶。因為沒指定 <code>offset</code>,會回傳資料比如的前 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="group">6 Group</h3><p>要在 <code>Model.find</code> 裡使用 SQL 的 <code>LIMIT</code>,可以對 Active Record Relation 使用 <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>會依照存在資料庫裡的順序,按日期回傳單筆訂單物件。</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>
<h4 id="分組項目的總數">6.1 分組項目的總數</h4><p>要取得單一查詢呼叫有幾筆結果,在 <code>group</code> 之後呼叫 <code>count</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Order.group(:status).count
# => { 'awaiting_approval' => 7, 'paid' => 12 }
</pre>
</div>
<p>上例執行的 SQL 看起來會像是:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT COUNT (*) AS count_all, status AS status
FROM "orders"
GROUP BY status
</pre>
</div>
<h3 id="having">7 Having</h3><p>在 SQL 裡,可以使用 <code>HAVING</code> 子句來對 <code>GROUP BY</code> 欄位下條件。<code>Model.find</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>100</code> 的訂單。</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">
Article.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 articles WHERE id > 10 LIMIT 20
# Original query without `unscope`
SELECT * FROM articles 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">
Article.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "articles".* FROM "articles" WHERE trashed = 0
</pre>
</div>
<p>使用了 <code>unscope</code> 的 Relation 會影響與其合併的 Relation:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
Article.order('id asc').merge(Article.unscope(:order))
# SELECT "articles".* FROM "articles"
</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">
Article.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 articles WHERE id > 10 ORDER BY id DESC
# Original query without `only`
SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
</pre>
</div>
<h4 id="reorder">8.3 <code>reorder</code>
</h4><p><code>reorder</code> 可以覆蓋掉預設 scope 的 <code>order</code> 條件:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article < ActiveRecord::Base
has_many :comments, -> { order('posted_at DESC') }
end
Article.find(10).comments.reorder('name')
</pre>
</div>
<p>執行的 SQL 語句:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY name
</pre>
</div>
<p>原本會執行的 SQL 語句(沒用 <code>reorder</code>):</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_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 語句(<code>ASC</code> 反轉為 <code>DESC</code>):</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><code>reverse_order</code> <strong>不接受參數</strong>。</p><h3 id="空-relation">9 空 Relation</h3><p><code>none</code> 方法回傳一個不包含任何記錄、可連鎖使用的 Relation。<code>none</code> 回傳的 Relation 上做查詢,仍會回傳空的 Relation。應用場景是回傳的 Relation 可能沒有記錄,但需要可以連鎖使用。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Article.none # returns an empty Relation and fires no queries.
</pre>
</div>
<p>回傳空的 Relation,不會對資料庫下查詢。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# The visible_articles method below is expected to return a Relation.
@articles = current_user.visible_articles.where(name: params[:name])
def visible_articles
case role
when 'Country Manager'
Article.where(country: country)
when 'Reviewer'
Article.published
when 'Bad User'
Article.none # => returning [] or nil breaks the caller code in this case
end
end
</pre>
</div>
<p>上例 <code>visible_articles</code> 可能沒有可見的 <code>articles</code>,但之後還有 <code>where</code> 子句,此時沒有 <code>articles</code> 的情況可以使用 <code>Article.none</code>。</p><h3 id="唯讀物件">10 唯讀物件</h3><p>Active Record 提供 <code>readonly</code> 方法,用來禁止修改回傳的物件。試圖要修改 <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>ActiveRecord::ReadOnlyRecord</code> 異常,因為 <code>visits</code> 的數值改變了。</p><h3 id="更新時鎖定記錄">11 更新時鎖定記錄</h3><p>鎖定可以避免更新可能發生的 race condition,確保更新是原子性的操作。</p><p>Active Record 提供兩種鎖定機制:</p>
<ul>
<li>樂觀鎖定(Optimistic Locking)</li>
<li>悲觀鎖定(Pessimistic Locking)</li>
</ul>
<h4 id="樂觀鎖定">11.1 樂觀鎖定</h4><p>樂觀鎖定允許多個使用者編輯相同的紀錄,並假設資料衝突發生衝突的可能性最小。透過檢查該記錄從資料庫取出後,是否有另個進程修改此記錄。如果有其他進程同時修改記錄時,會拋出 <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>悲觀鎖定使用資料庫提供的鎖定機制。在建立 Relation 時,使用 <code>lock</code> 可以對選擇的列獲得一個互斥鎖。通常使用 <code>lock</code> 的 Relation 會包在 transaction 裡,避免死鎖的情況發生。</p><p>比如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">