-
Notifications
You must be signed in to change notification settings - Fork 2
/
active_record_validations.html
1083 lines (960 loc) · 60.5 KB
/
active_record_validations.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>如何编写自定义的数据验证方法;</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="#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81%E7%AE%80%E4%BB%8B">数据验证简介</a>
<ul>
<li><a href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%81%9A%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81%EF%BC%9F">为什么要做数据验证?</a></li>
<li><a href="#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%81%9A%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81%EF%BC%9F">什么时候做数据验证?</a></li>
<li><a href="#%E8%B7%B3%E8%BF%87%E9%AA%8C%E8%AF%81">跳过验证</a></li>
<li><a href="#valid-questionmark-%E5%92%8C-invalid-questionmark"><code>valid?</code> 和 <code>invalid?</code></a></li>
<li><a href="#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81%E7%AE%80%E4%BB%8B-errors%5B%5D"><code>errors[]</code></a></li>
</ul>
</li>
<li>
<a href="#%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95">数据验证帮助方法</a>
<ul>
<li><a href="#acceptance"><code>acceptance</code></a></li>
<li><a href="#validates_associated"><code>validates_associated</code></a></li>
<li><a href="#confirmation"><code>confirmation</code></a></li>
<li><a href="#exclusion"><code>exclusion</code></a></li>
<li><a href="#format"><code>format</code></a></li>
<li><a href="#inclusion"><code>inclusion</code></a></li>
<li><a href="#length"><code>length</code></a></li>
<li><a href="#numericality"><code>numericality</code></a></li>
<li><a href="#presence"><code>presence</code></a></li>
<li><a href="#absence"><code>absence</code></a></li>
<li><a href="#uniqueness"><code>uniqueness</code></a></li>
<li><a href="#validates_with"><code>validates_with</code></a></li>
<li><a href="#validates_each"><code>validates_each</code></a></li>
</ul>
</li>
<li>
<a href="#%E5%B8%B8%E7%94%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E9%80%89%E9%A1%B9">常用的验证选项</a>
<ul>
<li><a href="#:allow_nil"><code>:allow_nil</code></a></li>
<li><a href="#:allow_blank"><code>:allow_blank</code></a></li>
<li><a href="#:message"><code>:message</code></a></li>
<li><a href="#:on"><code>:on</code></a></li>
</ul>
</li>
<li><a href="#%E4%B8%A5%E6%A0%BC%E9%AA%8C%E8%AF%81">严格验证</a></li>
<li>
<a href="#%E6%9D%A1%E4%BB%B6%E9%AA%8C%E8%AF%81">条件验证</a>
<ul>
<li><a href="#%E6%8C%87%E5%AE%9A-symbol">指定 Symbol</a></li>
<li><a href="#%E6%8C%87%E5%AE%9A%E5%AD%97%E7%AC%A6%E4%B8%B2">指定字符串</a></li>
<li><a href="#%E6%8C%87%E5%AE%9A-proc">指定 Proc</a></li>
<li><a href="#%E6%9D%A1%E4%BB%B6%E7%BB%84%E5%90%88">条件组合</a></li>
<li><a href="#%E8%81%94%E5%90%88%E6%9D%A1%E4%BB%B6">联合条件</a></li>
</ul>
</li>
<li>
<a href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%AA%8C%E8%AF%81%E6%96%B9%E5%BC%8F">自定义验证方式</a>
<ul>
<li><a href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%AA%8C%E8%AF%81%E4%BD%BF%E7%94%A8%E7%9A%84%E7%B1%BB">自定义验证使用的类</a></li>
<li><a href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%AA%8C%E8%AF%81%E4%BD%BF%E7%94%A8%E7%9A%84%E6%96%B9%E6%B3%95">自定义验证使用的方法</a></li>
</ul>
</li>
<li>
<a href="#%E5%A4%84%E7%90%86%E9%AA%8C%E8%AF%81%E9%94%99%E8%AF%AF">处理验证错误</a>
<ul>
<li><a href="#errors"><code>errors</code></a></li>
<li><a href="#%E5%A4%84%E7%90%86%E9%AA%8C%E8%AF%81%E9%94%99%E8%AF%AF-errors%5B%5D"><code>errors[]</code></a></li>
<li><a href="#errors.add"><code>errors.add</code></a></li>
<li><a href="#errors%5B:base%5D"><code>errors[:base]</code></a></li>
<li><a href="#errors.clear"><code>errors.clear</code></a></li>
<li><a href="#errors.size"><code>errors.size</code></a></li>
</ul>
</li>
<li><a href="#%E5%9C%A8%E8%A7%86%E5%9B%BE%E4%B8%AD%E6%98%BE%E7%A4%BA%E9%AA%8C%E8%AF%81%E9%94%99%E8%AF%AF">在视图中显示验证错误</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="数据验证简介">1 数据验证简介</h3><p>下面演示一个非常简单的数据验证:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
</pre>
</div>
<p>如上所示,如果 <code>Person</code>的 <code>name</code> 属性值为空,验证就会将其视为不合法对象。创建的第二个 <code>Person</code> 对象不会存入数据库。</p><p>在深入探讨之前,我们先来介绍数据验证在整个程序中的作用。</p><h4 id="为什么要做数据验证?">1.1 为什么要做数据验证?</h4><p>数据验证能确保只有合法的数据才会存入数据库。例如,程序可能需要用户提供一个合法的 Email 地址和邮寄地址。在模型中做验证是最有保障的,只有通过验证的数据才能存入数据库。数据验证和使用的数据库种类无关,终端用户也无法跳过,而且容易测试和维护。在 Rails 中做数据验证很简单,Rails 内置了很多帮助方法,能满足常规的需求,而且还可以编写自定义的验证方法。</p><p>数据存入数据库之前的验证方法还有其他几种,包括数据库内建的约束,客户端验证和控制器层验证。下面列出了这几种验证方法的优缺点:</p>
<ul>
<li>数据库约束和“存储过程”无法兼容多种数据库,而且测试和维护较为困难。不过,如果其他程序也要使用这个数据库,最好在数据库层做些约束。数据库层的某些验证(例如在使用量很高的数据表中做唯一性验证)通过其他方式实现起来有点困难。</li>
<li>客户端验证很有用,但单独使用时可靠性不高。如果使用 JavaScript 实现,用户在浏览器中禁用 JavaScript 后很容易跳过验证。客户端验证和其他验证方式结合使用,可以为用户提供实时反馈。</li>
<li>控制器层验证很诱人,但一般都不灵便,难以测试和维护。只要可能,就要保证控制器的代码简洁性,这样才有利于长远发展。</li>
</ul>
<p>你可以根据实际的需求选择使用哪种验证方式。Rails 团队认为,模型层数据验证最具普适性。</p><h4 id="什么时候做数据验证?">1.2 什么时候做数据验证?</h4><p>在 Active Record 中对象有两种状态:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 <code>new</code> 方法)还不属于数据库。在对象上调用 <code>save</code> 方法后,才会把对象存入相应的数据表。Active Record 使用实例方法 <code>new_record?</code> 判断对象是否已经存入数据库。假如有下面这个简单的 Active Record 类:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
end
</pre>
</div>
<p>我们可以在 <code>rails console</code> 中看一下到底怎么回事:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
$ rails console
>> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
>> p.new_record?
=> true
>> p.save
=> true
>> p.new_record?
=> false
</pre>
</div>
<p>新建并保存记录会在数据库中执行 SQL <code>INSERT</code> 操作。更新现有的记录会在数据库上执行 SQL <code>UPDATE</code> 操作。一般情况下,数据验证发生在这些 SQL 操作执行之前。如果验证失败,对象会被标记为不合法,Active Record 不会向数据库发送 <code>INSERT</code> 或 <code>UPDATE</code> 指令。这样就可以避免把不合法的数据存入数据库。你可以选择在对象创建、保存或更新时执行哪些数据验证。</p><div class="warning"><p>修改数据库中对象的状态有很多方法。有些方法会做数据验证,有些则不会。所以,如果不小心处理,还是有可能把不合法的数据存入数据库。</p></div><p>下列方法会做数据验证,如果验证失败就不会把对象存入数据库:</p>
<ul>
<li><code>create</code></li>
<li><code>create!</code></li>
<li><code>save</code></li>
<li><code>save!</code></li>
<li><code>update</code></li>
<li><code>update!</code></li>
</ul>
<p>爆炸方法(例如 <code>save!</code>)会在验证失败后抛出异常。验证失败后,非爆炸方法不会抛出异常,<code>save</code> 和 <code>update</code> 返回 <code>false</code>,<code>create</code> 返回对象本身。</p><h4 id="跳过验证">1.3 跳过验证</h4><p>下列方法会跳过验证,不管验证是否通过都会把对象存入数据库,使用时要特别留意。</p>
<ul>
<li><code>decrement!</code></li>
<li><code>decrement_counter</code></li>
<li><code>increment!</code></li>
<li><code>increment_counter</code></li>
<li><code>toggle!</code></li>
<li><code>touch</code></li>
<li><code>update_all</code></li>
<li><code>update_attribute</code></li>
<li><code>update_column</code></li>
<li><code>update_columns</code></li>
<li><code>update_counters</code></li>
</ul>
<p>注意,使用 <code>save</code> 时如果传入 <code>validate: false</code>,也会跳过验证。使用时要特别留意。</p>
<ul>
<li><code>save(validate: false)</code></li>
</ul>
<h4 id="valid-questionmark-和-invalid-questionmark">1.4 <code>valid?</code> 和 <code>invalid?</code>
</h4><p>Rails 使用 <code>valid?</code> 方法检查对象是否合法。<code>valid?</code> 方法会触发数据验证,如果对象上没有错误,就返回 <code>true</code>,否则返回 <code>false</code>。前面我们已经用过了:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
</pre>
</div>
<p>Active Record 验证结束后,所有发现的错误都可以通过实例方法 <code>errors.messages</code> 获取,该方法返回一个错误集合。如果数据验证后,这个集合为空,则说明对象是合法的。</p><p>注意,使用 <code>new</code> 方法初始化对象时,即使不合法也不会报错,因为这时还没做数据验证。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true
end
>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}
>> p.valid?
# => false
>> p.errors.messages
# => {name:["can't be blank"]}
>> p = Person.create
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {name:["can't be blank"]}
>> p.save
# => false
>> p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
>> Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
</pre>
</div>
<p><code>invalid?</code> 是 <code>valid?</code> 的逆测试,会触发数据验证,如果找到错误就返回 <code>true</code>,否则返回 <code>false</code>。</p><h4 id="数据验证简介-errors[]">1.5 <code>errors[]</code>
</h4><p>要检查对象的某个属性是否合法,可以使用 <code>errors[:attribute]</code>。<code>errors[:attribute]</code> 中包含 <code>:attribute</code> 的所有错误。如果某个属性没有错误,就会返回空数组。</p><p>这个方法只在数据验证之后才能使用,因为它只是用来收集错误信息的,并不会触发验证。而且,和前面介绍的 <code>ActiveRecord::Base#invalid?</code> 方法不一样,因为 <code>errors[:attribute]</code> 不会验证整个对象,只检查对象的某个属性是否出错。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true
end
>> Person.new.errors[:name].any? # => false
>> Person.create.errors[:name].any? # => true
</pre>
</div>
<p>我们会在“<a href="#working-with-validation-errors">处理验证错误</a>”一节详细介绍验证错误。现在,我们来看一下 Rails 默认提供的数据验证帮助方法。</p><h3 id="数据验证帮助方法">2 数据验证帮助方法</h3><p>Active Record 预先定义了很多数据验证帮助方法,可以直接在模型类定义中使用。这些帮助方法提供了常用的验证规则。每次验证失败后,都会向对象的 <code>errors</code> 集合中添加一个消息,这些消息和所验证的属性是关联的。</p><p>每个帮助方法都可以接受任意数量的属性名,所以一行代码就能在多个属性上做同一种验证。</p><p>所有的帮助方法都可指定 <code>:on</code> 和 <code>:message</code> 选项,指定何时做验证,以及验证失败后向 <code>errors</code> 集合添加什么消息。<code>:on</code> 选项的可选值是 <code>:create</code> 和 <code>:update</code>。每个帮助函数都有默认的错误消息,如果没有通过 <code>:message</code> 选项指定,则使用默认值。下面分别介绍各帮助方法。</p><h4 id="acceptance">2.1 <code>acceptance</code>
</h4><p>这个方法检查表单提交时,用户界面中的复选框是否被选中。这个功能一般用来要求用户接受程序的服务条款,阅读一些文字,等等。这种验证只针对网页程序,不会存入数据库(如果没有对应的字段,该方法会创建一个虚拟属性)。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :terms_of_service, acceptance: true
end
</pre>
</div>
<p>这个帮助方法的默认错误消息是“must be accepted”。</p><p>这个方法可以指定 <code>:accept</code> 选项,决定可接受什么值。默认为“1”,很容易修改:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :terms_of_service, acceptance: { accept: 'yes' }
end
</pre>
</div>
<h4 id="validates_associated">2.2 <code>validates_associated</code>
</h4><p>如果模型和其他模型有关联,也要验证关联的模型对象,可以使用这个方法。保存对象时,会在相关联的每个对象上调用 <code>valid?</code> 方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Library < ActiveRecord::Base
has_many :books
validates_associated :books
end
</pre>
</div>
<p>这个帮助方法可用于所有关联类型。</p><div class="warning"><p>不要在关联的两端都使用 <code>validates_associated</code>,这样会生成一个循环。</p></div><p><code>validates_associated</code> 的默认错误消息是“is invalid”。注意,相关联的每个对象都有各自的 <code>errors</code> 集合,错误消息不会都集中在调用该方法的模型对象上。</p><h4 id="confirmation">2.3 <code>confirmation</code>
</h4><p>如果要检查两个文本字段的值是否完全相同,可以使用这个帮助方法。例如,确认 Email 地址或密码。这个帮助方法会创建一个虚拟属性,其名字为要验证的属性名后加 <code>_confirmation</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :email, confirmation: true
end
</pre>
</div>
<p>在视图中可以这么写:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
</pre>
</div>
<p>只有 <code>email_confirmation</code> 的值不是 <code>nil</code> 时才会做这个验证。所以要为确认属性加上存在性验证(后文会介绍 <code>presence</code> 验证)。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
</pre>
</div>
<p>这个帮助方法的默认错误消息是“doesn't match confirmation”。</p><h4 id="exclusion">2.4 <code>exclusion</code>
</h4><p>这个帮助方法检查属性的值是否不在指定的集合中。集合可以是任何一种可枚举的对象。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Account < ActiveRecord::Base
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
end
</pre>
</div>
<p><code>exclusion</code> 方法要指定 <code>:in</code> 选项,设置哪些值不能作为属性的值。<code>:in</code> 选项有个别名 <code>:with</code>,作用相同。上面的例子设置了 <code>:message</code> 选项,演示如何获取属性的值。</p><p>默认的错误消息是“is reserved”。</p><h4 id="format">2.5 <code>format</code>
</h4><p>这个帮助方法检查属性的值是否匹配 <code>:with</code> 选项指定的正则表达式。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Product < ActiveRecord::Base
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end
</pre>
</div>
<p>默认的错误消息是“is invalid”。</p><h4 id="inclusion">2.6 <code>inclusion</code>
</h4><p>这个帮助方法检查属性的值是否在指定的集合中。集合可以是任何一种可枚举的对象。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Coffee < ActiveRecord::Base
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
</pre>
</div>
<p><code>inclusion</code> 方法要指定 <code>:in</code> 选项,设置可接受哪些值。<code>:in</code> 选项有个别名 <code>:within</code>,作用相同。上面的例子设置了 <code>:message</code> 选项,演示如何获取属性的值。</p><p>该方法的默认错误消息是“is not included in the list”。</p><h4 id="length">2.7 <code>length</code>
</h4><p>这个帮助方法验证属性值的长度,有多个选项,可以使用不同的方法指定长度限制:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, length: { minimum: 2 }
validates :bio, length: { maximum: 500 }
validates :password, length: { in: 6..20 }
validates :registration_number, length: { is: 6 }
end
</pre>
</div>
<p>可用的长度限制选项有:</p>
<ul>
<li>
<code>:minimum</code>:属性的值不能比指定的长度短;</li>
<li>
<code>:maximum</code>:属性的值不能比指定的长度长;</li>
<li>
<code>:in</code>(或 <code>:within</code>):属性值的长度在指定值之间。该选项的值必须是一个范围;</li>
<li>
<code>:is</code>:属性值的长度必须等于指定值;</li>
</ul>
<p>默认的错误消息根据长度验证类型而有所不同,还是可以 <code>:message</code> 定制。定制消息时,可以使用 <code>:wrong_length</code>、<code>:too_long</code> 和 <code>:too_short</code> 选项,<code>%{count}</code> 表示长度限制的值。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :bio, length: { maximum: 1000,
too_long: "%{count} characters is the maximum allowed" }
end
</pre>
</div>
<p>这个帮助方法默认统计字符数,但可以使用 <code>:tokenizer</code> 选项设置其他的统计方式:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Essay < ActiveRecord::Base
validates :content, length: {
minimum: 300,
maximum: 400,
tokenizer: lambda { |str| str.scan(/\w+/) },
too_short: "must have at least %{count} words",
too_long: "must have at most %{count} words"
}
end
</pre>
</div>
<p>注意,默认的错误消息使用复数形式(例如,“is too short (minimum is %{count} characters”),所以如果长度限制是 <code>minimum: 1</code>,就要提供一个定制的消息,或者使用 <code>presence: true</code> 代替。<code>:in</code> 或 <code>:within</code> 的值比 1 小时,都要提供一个定制的消息,或者在 <code>length</code> 之前,调用 <code>presence</code> 方法。</p><h4 id="numericality">2.8 <code>numericality</code>
</h4><p>这个帮助方法检查属性的值是否值包含数字。默认情况下,匹配的值是可选的正负符号后加整数或浮点数。如果只接受整数,可以把 <code>:only_integer</code> 选项设为 <code>true</code>。</p><p>如果 <code>:only_integer</code> 为 <code>true</code>,则使用下面的正则表达式验证属性的值。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
/\A[+-]?\d+\Z/
</pre>
</div>
<p>否则,会尝试使用 <code>Float</code> 把值转换成数字。</p><div class="warning"><p>注意上面的正则表达式允许最后出现换行符。</p></div><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Player < ActiveRecord::Base
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
</pre>
</div>
<p>除了 <code>:only_integer</code> 之外,这个方法还可指定以下选项,限制可接受的值:</p>
<ul>
<li>
<code>:greater_than</code>:属性值必须比指定的值大。该选项默认的错误消息是“must be greater than %{count}”;</li>
<li>
<code>:greater_than_or_equal_to</code>:属性值必须大于或等于指定的值。该选项默认的错误消息是“must be greater than or equal to %{count}”;</li>
<li>
<code>:equal_to</code>:属性值必须等于指定的值。该选项默认的错误消息是“must be equal to %{count}”;</li>
<li>
<code>:less_than</code>:属性值必须比指定的值小。该选项默认的错误消息是“must be less than %{count}”;</li>
<li>
<code>:less_than_or_equal_to</code>:属性值必须小于或等于指定的值。该选项默认的错误消息是“must be less than or equal to %{count}”;</li>
<li>
<code>:odd</code>:如果设为 <code>true</code>,属性值必须是奇数。该选项默认的错误消息是“must be odd”;</li>
<li>
<code>:even</code>:如果设为 <code>true</code>,属性值必须是偶数。该选项默认的错误消息是“must be even”;</li>
</ul>
<p>默认的错误消息是“is not a number”。</p><h4 id="presence">2.9 <code>presence</code>
</h4><p>这个帮助方法检查指定的属性是否为非空值,调用 <code>blank?</code> 方法检查值是否为 <code>nil</code> 或空字符串,即空字符串或只包含空白的字符串。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, :login, :email, presence: true
end
</pre>
</div>
<p>如果要确保关联对象存在,需要测试关联的对象本身是否存在,而不是用来映射关联的外键。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
</pre>
</div>
<p>为了能验证关联的对象是否存在,要在关联中指定 <code>:inverse_of</code> 选项。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
has_many :line_items, inverse_of: :order
end
</pre>
</div>
<p>如果验证 <code>has_one</code> 或 <code>has_many</code> 关联的对象是否存在,会在关联的对象上调用 <code>blank?</code> 和 <code>marked_for_destruction?</code> 方法。</p><p>因为 <code>false.blank?</code> 的返回值是 <code>true</code>,所以如果要验证布尔值字段是否存在要使用 <code>validates :field_name, inclusion: { in: [true, false] }</code>。</p><p>默认的错误消息是“can't be blank”。</p><h4 id="absence">2.10 <code>absence</code>
</h4><p>这个方法验证指定的属性值是否为空,使用 <code>present?</code> 方法检测值是否为 <code>nil</code> 或空字符串,即空字符串或只包含空白的字符串。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, :login, :email, absence: true
end
</pre>
</div>
<p>如果要确保关联对象为空,需要测试关联的对象本身是否为空,而不是用来映射关联的外键。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, absence: true
end
</pre>
</div>
<p>为了能验证关联的对象是否为空,要在关联中指定 <code>:inverse_of</code> 选项。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
has_many :line_items, inverse_of: :order
end
</pre>
</div>
<p>如果验证 <code>has_one</code> 或 <code>has_many</code> 关联的对象是否为空,会在关联的对象上调用 <code>present?</code> 和 <code>marked_for_destruction?</code> 方法。</p><p>因为 <code>false.present?</code> 的返回值是 <code>false</code>,所以如果要验证布尔值字段是否为空要使用 <code>validates :field_name, exclusion: { in: [true, false] }</code>。</p><p>默认的错误消息是“must be blank”。</p><h4 id="uniqueness">2.11 <code>uniqueness</code>
</h4><p>这个帮助方法会在保存对象之前验证属性值是否是唯一的。该方法不会在数据库中创建唯一性约束,所以有可能两个数据库连接创建的记录字段的值是相同的。为了避免出现这种问题,要在数据库的字段上建立唯一性索引。关于多字段索引的详细介绍,参阅 <a href="http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html">MySQL 手册</a>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Account < ActiveRecord::Base
validates :email, uniqueness: true
end
</pre>
</div>
<p>这个验证会在模型对应的数据表中执行一个 SQL 查询,检查现有的记录中该字段是否已经出现过相同的值。</p><p><code>:scope</code> 选项可以指定其他属性,用来约束唯一性验证:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Holiday < ActiveRecord::Base
validates :name, uniqueness: { scope: :year,
message: "should happen once per year" }
end
</pre>
</div>
<p>还有个 <code>:case_sensitive</code> 选项,指定唯一性验证是否要区分大小写,默认值为 <code>true</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, uniqueness: { case_sensitive: false }
end
</pre>
</div>
<div class="warning"><p>注意,有些数据库的设置是,查询时不区分大小写。</p></div><p>默认的错误消息是“has already been taken”。</p><h4 id="validates_with">2.12 <code>validates_with</code>
</h4><p>这个帮助方法把记录交给其他的类做验证。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
</pre>
</div>
<div class="note"><p><code>record.errors[:base]</code> 中的错误针对整个对象,而不是特定的属性。</p></div><p><code>validates_with</code> 方法的参数是一个类,或一组类,用来做验证。<code>validates_with</code> 方法没有默认的错误消息。在做验证的类中要手动把错误添加到记录的错误集合中。</p><p>实现 <code>validate</code> 方法时,必须指定 <code>record</code> 参数,这是要做验证的记录。</p><p>和其他验证一样,<code>validates_with</code> 也可指定 <code>:if</code>、<code>:unless</code> 和 <code>:on</code> 选项。如果指定了其他选项,会包含在 <code>options</code> 中传递给做验证的类。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end
class Person < ActiveRecord::Base
validates_with GoodnessValidator, fields: [:first_name, :last_name]
end
</pre>
</div>
<p>注意,做验证的类在整个程序的生命周期内只会初始化一次,而不是每次验证时都初始化,所以使用实例变量时要特别小心。</p><p>如果做验证的类很复杂,必须要用实例变量,可以用纯粹的 Ruby 对象代替:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validate do |person|
GoodnessValidator.new(person).validate
end
end
class GoodnessValidator
def initialize(person)
@person = person
end
def validate
if some_complex_condition_involving_ivars_and_private_methods?
@person.errors[:base] << "This person is evil"
end
end
# ...
end
</pre>
</div>
<h4 id="validates_each">2.13 <code>validates_each</code>
</h4><p>这个帮助方法会把属性值传入代码库做验证,没有预先定义验证的方式,你应该在代码库中定义验证方式。要验证的每个属性都会传入块中做验证。在下面的例子中,我们确保名和姓都不能以小写字母开头:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
end
end
</pre>
</div>
<p>代码块的参数是记录,属性名和属性值。在代码块中可以做任何检查,确保数据合法。如果验证失败,要向模型添加一个错误消息,把数据标记为不合法。</p><h3 id="常用的验证选项">3 常用的验证选项</h3><p>常用的验证选项包括:</p><h4 id=":allow_nil">3.1 <code>:allow_nil</code>
</h4><p>指定 <code>:allow_nil</code> 选项后,如果要验证的值为 <code>nil</code> 就会跳过验证。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Coffee < ActiveRecord::Base
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }, allow_nil: true
end
</pre>
</div>
<h4 id=":allow_blank">3.2 <code>:allow_blank</code>
</h4><p><code>:allow_blank</code> 选项和 <code>:allow_nil</code> 选项类似。如果要验证的值为空(调用 <code>blank?</code> 方法,例如 <code>nil</code> 或空字符串),就会跳过验证。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Topic < ActiveRecord::Base
validates :title, length: { is: 5 }, allow_blank: true
end
Topic.create(title: "").valid? # => true
Topic.create(title: nil).valid? # => true
</pre>
</div>
<h4 id=":message">3.3 <code>:message</code>
</h4><p>前面已经介绍过,如果验证失败,会把 <code>:message</code> 选项指定的字符串添加到 <code>errors</code> 集合中。如果没指定这个选项,Active Record 会使用各种验证帮助方法的默认错误消息。</p><h4 id=":on">3.4 <code>:on</code>
</h4><p><code>:on</code> 选项指定什么时候做验证。所有内建的验证帮助方法默认都在保存时(新建记录或更新记录)做验证。如果想修改,可以使用 <code>on: :create</code>,指定只在创建记录时做验证;或者使用 <code>on: :update</code>,指定只在更新记录时做验证。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
# it will be possible to update email with a duplicated value
validates :email, uniqueness: true, on: :create
# it will be possible to create the record with a non-numerical age
validates :age, numericality: true, on: :update
# the default (validates on both create and update)
validates :name, presence: true
end
</pre>
</div>
<h3 id="严格验证">4 严格验证</h3><p>数据验证还可以使用严格模式,失败后会抛出 <code>ActiveModel::StrictValidationFailed</code> 异常。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: { strict: true }
end
Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
</pre>
</div>
<p>通过 <code>:strict</code> 选项,还可以指定抛出什么异常:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
Person.new.valid? # => TokenGenerationException: Token can't be blank
</pre>
</div>
<h3 id="条件验证">5 条件验证</h3><p>有时只有满足特定条件时做验证才说得通。条件可通过 <code>:if</code> 和 <code>:unless</code> 选项指定,这两个选项的值可以是 Symbol、字符串、<code>Proc</code> 或数组。<code>:if</code> 选项指定何时做验证。如果要指定何时不做验证,可以使用 <code>:unless</code> 选项。</p><h4 id="指定-symbol">5.1 指定 Symbol</h4><p><code>:if</code> 和 <code>:unless</code> 选项的值为 Symbol 时,表示要在验证之前执行对应的方法。这是最常用的设置方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
</pre>
</div>
<h4 id="指定字符串">5.2 指定字符串</h4><p><code>:if</code> 和 <code>:unless</code> 选项的值还可以是字符串,但必须是 Ruby 代码,传入 <code>eval</code> 方法中执行。当字符串表示的条件非常短时才应该使用这种形式。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :surname, presence: true, if: "name.nil?"
end
</pre>
</div>
<h4 id="指定-proc">5.3 指定 Proc</h4><p><code>:if</code> and <code>:unless</code> 选项的值还可以是 Proc。使用 Proc 对象可以在行间编写条件,不用定义额外的方法。这种形式最适合用在一行代码能表示的条件上。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Account < ActiveRecord::Base
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
</pre>
</div>
<h4 id="条件组合">5.4 条件组合</h4><p>有时同一个条件会用在多个验证上,这时可以使用 <code>with_options</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
end
end
</pre>
</div>
<p><code>with_options</code> 代码块中的所有验证都会使用 <code>if: :is_admin?</code> 这个条件。</p><h4 id="联合条件">5.5 联合条件</h4><p>另一方面,当多个条件规定验证是否应该执行时,可以使用数组。而且,同一个验证可以同时指定 <code>:if</code> 和 <code>:unless</code> 选项。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Computer < ActiveRecord::Base
validates :mouse, presence: true,
if: ["market.retail?", :desktop?]
unless: Proc.new { |c| c.trackpad.present? }
end
</pre>
</div>
<p>只有当 <code>:if</code> 选项的所有条件都返回 <code>true</code>,且 <code>:unless</code> 选项中的条件返回 <code>false</code> 时才会做验证。</p><h3 id="自定义验证方式">6 自定义验证方式</h3><p>如果内建的数据验证帮助方法无法满足需求时,可以选择自己定义验证使用的类或方法。</p><h4 id="自定义验证使用的类">6.1 自定义验证使用的类</h4><p>自定义的验证类继承自 <code>ActiveModel::Validator</code>,必须实现 <code>validate</code> 方法,传入的参数是要验证的记录,然后验证这个记录是否合法。自定义的验证类通过 <code>validates_with</code> 方法调用。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please!'
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
</pre>
</div>
<p>在自定义的验证类中验证单个属性,最简单的方法是集成 <code>ActiveModel::EachValidator</code> 类。此时,自定义的验证类中要实现 <code>validate_each</code> 方法。这个方法接受三个参数:记录,属性名和属性值。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, presence: true, email: true
end
</pre>
</div>
<p>如上面的代码所示,可以同时使用内建的验证方法和自定义的验证类。</p><h4 id="自定义验证使用的方法">6.2 自定义验证使用的方法</h4><p>还可以自定义方法验证模型的状态,如果验证失败,向 <code>errors</code> 集合添加错误消息。然后还要使用类方法 <code>validate</code> 注册这些方法,传入自定义验证方法名的 Symbol 形式。</p><p>类方法可以接受多个 Symbol,自定义的验证方法会按照注册的顺序执行。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Invoice < ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
if expiration_date.present? && expiration_date < Date.today
errors.add(:expiration_date, "can't be in the past")
end
end
def discount_cannot_be_greater_than_total_value
if discount > total_value
errors.add(:discount, "can't be greater than total value")
end
end
end
</pre>
</div>
<p>默认情况下,每次调用 <code>valid?</code> 方法时都会执行自定义的验证方法。使用 <code>validate</code> 方法注册自定义验证方法时可以设置 <code>:on</code> 选项,执行什么时候运行。<code>:on</code> 的可选值为 <code>:create</code> 和 <code>:update</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Invoice < ActiveRecord::Base
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
</pre>
</div>
<h3 id="处理验证错误">7 处理验证错误</h3><p>除了前面介绍的 <code>valid?</code> 和 <code>invalid?</code> 方法之外,Rails 还提供了很多方法用来处理 <code>errors</code> 集合,以及查询对象的合法性。</p><p>下面介绍其中一些常用的方法。所有可用的方法请查阅 <code>ActiveModel::Errors</code> 的文档。</p><h4 id="errors">7.1 <code>errors</code>
</h4><p><code>ActiveModel::Errors</code> 的实例包含所有的错误。其键是每个属性的名字,值是一个数组,包含错误消息字符串。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true, length: { minimum: 3 }
end
person = Person.new
person.valid? # => false
person.errors.messages
# => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
person = Person.new(name: "John Doe")
person.valid? # => true
person.errors.messages # => {}
</pre>
</div>
<h4 id="处理验证错误-errors[]">7.2 <code>errors[]</code>
</h4><p><code>errors[]</code> 用来获取某个属性上的错误消息,返回结果是一个由该属性所有错误消息字符串组成的数组,每个字符串表示一个错误消息。如果字段上没有错误,则返回空数组。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true, length: { minimum: 3 }
end
person = Person.new(name: "John Doe")
person.valid? # => true
person.errors[:name] # => []
person = Person.new(name: "JD")
person.valid? # => false
person.errors[:name] # => ["is too short (minimum is 3 characters)"]
person = Person.new
person.valid? # => false
person.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]
</pre>
</div>
<h4 id="errors.add">7.3 <code>errors.add</code>
</h4><p><code>add</code> 方法可以手动添加某属性的错误消息。使用 <code>errors.full_messages</code> 或 <code>errors.to_a</code> 方法会以最终显示给用户的形式显示错误消息。这些错误消息的前面都会加上字段名可读形式(并且首字母大写)。<code>add</code> 方法接受两个参数:错误消息要添加到的字段名和错误消息本身。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors.add(:name, "cannot contain the characters !@#%*()_-+=")
end
end
person = Person.create(name: "!@#")
person.errors[:name]
# => ["cannot contain the characters !@#%*()_-+="]
person.errors.full_messages
# => ["Name cannot contain the characters !@#%*()_-+="]
</pre>
</div>
<p>还有一种方法可以实现同样地效果,使用 <code>[]=</code> 设置方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:name] = "cannot contain the characters !@#%*()_-+="
end
end
person = Person.create(name: "!@#")
person.errors[:name]
# => ["cannot contain the characters !@#%*()_-+="]
person.errors.to_a
# => ["Name cannot contain the characters !@#%*()_-+="]
</pre>
</div>
<h4 id="errors[:base]">7.4 <code>errors[:base]</code>
</h4><p>错误消息可以添加到整个对象上,而不是针对某个属性。如果不想管是哪个属性导致对象不合法,只想把对象标记为不合法状态,就可以使用这个方法。<code>errors[:base]</code> 是个数组,可以添加字符串作为错误消息。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:base] << "This person is invalid because ..."
end
end
</pre>
</div>
<h4 id="errors.clear">7.5 <code>errors.clear</code>
</h4><p>如果想清除 <code>errors</code> 集合中的所有错误消息,可以使用 <code>clear</code> 方法。当然了,在不合法的对象上调用 <code>errors.clear</code> 方法后,这个对象还是不合法的,虽然 <code>errors</code> 集合为空了,但下次调用 <code>valid?</code> 方法,或调用其他把对象存入数据库的方法时, 会再次进行验证。如果任何一个验证失败了,<code>errors</code> 集合中就再次出现值了。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true, length: { minimum: 3 }
end
person = Person.new
person.valid? # => false
person.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]
person.errors.clear
person.errors.empty? # => true
p.save # => false
p.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]
</pre>
</div>
<h4 id="errors.size">7.6 <code>errors.size</code>
</h4><p><code>size</code> 方法返回对象上错误消息的总数。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Person < ActiveRecord::Base
validates :name, presence: true, length: { minimum: 3 }
end
person = Person.new
person.valid? # => false
person.errors.size # => 2
person = Person.new(name: "Andrea", email: "andrea@example.com")
person.valid? # => true
person.errors.size # => 0
</pre>
</div>
<h3 id="在视图中显示验证错误">8 在视图中显示验证错误</h3><p>在模型中加入数据验证后,如果在表单中创建模型,出错时,你或许想把错误消息显示出来。</p><p>因为每个程序显示错误消息的方式不同,所以 Rails 没有直接提供用来显示错误消息的视图帮助方法。不过,Rails 提供了这么多方法用来处理验证,自己编写一个也不难。使用脚手架时,Rails 会在生成的 <code>_form.html.erb</code> 中加入一些 ERB 代码,显示模型错误消息的完整列表。</p><p>假设有个模型对象存储在实例变量 <code>@post</code> 中,视图的代码可以这么写:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
<% if @post.errors.any? %>
<div id="error_explanation">