-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathchapter4.html
1630 lines (1381 loc) · 135 KB
/
chapter4.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-CN">
<head>
<meta charset="UTF-8"/>
<title>Ruby on Rails 教程 - 第 4 章 Rails 背后的 Ruby</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="最好的 Ruby on Rails 入门教程"/>
<meta name="keywords" content="ruby, rails, tutorial"/>
<meta name="author" content="Michael Hartl"/>
<meta name="translator" content="安道"/>
<meta name="generator" content="persie 0.0.1.beta.3"/>
<link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/twitter-bootstrap/3.2.0/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/font-awesome/4.2.0/css/font-awesome.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/style.css"/>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/twitter-bootstrap/3.2.0/js/collapse.min.js"></script>
<script type="text/javascript" src="assets/global.js"></script>
</head>
<body>
<header class="navbar navbar-default navbar-fixed-top navbar-book">
<div class="container">
<div class="navbar-header">
<a href="http://railstutorial-china.org" class="navbar-brand">Ruby on Rails 教程</a>
<button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target=".book-navbar-collapse">
<span class="sr-only">导航</span>
<i class="fa fa-bars"></i>
</button>
<a href="http://railstutorial-china.org/#purchase" id="navbar-purchase-xs" class="btn btn-warning navbar-btn visible-xs collapsed-purchase-btn">购买</a>
</div>
<nav class="collapse navbar-collapse book-navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="http://railstutorial-china.org" title="首页">首页</a></li>
<li class="active"><a href="http://railstutorial-china.org/read/" title="在线阅读">阅读</a></li>
<li><a href="http://railstutorial-china.org/blog/" title="最新消息">博客</a></li>
<li><a href="https://selfstore.io/products/189/topics" title="论坛">论坛</a></li>
<li class="hidden-xs"><div><a href="http://railstutorial-china.org/#purchase" id="navbar-purchase" class="btn btn-warning navbar-btn" title="购买电子书">购买</a></div></li>
</ul>
</nav>
</div>
</header>
<div class="content">
<div class="container">
<div class="row">
<div class="col-lg-offset-2 col-lg-8">
<div class="alert alert-warning">
<p>在线版的内容可能落后于电子书,如果想及时获得更新,请<a href="http://railstutorial-china.org/#purchase" title="购买电子书">购买电子书</a>。</p>
</div>
<article class="article">
<section data-type="chapter" id="rails-flavored-ruby">
<h1><span class="title-label">第 4 章</span> Rails 背后的 Ruby</h1>
<p>有了<a href="chapter3.html#mostly-static-pages">第 3 章</a>的例子做铺垫,本章要介绍一些对 Rails 来说很重要的 Ruby 知识。Ruby 语言的知识点很多,不过对 Rails 开发者而言需要掌握的很少。我们采用的方式有别于常规的 Ruby 学习过程。本章的目标是,不管你有没有 Ruby 编程经验,都得让你掌握编写 Rails 应用所需的 Ruby 知识。这一章的内容很多,第一次阅读不能完全掌握也没关系。后续的章节我会经常提到本章的内容。</p>
<section data-type="sect1" id="motivation">
<h1><span class="title-label">4.1</span> 导言</h1>
<p>从前一章得知,即使完全不懂 Ruby 语言,我们也可以创建 Rails 应用的骨架,以及编写测试。我们依赖于书中提供的测试代码,得到错误信息,然后让测试组件通过。但是我们不能总是这样,所以这一章暂时不讲网站开发,而要正视我们的短肋——Ruby 语言。</p>
<p>前一章末尾我们修改了几乎是静态内容的页面,让它们使用 Rails 布局,去除视图中的重复。我们使用的布局如<a href="#listing-application-layout-redux">代码清单 4.1</a> 所示(和<a href="chapter3.html#listing-application-layout">代码清单 3.32</a> 一样)。</p>
<div id="listing-application-layout-redux" data-type="listing">
<h5><span class="title-label">代码清单 4.1</span>:演示应用的网站布局</h5>
<div class="source-file">app/views/layouts/application.html.erb</div>
<div class="highlight language-erb"><pre><span class="x"><!DOCTYPE html></span>
<span class="x"><html></span>
<span class="x"> <head></span>
<span class="x"> <title></span><span class="cp"><%=</span> <span class="k">yield</span><span class="p">(</span><span class="ss">:title</span><span class="p">)</span> <span class="cp">%></span><span class="x"> | Ruby on Rails Tutorial Sample App</title></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s1">'application'</span><span class="p">,</span> <span class="ss">media</span><span class="p">:</span> <span class="s1">'all'</span><span class="p">,</span>
<span class="s1">'data-turbolinks-track'</span> <span class="o">=></span> <span class="kp">true</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s1">'application'</span><span class="p">,</span> <span class="s1">'data-turbolinks-track'</span> <span class="o">=></span> <span class="kp">true</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="n">csrf_meta_tags</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </head></span>
<span class="x"> <body></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </body></span>
<span class="x"></html></span>
</pre></div>
</div>
<p>我们把注意力集中在上述代码中的这一行:</p>
<div data-type="listing">
<div class="highlight language-erb"><pre><span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"application"</span><span class="p">,</span> <span class="ss">media</span><span class="p">:</span> <span class="s2">"all"</span><span class="p">,</span>
<span class="s2">"data-turbolinks-track"</span> <span class="o">=></span> <span class="kp">true</span> <span class="cp">%></span><span class="x"></span>
</pre></div>
</div>
<p>这行代码使用 Rails 内置的方法 <code>stylesheet_link_tag</code>(详细信息参见 <a href="http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-stylesheet_link_tag">Rails API 文档</a>),在所有<a href="http://www.w3.org/TR/CSS2/media.html">媒介类型</a>中引入 <code>application.css</code>。对有经验的 Rails 开发者来说,这行代码看起来很简单,但是其中至少有四个 Ruby 知识点可能会让你困惑:内置的 Rails 方法,调用方法时不用括号,符号和哈希。这几点本章都会介绍。</p>
<p>Rails 除了提供很多内置的方法供我们在视图中使用之外,还允许我们自己定义。自己定义的方法叫辅助方法(helper)。为了说明如何自己定义辅助方法,我们来看看<a href="#listing-application-layout-redux">代码清单 4.1</a> 中标题那一行:</p>
<div data-type="listing">
<div class="highlight language-erb"><pre><span class="cp"><%=</span> <span class="k">yield</span><span class="p">(</span><span class="ss">:title</span><span class="p">)</span> <span class="cp">%></span><span class="x"> | Ruby on Rails Tutorial Sample App</span>
</pre></div>
</div>
<p>这行代码要求每个视图都要使用 <code>provide</code> 方法定义标题,例如:</p>
<div data-type="listing">
<div class="highlight language-erb"><pre><span class="cp"><%</span> <span class="n">provide</span><span class="p">(</span><span class="ss">:title</span><span class="p">,</span> <span class="s2">"Home"</span><span class="p">)</span> <span class="cp">%></span><span class="x"></span>
<span class="x"><h1>Sample App</h1></span>
<span class="x"><p></span>
<span class="x"> This is the home page for the</span>
<span class="x"> <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a></span>
<span class="x"> sample application.</span>
<span class="x"></p></span>
</pre></div>
</div>
<p>那么,如果我们不提供标题会怎样呢?标题一般都包含一个公共部分,如果想更具体些,可以再加上变动的部分。我们在布局中用了个小技巧,基本上已经实现了这样的标题。如果在视图中不调用 <code>provide</code> 方法,也就是不提供变动的部分,那么得到的标题会变成:</p>
<div data-type="listing">
<div class="highlight language-text"><pre> | Ruby on Rails Tutorial Sample App
</pre></div>
</div>
<p>标题中有公共部分,但前面还显示了竖线。</p>
<p>为了解决这个问题,我们要自定义一个辅助方法,命名为 <code>full_title</code>。如果视图中没有定义页面的标题,<code>full_title</code> 返回标题的公共部分,即“Ruby on Rails Tutorial Sample App”;如果定义了,则在变动部分后面加上一个竖线,如<a href="#listing-title-helper">代码清单 4.2</a> 所示。<sup>[<a id="fn-ref-1" href="#fn-1">1</a>]</sup></p>
<div id="listing-title-helper" data-type="listing">
<h5><span class="title-label">代码清单 4.2</span>:定义 <code>full_title</code> 辅助方法</h5>
<div class="source-file">app/helpers/application_helper.rb</div>
<div class="highlight language-ruby"><pre><span class="k">module</span> <span class="nn">ApplicationHelper</span>
<span class="c1"># 根据所在的页面返回完整的标题</span>
<span class="k">def</span> <span class="nf">full_title</span><span class="p">(</span><span class="n">page_title</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span>
<span class="n">base_title</span> <span class="o">=</span> <span class="s2">"Ruby on Rails Tutorial Sample App"</span>
<span class="k">if</span> <span class="n">page_title</span><span class="o">.</span><span class="n">empty?</span>
<span class="n">base_title</span>
<span class="k">else</span>
<span class="n">page_title</span> <span class="o">+</span> <span class="s2">" | "</span> <span class="o">+</span> <span class="n">base_title</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>现在,这个辅助方法定义好了,我们可以用它来简化布局。把下面这行:</p>
<div data-type="listing">
<div class="highlight language-erb"><pre><span class="x"><title></span><span class="cp"><%=</span> <span class="k">yield</span><span class="p">(</span><span class="ss">:title</span><span class="p">)</span> <span class="cp">%></span><span class="x"> | Ruby on Rails Tutorial Sample App</title></span>
</pre></div>
</div>
<p>改成:</p>
<div data-type="listing">
<div class="highlight language-erb"><pre><span class="x"><title></span><span class="cp"><%=</span> <span class="n">full_title</span><span class="p">(</span><span class="k">yield</span><span class="p">(</span><span class="ss">:title</span><span class="p">))</span> <span class="cp">%></span><span class="x"></title></span>
</pre></div>
</div>
<p>如<a href="#listing-application-layout-full-title">代码清单 4.3</a> 所示。</p>
<div id="listing-application-layout-full-title" data-type="listing">
<h5><span class="title-label">代码清单 4.3</span>:使用 <code>full_title</code> 辅助方法的网站布局 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/layouts/application.html.erb</div>
<div class="highlight language-erb"><pre><span class="x"><!DOCTYPE html></span>
<span class="x"><html></span>
<span class="x"> <head></span>
<span class="hll"><span class="x"> <title></span><span class="cp"><%=</span> <span class="n">full_title</span><span class="p">(</span><span class="k">yield</span><span class="p">(</span><span class="ss">:title</span><span class="p">))</span> <span class="cp">%></span><span class="x"></title></span>
</span><span class="x"> </span><span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s1">'application'</span><span class="p">,</span> <span class="ss">media</span><span class="p">:</span> <span class="s1">'all'</span><span class="p">,</span>
<span class="s1">'data-turbolinks-track'</span> <span class="o">=></span> <span class="kp">true</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s1">'application'</span><span class="p">,</span> <span class="s1">'data-turbolinks-track'</span> <span class="o">=></span> <span class="kp">true</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="n">csrf_meta_tags</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </head></span>
<span class="x"> <body></span>
<span class="x"> </span><span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span><span class="x"></span>
<span class="x"> </body></span>
<span class="x"></html></span>
</pre></div>
</div>
<p>为了让这个辅助方法起作用,我们要在首页的视图中把不必要的单词“Home”删掉,只保留标题的公共部分。首先,我们要修改测试代码,如<a href="#listing-home-base-title-spec">代码清单 4.4</a> 所示,确认标题中没有 <code>"Home"</code>。</p>
<div id="listing-home-base-title-spec" data-type="listing">
<h5><span class="title-label">代码清单 4.4</span>:修改首页的标题测试 <span class="red">RED</span></h5>
<div class="source-file">test/controllers/static_pages_controller_test.rb</div>
<div class="highlight language-ruby"><pre><span class="nb">require</span> <span class="s1">'test_helper'</span>
<span class="k">class</span> <span class="nc">StaticPagesControllerTest</span> <span class="o"><</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">TestCase</span>
<span class="nb">test</span> <span class="s2">"should get home"</span> <span class="k">do</span>
<span class="n">get</span> <span class="ss">:home</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="hll"> <span class="n">assert_select</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"Ruby on Rails Tutorial Sample App"</span>
</span> <span class="k">end</span>
<span class="nb">test</span> <span class="s2">"should get help"</span> <span class="k">do</span>
<span class="n">get</span> <span class="ss">:help</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="n">assert_select</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"Help | Ruby on Rails Tutorial Sample App"</span>
<span class="k">end</span>
<span class="nb">test</span> <span class="s2">"should get about"</span> <span class="k">do</span>
<span class="n">get</span> <span class="ss">:about</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="n">assert_select</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"About | Ruby on Rails Tutorial Sample App"</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>我们要运行测试组件,确认有一个测试失败:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 4.5</span>:<strong class="red">RED</strong></h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle <span class="nb">exec </span>rake <span class="nb">test</span>
<span class="m">3</span> tests, <span class="m">6</span> assertions, <span class="m">1</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
<p>为了让测试通过,我们要把首页视图中的 <code>provide</code> 那行删除,如<a href="#listing-home-page-base-title">代码清单 4.6</a> 所示。</p>
<div id="listing-home-page-base-title" data-type="listing">
<h5><span class="title-label">代码清单 4.6</span>:没定义页面标题的首页视图 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-erb"><pre><span class="x"><h1>Sample App</h1></span>
<span class="x"><p></span>
<span class="x"> This is the home page for the</span>
<span class="x"> <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a></span>
<span class="x"> sample application.</span>
<span class="x"></p></span>
</pre></div>
</div>
<p>现在测试应该可以通过了:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 4.7</span>:<strong class="green">GREEN</strong></h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle <span class="nb">exec </span>rake <span class="nb">test</span>
</pre></div>
</div>
<p>(注意,之前运行 <code>rake test</code> 时都显示了通过和失败测试的数量,为了行文简洁,从这以后都会省略这些数据。)</p>
<p>和引入应用的样式表那行代码一样,<a href="#listing-title-helper">代码清单 4.2</a> 的内容对有经验的 Rails 开发者来说也很简单,但其中很多重要的 Ruby 知识:模块,方法定义,可选的方法参数,注释,本地变量赋值,布尔值,流程控制,字符串拼接和返回值。本章会一一介绍这些知识。</p>
</section>
<section data-type="sect1" id="strings-and-methods">
<h1><span class="title-label">4.2</span> 字符串和方法</h1>
<p>我们学习 Ruby 主要使用的工具是 Rails 控制台,它是用来和 Rails 应用交互的命令行工具,在 <a href="chapter2.html#a-user-has-many-microposts">2.3.3 节</a>介绍过。控制台基于 Ruby 的交互程序(<code>irb</code>)开发,因此能使用 Ruby 语言的全部功能。(<a href="#a-controller-class">4.4.4 节</a>会介绍,控制台还可以访问 Rails 环境。)</p>
<p>如果使用云端 IDE,我建议使用一些 irb 配置参数。使用简单的 <code>nano</code> 文本编辑器,把<a href="#listing-irbrc">代码清单 4.8</a> 中的内容写入家目录里的 <code>.irbrc</code> 文件:<sup>[<a id="fn-ref-2" href="#fn-2">2</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>nano ~/.irbrc
</pre></div>
</div>
<p><a href="#listing-irbrc">代码清单 4.8</a> 中的内容作用是简化 irb 提示符,以及禁用一些烦人的自动缩进行为。</p>
<div id="listing-irbrc" data-type="listing">
<h5><span class="title-label">代码清单 4.8</span>:添加一些 irb 配置</h5>
<div class="source-file">~/.irbrc</div>
<div class="highlight language-ruby"><pre><span class="no">IRB</span><span class="o">.</span><span class="n">conf</span><span class="o">[</span><span class="ss">:PROMPT_MODE</span><span class="o">]</span> <span class="o">=</span> <span class="ss">:SIMPLE</span>
<span class="no">IRB</span><span class="o">.</span><span class="n">conf</span><span class="o">[</span><span class="ss">:AUTO_INDENT_MODE</span><span class="o">]</span> <span class="o">=</span> <span class="kp">false</span>
</pre></div>
</div>
<p>不管加没加这些设置,控制器的启动方法都是在命令行中执行下面的命令:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="go">$ rails console</span>
<span class="go">Loading development environment</span>
<span class="go">>></span>
</pre></div>
</div>
<p>默认情况下,控制台在开发环境中启动,这是 Rails 定义的三个独立环境之一(另外两个是测试环境和生产环境)。这三个环境的区别对本章不重要,<a href="chapter7.html#debug-and-rails-environments">7.1.1 节</a>会详细介绍。</p>
<p>控制台是学习的好工具,你可以尽情地探索它的用法。别担心,你(几乎)不会破坏任何东西。如果在控制台中遇到了问题,可以按 Ctrl-C 键结束当前执行的操作,或者按 Ctrl-D 键直接退出。在阅读本章后续内容的过程中,你会发现查阅 <a href="http://ruby-doc.org/">Ruby API</a> 很有帮助。API 中有很多信息(或许太多了),例如,如果想进一步了解 Ruby 字符串,可以查看 <code>String</code> 类的文档。</p>
<section data-type="sect2" id="comments">
<h2><span class="title-label">4.2.1</span> 注释</h2>
<p>Ruby 中的注释以井号 <code>#</code>(也叫“哈希符号”,或者更诗意一点,叫“散列字元”)开头,一直到行尾结束。Ruby 会忽略注释,但是注释对人类读者(往往也包括代码的编写者)很有用。在下面的代码中</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="c1"># 根据所在的页面返回完整的标题</span>
<span class="k">def</span> <span class="nf">full_title</span><span class="p">(</span><span class="n">page_title</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="k">end</span>
</pre></div>
</div>
<p>第一行就是注释,说明其后方法的作用。</p>
<p>在控制台中一般不用写注释,不过为了说明代码的作用,我会按照下面的形式加上注释,例如:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="go">$ rails console</span>
<span class="gp">>> </span><span class="mi">17</span> <span class="o">+</span> <span class="mi">42</span> <span class="c1"># 整数加法运算</span>
<span class="go">=> 59</span>
</pre></div>
</div>
<p>阅读的过程中,在控制台中输入或者复制粘贴命令时,如果愿意你可以不加注释,反正控制台会忽略注释。</p>
</section>
<section data-type="sect2" id="strings">
<h2><span class="title-label">4.2.2</span> 字符串</h2>
<p>对 Web 应用来说,字符串或许是最重要的数据结构,因为网页的内容就是从服务器发送给浏览器的字符串。我们先在控制台中体验一下字符串:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="go">$ rails console</span>
<span class="gp">>> </span><span class="s2">""</span> <span class="c1"># 空字符串</span>
<span class="go">=> ""</span>
<span class="gp">>> </span><span class="s2">"foo"</span> <span class="c1"># 非空字符串</span>
<span class="go">=> "foo"</span>
</pre></div>
</div>
<p>这些是字符串字面量,使用双引号(<code>"</code>)创建。控制台回显的是每一行的计算结果,本例中,字符串字面量的结果就是字符串本身。</p>
<p>我们还可以使用 <code>+</code> 号连接字符串:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"foo"</span> <span class="o">+</span> <span class="s2">"bar"</span> <span class="c1"># 字符串连接</span>
<span class="go">=> "foobar"</span>
</pre></div>
</div>
<p><code>"foo"</code> 连接 <code>"bar"</code> 得到的结果是字符串 <code>"foobar"</code>。<sup>[<a id="fn-ref-3" href="#fn-3">3</a>]</sup></p>
<p>另一种创建字符串的方式是通过特殊的句法 <code>#{}</code> 进行插值操作:<sup>[<a id="fn-ref-4" href="#fn-4">4</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">first_name</span> <span class="o">=</span> <span class="s2">"Michael"</span> <span class="c1"># 变量赋值</span>
<span class="go">=> "Michael"</span>
<span class="gp">>> </span><span class="s2">"</span><span class="si">#{</span><span class="n">first_name</span><span class="si">}</span><span class="s2"> Hartl"</span> <span class="c1"># 字符串插值</span>
<span class="go">=> "Michael Hartl"</span>
</pre></div>
</div>
<p>我们先把 <code>"Michael"</code> 赋值给变量 <code>first_name</code>,然后将其插入字符串 <code>"#{first_name} Hartl"</code> 中。我们也可以把两个字符串都赋值给变量:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">first_name</span> <span class="o">=</span> <span class="s2">"Michael"</span>
<span class="go">=> "Michael"</span>
<span class="gp">>> </span><span class="n">last_name</span> <span class="o">=</span> <span class="s2">"Hartl"</span>
<span class="go">=> "Hartl"</span>
<span class="gp">>> </span><span class="n">first_name</span> <span class="o">+</span> <span class="s2">" "</span> <span class="o">+</span> <span class="n">last_name</span> <span class="c1"># 字符串连接,中间加了空格</span>
<span class="go">=> "Michael Hartl"</span>
<span class="gp">>> </span><span class="s2">"</span><span class="si">#{</span><span class="n">first_name</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">last_name</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># 作用相同的插值</span>
<span class="go">=> "Michael Hartl"</span>
</pre></div>
</div>
<p>注意,最后两个表达式的作用相同,不过我倾向于使用插值的方式。在两个字符串中间加入一个空格(<code>" "</code>)显得很别扭。</p>
<section data-type="sect3" id="printing">
<h3>打印字符串</h3>
<p>打印字符串最常用的 Ruby 方法是 <code>puts</code>(读作“put ess”,意思是“打印字符串”):</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="nb">puts</span> <span class="s2">"foo"</span> <span class="c1"># 打印字符串</span>
<span class="go">foo</span>
<span class="go">=> nil</span>
</pre></div>
</div>
<p><code>puts</code> 方法还有一个副作用:<code>puts "foo"</code> 先把字符串打印到屏幕上,然后返回<a href="http://www.answers.com/nil">空值字面量</a>——<code>nil</code> 在 Ruby 中是个特殊值,表示“什么都没有”。(为了行文简洁,后续内容会省略 <code>⇒ nil</code>。)</p>
<p>从前面的例子可以看出,<code>puts</code> 方法会自动在输出的字符串后面加入换行符 <code>\n</code>。功能类似的 <code>print</code> 方法则不会:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="nb">print</span> <span class="s2">"foo"</span> <span class="c1"># 打印字符串(和 puts 作用一样,但没添加换行符)</span>
<span class="go">foo=> nil</span>
<span class="gp">>> </span><span class="nb">print</span> <span class="s2">"foo</span><span class="se">\n</span><span class="s2">"</span> <span class="c1"># 和 puts "foo" 一样</span>
<span class="go">foo</span>
<span class="go">=> nil</span>
</pre></div>
</div>
</section>
<section data-type="sect3" id="single-quoted-strings">
<h3>单引号字符串</h3>
<p>目前介绍的例子都使用双引号创建字符串,不过 Ruby 也支持用单引号创建字符串。大多数情况下这两种字符串的效果是一样的:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s1">'foo'</span> <span class="c1"># 单引号创建的字符串</span>
<span class="go">=> "foo"</span>
<span class="gp">>> </span><span class="s1">'foo'</span> <span class="o">+</span> <span class="s1">'bar'</span>
<span class="go">=> "foobar"</span>
</pre></div>
</div>
<p>不过,两种方式之间有个重要的区别:Ruby 不会对单引号字符串进行插值操作:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s1">'#{foo} bar'</span> <span class="c1"># 单引号字符串不能进行插值操作</span>
<span class="go">=> "\#{foo} bar"</span>
</pre></div>
</div>
<p>注意,控制台返回的是双引号字符串,因此要使用反斜线转义特殊字符,例如 <code>#</code>。</p>
<p>如果双引号字符串可以做单引号能做的所有事,而且还能进行插值,那么单引号字符串存在的意义是什么呢?单引号字符串的用处在于它们真的就是字面值,只包含你输入的字符。例如,反斜线在很多系统中都很特殊,例如在换行符(<code>\n</code>)中。如果有一个变量需要包含一个反斜线,使用单引号就很简单:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s1">'\n'</span> <span class="c1"># 反斜线和 n 字面值</span>
<span class="go">=> "\\n"</span>
</pre></div>
</div>
<p>和前面的 <code>#</code> 字符一样,Ruby 要使用一个额外的反斜线来转义反斜线——在双引号字符串中,要表达一个反斜线就要使用两个反斜线。对简单的例子来说,这省不了多少事,但是如果有很多需要转义的字符就显得出它的作用了:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s1">'Newlines (\n) and tabs (\t) both use the backslash character \.'</span>
<span class="go">=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."</span>
</pre></div>
</div>
<p>最后,有一点要注意,单双引号基本上可以互换使用,源码中经常混用,没有章法可循,对此我们只能默默接受——“欢迎进入 Ruby 世界”!</p>
</section>
</section>
<section data-type="sect2" id="objects-and-message-passing">
<h2><span class="title-label">4.2.3</span> 对象和消息传送</h2>
<p>在 Ruby 中,一切皆对象,包括字符串和 <code>nil</code> 都是。我们会在 <a href="#class-inheritance">4.4.2 节</a>介绍对象技术层面上的意义,不过一般很难通过阅读一本书就理解对象,你要多看一些例子才能建立对对象的感性认识。</p>
<p>对象的作用说起来很简单:响应消息。例如,一个字符串对象可以响应 <code>length</code> 这个消息,返回字符串中包含的字符数量:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"foobar"</span><span class="o">.</span><span class="n">length</span> <span class="c1"># 把 length 消息传给字符串</span>
<span class="go">=> 6</span>
</pre></div>
</div>
<p>一般来说,传给对象的消息是“方法”,是在这个对象上定义的函数。<sup>[<a id="fn-ref-5" href="#fn-5">5</a>]</sup>字符串还可以响应 <code>empty?</code> 方法:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"foobar"</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">=> false</span>
<span class="gp">>> </span><span class="s2">""</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">=> true</span>
</pre></div>
</div>
<p>注意,<code>empty?</code> 方法末尾有个问号,这是 Ruby 的约定,说明方法的返回值是布尔值,即 <code>true</code> 或 <code>false</code>。布尔值在流程控制中特别有用:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">s</span> <span class="o">=</span> <span class="s2">"foobar"</span>
<span class="gp">>> </span><span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">empty?</span>
<span class="gp">>> </span> <span class="s2">"The string is empty"</span>
<span class="gp">>> </span><span class="k">else</span>
<span class="gp">>> </span> <span class="s2">"The string is nonempty"</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">=> "The string is nonempty"</span>
</pre></div>
</div>
<p>如果分支很多,可以使用 <code>elsif</code>(<code>else</code> + <code>if</code>):</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">nil?</span>
<span class="gp">>> </span> <span class="s2">"The variable is nil"</span>
<span class="gp">>> </span><span class="k">elsif</span> <span class="n">s</span><span class="o">.</span><span class="n">empty?</span>
<span class="gp">>> </span> <span class="s2">"The string is empty"</span>
<span class="gp">>> </span><span class="k">elsif</span> <span class="n">s</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="s2">"foo"</span><span class="p">)</span>
<span class="gp">>> </span> <span class="s2">"The string includes 'foo'"</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">=> "The string includes 'foo'"</span>
</pre></div>
</div>
<p>布尔值还可以使用 <code>&&</code>(和)、<code>||</code>(或)和 <code>!</code>(非)操作符结合在一起使用:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">x</span> <span class="o">=</span> <span class="s2">"foo"</span>
<span class="go">=> "foo"</span>
<span class="gp">>> </span><span class="n">y</span> <span class="o">=</span> <span class="s2">""</span>
<span class="go">=> ""</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="s2">"Both strings are empty"</span> <span class="k">if</span> <span class="n">x</span><span class="o">.</span><span class="n">empty?</span> <span class="o">&&</span> <span class="n">y</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">=> nil</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="s2">"One of the strings is empty"</span> <span class="k">if</span> <span class="n">x</span><span class="o">.</span><span class="n">empty?</span> <span class="o">||</span> <span class="n">y</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">"One of the strings is empty"</span>
<span class="go">=> nil</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="s2">"x is not empty"</span> <span class="k">if</span> <span class="o">!</span><span class="n">x</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">"x is not empty"</span>
<span class="go">=> nil</span>
</pre></div>
</div>
<p>因为在 Ruby 中一切都是对象,那么 <code>nil</code> 也是对象,所以它也可以响应方法。举个例子,<code>to_s</code> 方法基本上可以把任何对象转换成字符串:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="kp">nil</span><span class="o">.</span><span class="n">to_s</span>
<span class="go">=> ""</span>
</pre></div>
</div>
<p>结果显然是个空字符串,我们可以通过下面的方法串联(chain)验证这一点:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="kp">nil</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">NoMethodError: undefined method `empty?' for nil:NilClass</span>
<span class="gp">>> </span><span class="kp">nil</span><span class="o">.</span><span class="n">to_s</span><span class="o">.</span><span class="n">empty?</span> <span class="c1"># 消息串联</span>
<span class="go">=> true</span>
</pre></div>
</div>
<p>我们看到,<code>nil</code> 对象本身无法响应 <code>empty?</code> 方法,但是 <code>nil.to_s</code> 可以。</p>
<p>有一个特殊的方法可以测试对象是否为空,你或许能猜到这个方法:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"foo"</span><span class="o">.</span><span class="n">nil?</span>
<span class="go">=> false</span>
<span class="gp">>> </span><span class="s2">""</span><span class="o">.</span><span class="n">nil?</span>
<span class="go">=> false</span>
<span class="gp">>> </span><span class="kp">nil</span><span class="o">.</span><span class="n">nil?</span>
<span class="go">=> true</span>
</pre></div>
</div>
<p>下面的代码</p>
<div data-type="listing">
<div class="highlight language-sh"><pre>puts <span class="s2">"x is not empty"</span> <span class="k">if</span> !x.empty?
</pre></div>
</div>
<p>演示了 <code>if</code> 关键字的另一种用法:你可以编写一个当且只当 <code>if</code> 后面的表达式为真值时才执行的语句。还有个对应的 <code>unless</code> 关键字也可以这么用:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">string</span> <span class="o">=</span> <span class="s2">"foobar"</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="s2">"The string '</span><span class="si">#{</span><span class="n">string</span><span class="si">}</span><span class="s2">' is nonempty."</span> <span class="k">unless</span> <span class="n">string</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">The string 'foobar' is nonempty.</span>
<span class="go">=> nil</span>
</pre></div>
</div>
<p>我们需要注意一下 <code>nil</code> 对象的特殊性,除了 <code>false</code> 本身之外,所有 Ruby 对象中它是唯一一个布尔值为“假”的。我们可以使用 <code>!!</code>(读作“bang bang”)对对象做两次取反操作,把对象转换成布尔值:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="o">!!</span><span class="kp">nil</span>
<span class="go">=> false</span>
</pre></div>
</div>
<p>除此之外,其他所有 Ruby 对象都是“真”值,数字 0 也是:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="o">!!</span><span class="mi">0</span>
<span class="go">=> true</span>
</pre></div>
</div>
</section>
<section data-type="sect2" id="method-definitions">
<h2><span class="title-label">4.2.4</span> 定义方法</h2>
<p>在控制台中,我们可以像定义 <code>home</code> 动作(<a href="chapter3.html#listing-static-pages-controller">代码清单 3.6</a>)和 <code>full_title</code> 辅助方法(<a href="#listing-title-helper">代码清单 4.2</a>)一样定义方法。(在控制台中定义方法有点麻烦,我们一般会在文件中定义,这里只是为了演示。)例如,我们要定义一个名为 <code>string_message</code> 的方法,接受一个参数,返回值取决于参数是否为空:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="k">def</span> <span class="nf">string_message</span><span class="p">(</span><span class="n">str</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span>
<span class="gp">>> </span> <span class="k">if</span> <span class="n">str</span><span class="o">.</span><span class="n">empty?</span>
<span class="gp">>> </span> <span class="s2">"It's an empty string!"</span>
<span class="gp">>> </span> <span class="k">else</span>
<span class="gp">>> </span> <span class="s2">"The string is nonempty."</span>
<span class="gp">>> </span> <span class="k">end</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">=> :string_message</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="n">string_message</span><span class="p">(</span><span class="s2">"foobar"</span><span class="p">)</span>
<span class="go">The string is nonempty.</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="n">string_message</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
<span class="go">It's an empty string!</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="n">string_message</span>
<span class="go">It's an empty string!</span>
</pre></div>
</div>
<p>如最后一个命令所示,可以完全不指定参数(这种情况可以省略括号)。因为 <code>def string_message(str = '')</code> 中提供了参数的默认值,即空字符串。所以,<code>str</code> 参数是可选的,如果不指定,就使用默认值。</p>
<p>注意,Ruby 方法不用显式指定返回值,方法的返回值是最后一个语句的计算结果。上面这个函数的返回值是两个字符串中的一个,具体是哪一个取决于 <code>str</code> 参数是否为空。在 Ruby 方法中也可以显式指定返回值,下面这个方法和前面的等价:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="k">def</span> <span class="nf">string_message</span><span class="p">(</span><span class="n">str</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span>
<span class="gp">>> </span> <span class="k">return</span> <span class="s2">"It's an empty string!"</span> <span class="k">if</span> <span class="n">str</span><span class="o">.</span><span class="n">empty?</span>
<span class="gp">>> </span> <span class="k">return</span> <span class="s2">"The string is nonempty."</span>
<span class="gp">>> </span><span class="k">end</span>
</pre></div>
</div>
<p>(细心的读者可能会发现,其实没必要使用第二个 <code>return</code>,这一行是方法的最后一个表达式,不管有没有 <code>return</code>,字符串 <code>"The string is nonempty."</code> 都会作为返回值返回。不过两处都加上 <code>return</code> 看起来更好。)</p>
<p>还有一点很重要,方法并不关心参数的名字是什么。在前面定义的第一个方法中,可以把 <code>str</code> 换成任意有效的变量名,例如 <code>the_function_argument</code>,但是方法的作用不变:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="k">def</span> <span class="nf">string_message</span><span class="p">(</span><span class="n">the_function_argument</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span>
<span class="gp">>> </span> <span class="k">if</span> <span class="n">the_function_argument</span><span class="o">.</span><span class="n">empty?</span>
<span class="gp">>> </span> <span class="s2">"It's an empty string!"</span>
<span class="gp">>> </span> <span class="k">else</span>
<span class="gp">>> </span> <span class="s2">"The string is nonempty."</span>
<span class="gp">>> </span> <span class="k">end</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">=> nil</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="n">string_message</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
<span class="go">It's an empty string!</span>
<span class="gp">>> </span><span class="nb">puts</span> <span class="n">string_message</span><span class="p">(</span><span class="s2">"foobar"</span><span class="p">)</span>
<span class="go">The string is nonempty.</span>
</pre></div>
</div>
</section>
<section data-type="sect2" id="back-to-the-title-helper">
<h2><span class="title-label">4.2.5</span> 回顾标题的辅助方法</h2>
<p>下面我们来理解一下<a href="#listing-title-helper">代码清单 4.2</a> 中的 <code>full_title</code> 辅助方法,<sup>[<a id="fn-ref-6" href="#fn-6">6</a>]</sup>在其中加上注解之后如<a href="#listing-annotated-title-helper">代码清单 4.9</a> 所示:</p>
<div id="listing-annotated-title-helper" data-type="listing">
<h5><span class="title-label">代码清单 4.9</span>:注解 <code>full_title</code> 方法</h5>
<div class="source-file">app/helpers/application_helper.rb</div>
<div class="highlight language-ruby"><pre><span class="k">module</span> <span class="nn">ApplicationHelper</span>
<span class="c1"># 根据所在的页面返回完整的标题 # 在文档中显示的注释</span>
<span class="k">def</span> <span class="nf">full_title</span><span class="p">(</span><span class="n">page_title</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span> <span class="c1"># 定义方法,参数可选</span>
<span class="n">base_title</span> <span class="o">=</span> <span class="s2">"Ruby on Rails Tutorial Sample App"</span> <span class="c1"># 变量赋值</span>
<span class="k">if</span> <span class="n">page_title</span><span class="o">.</span><span class="n">empty?</span> <span class="c1"># 布尔测试</span>
<span class="n">base_title</span> <span class="c1"># 隐式返回值</span>
<span class="k">else</span>
<span class="n">page_title</span> <span class="o">+</span> <span class="s2">" | "</span> <span class="o">+</span> <span class="n">base_title</span> <span class="c1"># 字符串拼接</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>方法定义、变量赋值、布尔测试、流程控制和字符串拼接<sup>[<a id="fn-ref-7" href="#fn-7">7</a>]</sup>——组合在一起定义了一个可以在网站布局中使用的辅助方法。这里还有一个知识点——<code>module ApplicationHelper</code>:模块为我们提供了一种把相关方法组织在一起的方式,我们可以使用 <code>include</code> 把模块插入其他的类中。编写普通的 Ruby 程序时,你要自己定义一个模块,然后再显式将其引入类中,但是辅助方法所在的模块会由 Rails 为我们引入,结果是,<code>full_title</code> 方法<a href="http://catb.org/jargon/html/A/automagically.html">自动</a>可在所有视图中使用。</p>
</section>
</section>
<section data-type="sect1" id="other-data-structures">
<h1><span class="title-label">4.3</span> 其他数据类型</h1>
<p>虽然 Web 应用最终都是处理字符串,但也需要其他的数据类型来生成字符串。本节介绍一些对开发 Rails 应用很重要的其他 Ruby 数据类型。</p>
<section data-type="sect2" id="arrays-and-ranges">
<h2><span class="title-label">4.3.1</span> 数组和值域</h2>
<p>数组是一组具有特定顺序的元素。前面还没用过数组,不过理解数组对理解哈希有很大帮助(<a href="#hashes-and-symbols">4.3.3 节</a>),也有助于理解 Rails 中的数据模型(例如 <a href="chapter2.html#a-user-has-many-microposts">2.3.3 节</a>用到的 <code>has_many</code> 关联,<a href="chapter11.html#user-micropost-associations">11.1.3 节</a>会做详细介绍)。</p>
<p>目前,我们已经花了很多时间理解字符串,从字符串过渡到数组可以从 <code>split</code> 方法开始:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span> <span class="s2">"foo bar baz"</span><span class="o">.</span><span class="n">split</span> <span class="c1"># 把字符串拆分成有三个元素的数组</span>
<span class="go">=> ["foo", "bar", "baz"]</span>
</pre></div>
</div>
<p>上述操作得到的结果是一个有三个字符串的数组。默认情况下,<code>split</code> 在空格处把字符串拆分成数组,不过也可以在几乎任何地方拆分:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"fooxbarxbazx"</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">'x'</span><span class="p">)</span>
<span class="go">=> ["foo", "bar", "baz"]</span>
</pre></div>
</div>
<p>和大多数编程语言的习惯一样,Ruby 数组的索引也从零开始,因此数组中第一个元素的索引是 0,第二个元素的索引是 1,依此类推:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span> <span class="o">=</span> <span class="o">[</span><span class="mi">42</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">17</span><span class="o">]</span>
<span class="go">=> [42, 8, 17]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="c1"># Ruby 使用方括号获取数组元素</span>
<span class="go">=> 42</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
<span class="go">=> 8</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span>
<span class="go">=> 17</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[-</span><span class="mi">1</span><span class="o">]</span> <span class="c1"># 索引还可以是负数</span>
<span class="go">=> 17</span>
</pre></div>
</div>
<p>我们看到,Ruby 使用方括号获取数组中的元素。除了方括号之外,Ruby 还为一些经常需要获取的元素提供了别名:<sup>[<a id="fn-ref-8" href="#fn-8">8</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span> <span class="c1"># 只是为了看一下 a 的值是什么</span>
<span class="go">=> [42, 8, 17]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">first</span>
<span class="go">=> 42</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">second</span>
<span class="go">=> 8</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">last</span>
<span class="go">=> 17</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">last</span> <span class="o">==</span> <span class="n">a</span><span class="o">[-</span><span class="mi">1</span><span class="o">]</span> <span class="c1"># 用 == 符号对比</span>
<span class="go">=> true</span>
</pre></div>
</div>
<p>最后一行用到了相等比较操作符 <code>==</code>,Ruby 和其他语言一样还提供了 <code>!=</code>(不等)等其他操作符:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">x</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="n">length</span> <span class="c1"># 和字符串一样,数组也可以响应 length 方法</span>
<span class="go">=> 3</span>
<span class="gp">>> </span><span class="n">x</span> <span class="o">==</span> <span class="mi">3</span>
<span class="go">=> true</span>
<span class="gp">>> </span><span class="n">x</span> <span class="o">==</span> <span class="mi">1</span>
<span class="go">=> false</span>
<span class="gp">>> </span><span class="n">x</span> <span class="o">!=</span> <span class="mi">1</span>
<span class="go">=> true</span>
<span class="gp">>> </span><span class="n">x</span> <span class="o">>=</span> <span class="mi">1</span>
<span class="go">=> true</span>
<span class="gp">>> </span><span class="n">x</span> <span class="o"><</span> <span class="mi">1</span>
<span class="go">=> false</span>
</pre></div>
</div>
<p>除了 <code>length</code>(上述代码的第一行)之外,数组还可以响应一系列其他方法:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span>
<span class="go">=> [42, 8, 17]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">empty?</span>
<span class="go">=> false</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
<span class="go">=> true</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">sort</span>
<span class="go">=> [8, 17, 42]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">reverse</span>
<span class="go">=> [17, 8, 42]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">shuffle</span>
<span class="go">=> [17, 42, 8]</span>
<span class="gp">>> </span><span class="n">a</span>
<span class="go">=> [42, 8, 17]</span>
</pre></div>
</div>
<p>注意,上面的方法都没有修改 <code>a</code> 的值。如果想修改数组的值,要使用相应的“炸弹”(bang)方法(之所以这么叫是因为,这里的感叹号经常都读作“bang”):</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span>
<span class="go">=> [42, 8, 17]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">sort!</span>
<span class="go">=> [8, 17, 42]</span>
<span class="gp">>> </span><span class="n">a</span>
<span class="go">=> [8, 17, 42]</span>
</pre></div>
</div>
<p>还可以使用 <code>push</code> 方法向数组中添加元素,或者使用等价的 <code><<</code> 操作符:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span> <span class="c1"># 把 6 加到数组结尾</span>
<span class="go">=> [42, 8, 17, 6]</span>
<span class="gp">>> </span><span class="n">a</span> <span class="o"><<</span> <span class="mi">7</span> <span class="c1"># 把 7 加到数组结尾</span>
<span class="go">=> [42, 8, 17, 6, 7]</span>
<span class="gp">>> </span><span class="n">a</span> <span class="o"><<</span> <span class="s2">"foo"</span> <span class="o"><<</span> <span class="s2">"bar"</span> <span class="c1"># 串联操作</span>
<span class="go">=> [42, 8, 17, 6, 7, "foo", "bar"]</span>
</pre></div>
</div>
<p>最后一个命令说明,可以把添加操作串在一起使用;也说明,Ruby 不像很多其他语言,数组中可以包含不同类型的数据(本例中包含整数和字符串)。</p>
<p>前面用 <code>split</code> 把字符串拆分成数组,我们还可以使用 <code>join</code> 方法进行相反的操作:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span>
<span class="go">=> [42, 8, 17, 7, "foo", "bar"]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">join</span> <span class="c1"># 没有连接符</span>
<span class="go">=> "428177foobar"</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span> <span class="c1"># 连接符是一个逗号和空格</span>
<span class="go">=> "42, 8, 17, 7, foo, bar"</span>
</pre></div>
</div>
<p>和数组有点类似的是值域(range),使用 <code>to_a</code> 方法把它转换成数组或许更好理解:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">9</span>
<span class="go">=> 0..9</span>
<span class="gp">>> </span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">9</span><span class="o">.</span><span class="n">to_a</span> <span class="c1"># 错了,to_a 在 9 上调用了</span>
<span class="go">NoMethodError: undefined method `to_a' for 9:Fixnum</span>
<span class="gp">>> </span><span class="p">(</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">9</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span> <span class="c1"># 调用 to_a 要用括号包住值域</span>
<span class="go">=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</span>
</pre></div>
</div>
<p>虽然 <code>0..9</code> 是有效的值域,不过上面第二个表达式告诉我们,调用方法时要加上括号。</p>
<p>值域经常用来获取数组中的一组元素:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span> <span class="o">=</span> <span class="sx">%w[foo bar baz quux]</span> <span class="c1"># %w 创建一个元素为字符串的数组</span>
<span class="go">=> ["foo", "bar", "baz", "quux"]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">2</span><span class="o">]</span>
<span class="go">=> ["foo", "bar", "baz"]</span>
</pre></div>
</div>
<p>有个特别有用的技巧:值域的结束值使用 -1 时,不用知道数组的长度就能从起始值开始一直获取到最后一个元素:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">9</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span>
<span class="go">=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">2</span><span class="o">.</span><span class="n">.</span><span class="p">(</span><span class="n">a</span><span class="o">.</span><span class="n">length</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">]</span> <span class="c1"># 显式使用数组的长度</span>
<span class="go">=> [2, 3, 4, 5, 6, 7, 8, 9]</span>
<span class="gp">>> </span><span class="n">a</span><span class="o">[</span><span class="mi">2</span><span class="o">.</span><span class="n">.</span><span class="o">-</span><span class="mi">1</span><span class="o">]</span> <span class="c1"># 小技巧,索引使用 -1</span>
<span class="go">=> [2, 3, 4, 5, 6, 7, 8, 9]</span>
</pre></div>
</div>
<p>值域也可以使用字母:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'e'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span>
<span class="go">=> ["a", "b", "c", "d", "e"]</span>
</pre></div>
</div>
</section>
<section data-type="sect2" id="blocks">
<h2><span class="title-label">4.3.2</span> 块</h2>
<p>数组和值域可以响应的方法中有很多都可以跟着一个块(block),这是 Ruby 最强大也是最难理解的功能:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="nb">puts</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="p">}</span>
<span class="go">2</span>
<span class="go">4</span>
<span class="go">6</span>
<span class="go">8</span>
<span class="go">10</span>
<span class="go">=> 1..5</span>
</pre></div>
</div>
<p>这段代码在值域 <code>(1..5)</code> 上调用 <code>each</code> 方法,然后又把 <code>{ |i| puts 2 * i }</code> 这个块传给 <code>each</code> 方法。<code>|i|</code> 两边的竖线在 Ruby 中用来定义块变量。只有这个方法才知道如何处理后面跟着的块。本例中,值域的 <code>each</code> 方法会处理后面的块,块中有一个本地变量 <code>i</code>,<code>each</code> 会把值域中的各个值传进块中,然后执行其中的代码。</p>
<p>花括号是表示块的一种方式,除此之外还有另一种方式:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="gp">?> </span> <span class="nb">puts</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">i</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">2</span>
<span class="go">4</span>
<span class="go">6</span>
<span class="go">8</span>
<span class="go">10</span>
<span class="go">=> 1..5</span>
</pre></div>
</div>
<p>块中的内容可以多于一行,而且经常多于一行。本书遵照一个常用的约定,当块只有一行简单的代码时使用花括号形式;当块是一行很长的代码,或者有多行时使用 <code>do..end</code> 形式:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">number</span><span class="o">|</span>
<span class="gp">?> </span> <span class="nb">puts</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">number</span>
<span class="gp">>> </span> <span class="nb">puts</span> <span class="s1">'-'</span>
<span class="gp">>> </span><span class="k">end</span>
<span class="go">2</span>
<span class="go">-</span>
<span class="go">4</span>
<span class="go">-</span>
<span class="go">6</span>
<span class="go">-</span>
<span class="go">8</span>
<span class="go">-</span>
<span class="go">10</span>
<span class="go">-</span>
<span class="go">=> 1..5</span>
</pre></div>
</div>
<p>上面的代码用 <code>number</code> 代替了 <code>i</code>,我想告诉你的是,变量名可以使用任何值。</p>
<p>除非你已经有了一些编程知识,否则对块的理解是没有捷径的。你要做的是多看,看多了就会习惯这种用法。<sup>[<a id="fn-ref-9" href="#fn-9">9</a>]</sup>幸好人类擅长从实例中归纳出一般性。下面是一些例子,其中几个用到了 <code>map</code> 方法:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="mi">3</span><span class="o">.</span><span class="n">times</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"Betelgeuse!"</span> <span class="p">}</span> <span class="c1"># 3.times 后跟的块没有变量</span>
<span class="go">"Betelgeuse!"</span>
<span class="go">"Betelgeuse!"</span>
<span class="go">"Betelgeuse!"</span>
<span class="go">=> 3</span>
<span class="gp">>> </span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span><span class="o">**</span><span class="mi">2</span> <span class="p">}</span> <span class="c1"># ** 表示幂运算</span>
<span class="go">=> [1, 4, 9, 16, 25]</span>
<span class="gp">>> </span><span class="sx">%w[a b c]</span> <span class="c1"># 再说一下,%w 用来创建元素为字符串的数组</span>
<span class="go">=> ["a", "b", "c"]</span>
<span class="gp">>> </span><span class="sx">%w[a b c]</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="n">char</span><span class="o">.</span><span class="n">upcase</span> <span class="p">}</span>
<span class="go">=> ["A", "B", "C"]</span>
<span class="gp">>> </span><span class="sx">%w[A B C]</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="n">char</span><span class="o">.</span><span class="n">downcase</span> <span class="p">}</span>
<span class="go">=> ["a", "b", "c"]</span>
</pre></div>
</div>
<p>可以看出,<code>map</code> 方法返回的是在数组或值域中每个元素上执行块中代码后得到的结果。在最后两个命令中,<code>map</code> 后面的块在块变量上调用一个方法,这种操作经常使用简写形式:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="sx">%w[A B C]</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="n">char</span><span class="o">.</span><span class="n">downcase</span> <span class="p">}</span>
<span class="go">=> ["a", "b", "c"]</span>
<span class="gp">>> </span><span class="sx">%w[A B C]</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:downcase</span><span class="p">)</span>
<span class="go">=> ["a", "b", "c"]</span>
</pre></div>
</div>
<p>(简写形式看起来有点儿奇怪,其中用到了符号,<a href="#hashes-and-symbols">4.3.3 节</a>会介绍。)这种写法比较有趣,一开始是由 Rails 扩展实现的,但人们太喜欢了,现在已经集成到 Ruby 核心代码中。</p>
<p>最后再看一个使用块的例子。我们看一下<a href="#listing-home-base-title-spec">代码清单 4.4</a> 中的一个测试用例:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="hll"><span class="nb">test</span> <span class="s2">"should get home"</span> <span class="k">do</span>
</span> <span class="n">get</span> <span class="ss">:home</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="n">assert_select</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"Ruby on Rails Tutorial Sample App"</span>
<span class="hll"><span class="k">end</span>
</span></pre></div>
</div>
<p>现在不需要理解细节(其实我也不懂),从 <code>do</code> 关键字可以看出,测试的主体其实就是个块。<code>test</code> 方法的参数是一个字符串(测试的描述)和一个块,运行测试组件时会执行块中的内容。</p>
<p>现在我们来分析一下我在 <a href="chapter1.html#heroku-commands">1.5.4 节</a>生成随机二级域名时使用的那行 Ruby 代码:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'z'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">shuffle</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">7</span><span class="o">].</span><span class="n">join</span>
</pre></div>
</div>
<p>我们一步步分解:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'z'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span> <span class="c1"># 由全部英文字母组成的数组</span>
<span class="go">=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",</span>
<span class="go">"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]</span>
<span class="gp">>> </span><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'z'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">shuffle</span> <span class="c1"># 打乱数组</span>
<span class="go">=> ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",</span>
<span class="go">"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]</span>
<span class="gp">>> </span><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'z'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">shuffle</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">7</span><span class="o">]</span> <span class="c1"># 取出前 8 个元素</span>
<span class="go">=> ["f", "w", "i", "a", "h", "p", "c", "x"]</span>
<span class="gp">>> </span><span class="p">(</span><span class="s1">'a'</span><span class="o">.</span><span class="n">.</span><span class="s1">'z'</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">shuffle</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="mi">7</span><span class="o">].</span><span class="n">join</span> <span class="c1"># 把取出的元素合并成字符串</span>
<span class="go">=> "mznpybuj"</span>
</pre></div>
</div>
</section>
<section data-type="sect2" id="hashes-and-symbols">
<h2><span class="title-label">4.3.3</span> 哈希和符号</h2>
<p>哈希(Hash)本质上就是数组,只不过它的索引不局限于只能使用数字。(实际上在一些语言中,特别是 Perl,因为这个原因把哈希叫做“关联数组”。)哈希的索引(或者叫“键”)几乎可以使用任何对象。例如,可以使用字符串当键:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">user</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># {} 是一个空哈希</span>
<span class="go">=> {}</span>
<span class="gp">>> </span><span class="n">user</span><span class="o">[</span><span class="s2">"first_name"</span><span class="o">]</span> <span class="o">=</span> <span class="s2">"Michael"</span> <span class="c1"># 键为 "first_name",值为 "Michael"</span>
<span class="go">=> "Michael"</span>
<span class="gp">>> </span><span class="n">user</span><span class="o">[</span><span class="s2">"last_name"</span><span class="o">]</span> <span class="o">=</span> <span class="s2">"Hartl"</span> <span class="c1"># 键为 "last_name",值为 "Hartl"</span>
<span class="go">=> "Hartl"</span>
<span class="gp">>> </span><span class="n">user</span><span class="o">[</span><span class="s2">"first_name"</span><span class="o">]</span> <span class="c1"># 获取元素的方式和数组类似</span>
<span class="go">=> "Michael"</span>
<span class="gp">>> </span><span class="n">user</span> <span class="c1"># 哈希的字面量形式</span>
<span class="go">=> {"last_name"=>"Hartl", "first_name"=>"Michael"}</span>
</pre></div>
</div>
<p>哈希通过一对花括号中包含一些键值对的形式表示,如果只有一对花括号而没有键值对(<code>{}</code>)就是一个空哈希。注意,哈希中的花括号和块中的花括号不是一个概念。(是的,这可能会让你困惑。)哈希虽然和数组类似,但二者却有一个很重要的区别:哈希中的元素没有特定的顺序。<sup>[<a id="fn-ref-10" href="#fn-10">10</a>]</sup>如果顺序很重要的话就要使用数组。</p>
<p>通过方括号的形式每次定义一个元素的方式不太敏捷,使用 <code>⇒</code> 分隔的键值对这种字面量形式定义哈希要简洁得多:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">user</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"first_name"</span> <span class="o">=></span> <span class="s2">"Michael"</span><span class="p">,</span> <span class="s2">"last_name"</span> <span class="o">=></span> <span class="s2">"Hartl"</span> <span class="p">}</span>
<span class="go">=> {"last_name"=>"Hartl", "first_name"=>"Michael"}</span>
</pre></div>
</div>
<p>在上面的代码中我用到了一个 Ruby 句法约定,在左花括号后面和右花括号前面加入了一个空格,不过控制台会忽略这些空格。(不要问我为什么这些空格是约定俗成的,或许是某个 Ruby 编程大牛喜欢这种形式,然后约定就产生了。)</p>
<p>目前为止哈希的键都使用字符串,在 Rails 中用“符号”(Symbol)当键很常见。符号看起来有点儿像字符串,只不过没有包含在一对引号中,而是在前面加一个冒号。例如,<code>:name</code> 就是一个符号。你可以把符号看成没有约束的字符串:<sup>[<a id="fn-ref-11" href="#fn-11">11</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="s2">"name"</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span>
<span class="go">=> ["n", "a", "m", "e"]</span>
<span class="gp">>> </span><span class="ss">:name</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span>
<span class="go">NoMethodError: undefined method `split' for :name:Symbol</span>
<span class="gp">>> </span><span class="s2">"foobar"</span><span class="o">.</span><span class="n">reverse</span>
<span class="go">=> "raboof"</span>
<span class="gp">>> </span><span class="ss">:foobar</span><span class="o">.</span><span class="n">reverse</span>
<span class="go">NoMethodError: undefined method `reverse' for :foobar:Symbol</span>
</pre></div>
</div>
<p>符号是 Ruby 特有的数据类型,其他语言很少用到。初看起来感觉很奇怪,不过 Rails 经常用到,所以你很快就会习惯。符号和字符串不同,并不是所有字符都能在符号中使用:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="ss">:foo</span><span class="o">-</span><span class="n">bar</span>
<span class="go">NameError: undefined local variable or method `bar' for main:Object</span>
<span class="gp">>> </span><span class="p">:</span><span class="mi">2</span><span class="n">foo</span>
<span class="go">SyntaxError</span>
</pre></div>
</div>
<p>只要以字母开头,其后都使用单词中常用的字符就没事。</p>
<p>用符号当键,我们可以按照如下的方式定义一个 <code>user</code> 哈希:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">user</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">:name</span> <span class="o">=></span> <span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">:email</span> <span class="o">=></span> <span class="s2">"[email protected]"</span> <span class="p">}</span>
<span class="go">=> {:name=>"Michael Hartl", :email=>"[email protected]"}</span>
<span class="gp">>> </span><span class="n">user</span><span class="o">[</span><span class="ss">:name</span><span class="o">]</span> <span class="c1"># 获取 :name 对应的值</span>
<span class="go">=> "Michael Hartl"</span>
<span class="gp">>> </span><span class="n">user</span><span class="o">[</span><span class="ss">:password</span><span class="o">]</span> <span class="c1"># 获取未定义的键对应的值</span>
<span class="go">=> nil</span>
</pre></div>
</div>
<p>从上面的例子可以看出,哈希中没有定义的键对应的值是 <code>nil</code>。</p>
<p>因为符号当键的情况太普遍了,Ruby 1.9 干脆就为这种用法定义了一种新句法:</p>
<div data-type="listing">
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">h1</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">:name</span> <span class="o">=></span> <span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">:email</span> <span class="o">=></span> <span class="s2">"[email protected]"</span> <span class="p">}</span>
<span class="go">=> {:name=>"Michael Hartl", :email=>"[email protected]"}</span>
<span class="gp">>> </span><span class="n">h2</span> <span class="o">=</span> <span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email</span><span class="p">:</span> <span class="s2">"[email protected]"</span> <span class="p">}</span>
<span class="go">=> {:name=>"Michael Hartl", :email=>"[email protected]"}</span>
<span class="gp">>> </span><span class="n">h1</span> <span class="o">==</span> <span class="n">h2</span>
<span class="go">=> true</span>
</pre></div>
</div>
<p>第二中句法把“符号 ⇒”变成了“键的名字:”形式:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email</span><span class="p">:</span> <span class="s2">"[email protected]"</span> <span class="p">}</span>
</pre></div>
</div>
<p>这种形式更好地沿袭了其他语言(例如 JavaScript)中哈希的表示方式,在 Rails 社区中也越来越受欢迎。这两种方式现在都在使用,所以你要能识别它们。可是,新句法有点让人困惑,因为 <code>:name</code> 本身是一种数据类型(符号),但 <code>name:</code> 却没有意义。不过在哈希字面量中,<code>:name ⇒</code> 和 <code>name:</code> 作用一样。因此,<code>{ :name ⇒ "Michael Hartl" }</code> 和 <code>{ name: "Michael Hartl" }</code> 是等效的。如果要表示符号,只能使用 <code>:name</code>(冒号在前面)。</p>
<p>哈希中元素的值可以是任何对象,甚至是另一个哈希,如<a href="#listing-nested-hashes">代码清单 4.10</a> 所示。</p>
<div id="listing-nested-hashes" data-type="listing">
<h5><span class="title-label">代码清单 4.10</span>:嵌套哈希</h5>
<div class="highlight language-irb"><pre><span class="gp">>> </span><span class="n">params</span> <span class="o">=</span> <span class="p">{}</span> <span class="c1"># 定义一个名为 params(parameters 的简称)的哈希</span>
<span class="go">=> {}</span>
<span class="gp">>> </span><span class="n">params</span><span class="o">[</span><span class="ss">:user</span><span class="o">]</span> <span class="o">=</span> <span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email</span><span class="p">:</span> <span class="s2">"[email protected]"</span> <span class="p">}</span>
<span class="go">=> {:name=>"Michael Hartl", :email=>"[email protected]"}</span>
<span class="gp">>> </span><span class="n">params</span>
<span class="go">=> {:user=>{:name=>"Michael Hartl", :email=>"[email protected]"}}</span>