-
Notifications
You must be signed in to change notification settings - Fork 2
/
chapter4.html
1366 lines (1138 loc) · 108 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>第 4 章 Rails 背后的 Ruby</title>
<meta name="author" content="Andor Chen" />
<link rel="stylesheet" href="assets/styles/style.css" />
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="assets/js/global.js"></script>
</head>
<body>
<div class="wrapper">
<div class="header">
<h1 class="logo"><a class="ir" href="http://railstutorial-china.org/rails4">Ruby on Rails 教程</a></h1>
<p class="subtitle">Ruby on Rails Tutorial 原书第 2 版(涵盖 Rails 4)</p>
</div>
<div class="content">
<div class="item chapter">
<h1 id="chapter-4"><span>第 4 章</span> Rails 背后的 Ruby</h1>
<ol class="toc"> <li class="level-2">
<a href="#section-4-1">4.1 导言</a>
</li>
<li class="level-2">
<a href="#section-4-2">4.2 字符串和方法</a>
</li>
<li class="level-3">
<a href="#section-4-2-1">4.2.1 注释</a>
</li>
<li class="level-3">
<a href="#section-4-2-2">4.2.2 字符串</a>
</li>
<li class="level-3">
<a href="#section-4-2-3">4.2.3 对象及向其传递消息</a>
</li>
<li class="level-3">
<a href="#section-4-2-4">4.2.4 定义方法</a>
</li>
<li class="level-3">
<a href="#section-4-2-5">4.2.5 回顾一下标题的帮助方法</a>
</li>
<li class="level-2">
<a href="#section-4-3">4.3 其他的数据类型</a>
</li>
<li class="level-3">
<a href="#section-4-3-1">4.3.1 数组和 Range</a>
</li>
<li class="level-3">
<a href="#section-4-3-2">4.3.2 块</a>
</li>
<li class="level-3">
<a href="#section-4-3-3">4.3.3 Hash 和 Symbol</a>
</li>
<li class="level-3">
<a href="#section-4-3-4">4.3.4 重温引入 CSS 的代码</a>
</li>
<li class="level-2">
<a href="#section-4-4">4.4 Ruby 类</a>
</li>
<li class="level-3">
<a href="#section-4-4-1">4.4.1 构造器</a>
</li>
<li class="level-3">
<a href="#section-4-4-2">4.4.2 类的继承</a>
</li>
<li class="level-3">
<a href="#section-4-4-3">4.4.3 修改内置的类</a>
</li>
<li class="level-3">
<a href="#section-4-4-4">4.4.4 控制器类</a>
</li>
<li class="level-3">
<a href="#section-4-4-5">4.4.5 用户类</a>
</li>
<li class="level-2">
<a href="#section-4-5">4.5 小结</a>
</li>
<li class="level-2">
<a href="#section-4-6">4.6 练习</a>
</li>
</ol>
<div class="main">
<p>有了<a href="chapter3.html">第 3 章</a>中的例子做铺垫,本章将为你介绍一些对 Rails 来说很重要的 Ruby 知识。Ruby 语言的知识点很多,不过对一个 Rails 开发者而言需要掌握的很少。我们采用的是有别于常规的 Ruby 学习过程,我们的目标是开发动态的 Web 应用程序,所以我建议你先学习 Rails,在这个过程中学习一些 Ruby 知识。如果要成为一个 Rails 专家,你就要更深入的掌握 Ruby 了。本书会为你在成为专家的路途上奠定一个坚实的基础。如 <a href="chapter1.html#section-1-1-1">1.1.1 节</a>中说过的,读完本书后我建议你阅读一本专门针对 Ruby 的书,例如《<a href="http://www.amazon.com/gp/product/1430223634">Ruby 入门</a>》、《<a href="http://www.amazon.com/gp/product/1933988657">The Well-Grounded Rubyist</a>》或《<a href="http://www.amazon.com/gp/product/0672328844">Ruby 之道</a>》。</p>
<p>本章介绍了很多内容,第一遍阅读没有掌握全部是可以理解的。在后续的章节我会经常提到本章的内容。</p>
<h2 id='section-4-1'><span>4.1</span> 导言</h2>
<p>从上一章我们可以看到,即使不懂任何背后用到的 Ruby 语言,我们也可以创建一个 Rails 应用程序骨架,也可以进行测试。不过我们依赖的是本教程中提供的测试代码,得到错误信息,然后让其通过。我们不能总是这样做,所以这一章我们要暂别网站开发学习,正视我们的 Ruby 短肋。</p>
<p>上次接触应用程序时,我们已经使用 Rails 布局去掉了几乎是静态的页面中的代码重复,参见代码 4.1,这段代码就是重新排版一下代码 3.26。</p>
<div class="codeblock has-caption" id="codeblock-4-1"><p class="caption"><span>代码 4.1:</span>示例程序的网站布局</p><p class="file"><code>app/views/layouts/application.html.erb</code></p><div class="highlight type-erb"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Ruby on Rails Tutorial Sample App | <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="nt"></title></span>
<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="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="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s2">"application"</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="cp"><%=</span> <span class="n">csrf_meta_tags</span> <span class="cp">%></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<p>让我们把注意力集中在代码 4.1 中的这一行:</p>
<div class="codeblock"><div class="highlight type-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="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>
</pre></div>
</div>
<p>这行代码使用 Rails 内置的方法 <code>stylesheet_link_tag</code>(更多内容请查看 <a href="http://api.rubyonrails.org/v3.2.0/classes/ActionView/Helpers/AssetTagHelper/StylesheetTagHelpers.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 方法,不用括号的方法调用,Symbol 和 Hash。这几点本章都会介绍。</p>
<p>除了提供很多内置的方法供我们在视图中使用之外,Rails 还允许我们自行创建。自行创建的这些方法叫做帮助方法(helper)。要说明如何自行创建一个帮助方法,我们要来看看代码 4.1 中标题那一行:</p>
<div class="codeblock"><div class="highlight type-erb"><pre>Ruby on Rails Tutorial Sample App | <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>
</pre></div>
</div>
<p>这行代码依赖于每个视图中定义的页面标题(使用 <code>provide</code>),例如</p>
<div class="codeblock"><div class="highlight type-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="s1">'Home'</span><span class="p">)</span> <span class="cp">%></span>
<span class="nt"><h1></span>Sample App<span class="nt"></h1></span>
<span class="nt"><p></span>
This is the home page for the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://railstutorial.org/"</span><span class="nt">></span>Ruby on Rails Tutorial<span class="nt"></a></span>
sample application.
<span class="nt"></p></span>
</pre></div>
</div>
<p>那么如果我们不提供标题会怎样呢?我们的标题一般都包含一个公共部分,如果想更具体些就要加上一个变动的部分了。我们在布局中用了个小技巧,基本上已经实现了这样的标题。如果我们删除视图中的 <code>provide</code> 方法调用,输出的标题就没有了变动的那部分:</p>
<div class="codeblock"><div class="highlight type-plaintext"><pre>Ruby on Rails Tutorial Sample App |
</pre></div>
</div>
<p>公共部分已经输出了,而且后面还有一个竖杠 <code>|</code>。</p>
<p>为了解决这个标题问题,我们会自定义一个帮助方法,叫做 <code>full_title</code>。如果视图中没有定义标题,<code>full_title</code> 会返回标题的公共部分,即“Ruby on Rails Tutorial Sample App”;如果定义了,则会在公共部分后面加上一个竖杠,然后再接上该页面的标题(如代码 4.2)。<sup class="footnote" id="fnref-4-1"><a href="#fn-4-1" rel="footnote">1</a></sup></p>
<div class="codeblock has-caption" id="codeblock-4-2"><p class="caption"><span>代码 4.2:</span>定义 <code>full_title</code> 帮助方法</p><p class="file"><code>app/helpers/application_helper.rb</code></p><div class="highlight type-ruby"><pre><span class="k">module</span> <span class="nn">ApplicationHelper</span>
<span class="c1"># Returns the full title on a per-page basis.</span>
<span class="k">def</span> <span class="nf">full_title</span><span class="p">(</span><span class="n">page_title</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="p">.</span><span class="nf">empty?</span>
<span class="n">base_title</span>
<span class="k">else</span>
<span class="s2">"</span><span class="si">#{</span><span class="n">base_title</span><span class="si">}</span><span class="s2"> | </span><span class="si">#{</span><span class="n">page_title</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>现在我们已经定义了一个帮助方法,我们可以用它来简化布局,将</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="o"><</span><span class="n">title</span><span class="o">></span><span class="no">Ruby</span> <span class="n">on</span> <span class="no">Rails</span> <span class="no">Tutorial</span> <span class="no">Sample</span> <span class="no">App</span> <span class="o">|</span> <span class="o"><</span><span class="sx">%= yield(:title) %></title>
</span></pre></div>
</div>
<p>替换成</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="o"><</span><span class="n">title</span><span class="o">><</span><span class="sx">%= full_title(yield(:title)) %></title>
</span></pre></div>
</div>
<p>如代码 4.3 所示。</p>
<div class="codeblock has-caption" id="codeblock-4-3"><p class="caption"><span>代码 4.3:</span>示例程序的网站布局</p><p class="file"><code>app/views/layouts/application.html.erb</code></p><div class="highlight type-erb"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><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="nt"></title></span>
<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="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="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s2">"application"</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="cp"><%=</span> <span class="n">csrf_meta_tags</span> <span class="cp">%></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<p>为了让这个帮助方法起作用,我们要在“首页”视图中将不必要的“Home”这个词删掉,让标题只保留公共部分。首先我们要按照代码 4.4 的内容更新现有的测试,增加对没包含 <code>'Home'</code> 的标题测试。</p>
<div class="codeblock has-caption" id="codeblock-4-4"><p class="caption"><span>代码 4.4:</span>更新“首页”标题的测试</p><p class="file"><code>spec/requests/static_pages_spec.rb</code></p><div class="highlight type-ruby"><pre><span class="nb">require</span> <span class="s1">'spec_helper'</span>
<span class="n">describe</span> <span class="s2">"Static pages"</span> <span class="k">do</span>
<span class="n">describe</span> <span class="s2">"Home page"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"should have the content 'Sample App'"</span> <span class="k">do</span>
<span class="n">visit</span> <span class="s1">'/static_pages/home'</span>
<span class="n">expect</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="nf">to</span> <span class="n">have_content</span><span class="p">(</span><span class="s1">'Sample App'</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s2">"should have the base title"</span> <span class="k">do</span>
<span class="n">visit</span> <span class="s1">'/static_pages/home'</span>
<span class="n">expect</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="nf">to</span> <span class="n">have_title</span><span class="p">(</span><span class="s2">"Ruby on Rails Tutorial Sample App"</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s2">"should not have a custom page title"</span> <span class="k">do</span>
<span class="n">visit</span> <span class="s1">'/static_pages/home'</span>
<span class="n">expect</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="nf">not_to</span> <span class="n">have_title</span><span class="p">(</span><span class="s1">'| Home'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<p>试试看你能否猜到为什么我们添加了一个新测试而不是直接修改之前的测试。(提示:答案在 <a href="chapter3.html#section-3-3-1">3.3.1 节</a>中。)</p>
<p>运行测试,查看是否有一个测试失败了:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rspec spec/requests/static_pages_spec.rb
</pre></div>
</div>
<p>为了让测试通过,我们要将“首页”视图中的 <code>provide</code> 那行删除,如代码 4.5 所示。</p>
<div class="codeblock has-caption" id="codeblock-4-5"><p class="caption"><span>代码 4.5:</span>删除标题定义后的“首页”</p><p class="file"><code>app/views/static_pages/home.html.erb</code></p><div class="highlight type-erb"><pre><span class="nt"><h1></span>Sample App<span class="nt"></h1></span>
<span class="nt"><p></span>
This is the home page for the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://railstutorial.org/"</span><span class="nt">></span>Ruby on Rails Tutorial<span class="nt"></a></span>
sample application.
<span class="nt"></p></span>
</pre></div>
</div>
<p>现在测试应该可以通过了:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rspec spec/requests/static_pages_spec.rb
</pre></div>
</div>
<p>和引入应用程序样式表那行代码一样,代码 4.2 的内容对经验丰富的 Rails 开发者来说看起来很简单,但是充满了很多会让人困惑的 Ruby 知识:module,注释,局部变量的赋值,布尔值,流程控制,字符串插值,还有返回值。这章也会介绍这些知识。</p>
<h2 id='section-4-2'><span>4.2</span> 字符串和方法</h2>
<p>学习 Ruby 我们主要使用的工具是 Rails 控制台,它是用来和 Rails 应用程序交互的命令行,在 <a href="chapter2.html#section-2-3-3">2.3.3 节</a>中介绍过。这个控制台是基于 Ruby 的交互程序(<code>irb</code>)开发的,因此也就能使用 Ruby 语言的全部功能。(在 <a href="chapter4.html#section-4-4-4">4.4.4 节</a>中会介绍,控制台还可以进入 Rails 环境。)使用下面的方法在命令行中启动控制台:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails console
Loading development environment
>>
</pre></div>
</div>
<p>默认情况下,控制台是以开发环境启用的,这是 Rails 定义的三个独立的环境之一(其他两个是测试环境和生产环境)。三个环境的区别在本章还不需要知道,我们会在 <a href="chapter7.html#section-7-1-1">7.1.1 节</a>中更详细的介绍。</p>
<p>控制台是个很好的学习工具,你不用有所畏惧尽情的使用吧,没必要担心,你(几乎)不会破坏任何东西。如果你在控制台中遇到问题了可以使用 Ctrl-C 结束当前执行的命令,或者使用 Ctrl-D 直接退出控制台。在阅读本章后面的内容时,你会发现查阅 <a href="http://ruby-doc.org/core-2.0/">Ruby API</a> 会很有用。API 包含很多信息,例如,如果你想查看关于 Ruby 字符串更多的内容,可以查看其中的 <code>String</code> 类页面。</p>
<h3 id='section-4-2-1'><span>4.2.1</span> 注释</h3>
<p>Ruby 中的注释以井号 <code>#</code>(也叫“Hash Mark”,或者更诗意的叫“散列字元”)开头,一直到行尾结束。Ruby 会忽略注释,但是注释对代码阅读者(包括代码的创作者)却很有用。在下面的代码中</p>
<div class="codeblock"><div class="highlight type-ruby"><pre> <span class="c1"># Returns the full title on a per-page basis.</span>
<span class="k">def</span> <span class="nf">full_title</span><span class="p">(</span><span class="n">page_title</span><span class="p">)</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<p>第一行就是注释,说明了后面方法的作用。</p>
<p>一般无需在控制台中写注释,不过为了说明代码,我会按照下面的形式加上注释,例如:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails console
<span class="gp">>> </span>17 + 42 <span class="c"># Integer addition</span>
<span class="gp">=> </span>59
</pre></div>
</div>
<p>在本节的阅读过程中,在控制台中输入或者复制粘贴命令时,如果愿意你可以不复制注释,反正控制台会忽略注释。</p>
<h3 id='section-4-2-2'><span>4.2.2</span> 字符串</h3>
<p>字符串算是 Web 应用程序中最有用的数据结构了,因为网页的内容就是从数据库发送到浏览器的字符串。我们先在控制台中体验一下字符串,这次我们使用 <code>rails c</code> 启动控制台,这是 <code>rails console</code> 的简写形式:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails c
<span class="gp">>> </span><span class="s2">""</span> <span class="c"># 空字符串</span>
<span class="gp">=> </span><span class="s2">""</span>
<span class="gp">>> </span><span class="s2">"foo"</span> <span class="c"># 非空的字符串</span>
<span class="gp">=> </span><span class="s2">"foo"</span>
</pre></div>
</div>
<p>上面的字符串是字面量(字面量字符串,literal string),通过双引号(<code>"</code>)创建。控制台回显的是每一行的计算结果,本例中字符串字面量的结果就是字符串本身。</p>
<p>我们还可以使用 <code>+</code> 号连接字符串:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"foo"</span> + <span class="s2">"bar"</span> <span class="c"># 字符串连接</span>
<span class="gp">=> </span><span class="s2">"foobar"</span>
</pre></div>
</div>
<p><code>"foo"</code> 连接 <code>"bar"</code> 的运行结果是字符串 <code>"foobar"</code>。<sup class="footnote" id="fnref-4-2"><a href="#fn-4-2" rel="footnote">2</a></sup></p>
<p>另外一种创建字符串的方式是通过一个特殊的句法(<code>#{}</code>)进行插值操作:<sup class="footnote" id="fnref-4-3"><a href="#fn-4-3" rel="footnote">3</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>first_name <span class="o">=</span> <span class="s2">"Michael"</span> <span class="c"># 变量赋值</span>
<span class="gp">=> </span><span class="s2">"Michael"</span>
<span class="gp">>> </span><span class="s2">"#{first_name} Hartl"</span> <span class="c"># 字符串插值</span>
<span class="gp">=> </span><span class="s2">"Michael Hartl"</span>
</pre></div>
</div>
<p>我们先把“<code>Michael</code>”赋值给变量 <code>first_name</code>,然后将其插入到字符串 <code>"#{first_name} Hartl"</code> 中。我们可以将两个字符串都赋值给变量:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>first_name <span class="o">=</span> <span class="s2">"Michael"</span>
<span class="gp">=> </span><span class="s2">"Michael"</span>
<span class="gp">>> </span>last_name <span class="o">=</span> <span class="s2">"Hartl"</span>
<span class="gp">=> </span><span class="s2">"Hartl"</span>
<span class="gp">>> </span>first_name + <span class="s2">" "</span> + last_name <span class="c"># 字符串连接,中间加了空格</span>
<span class="gp">=> </span><span class="s2">"Michael Hartl"</span>
<span class="gp">>> </span><span class="s2">"#{first_name} #{last_name}"</span> <span class="c"># 作用相同的插值</span>
<span class="gp">=> </span><span class="s2">"Michael Hartl"</span>
</pre></div>
</div>
<p>注意,两个表达式的结果是相同的,不过我倾向使用插值的方式。在两个字符串中加入一个空格(<code>" "</code>)显得很别扭。</p>
<h4 id='section-4-2-2-1'><span></span>打印字符串</h4>
<p>打印字符串最常用的 Ruby 方法是 <code>puts</code>(读作“put ess”,意思是“打印字符串”):</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>puts <span class="s2">"foo"</span> <span class="c"># 打印字符串</span>
foo
<span class="gp">=> </span>nil
</pre></div>
</div>
<p><code>puts</code> 方法还有一个副作用(side-effect):<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 class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>print <span class="s2">"foo"</span> <span class="c"># 打印字符串(和 puts 类似,但没有添加换行符)</span>
<span class="gp">foo=> </span>nil
<span class="gp">>> </span>print <span class="s2">"foo</span><span class="se">\n</span><span class="s2">"</span> <span class="c"># 和 puts "foo" 一样</span>
<span class="gp">=> </span>nil
</pre></div>
</div>
<h4 id='section-4-2-2-2'><span></span>单引号字符串</h4>
<p>目前介绍的例子都是使用双引号创建的字符串,不过 Ruby 也支持用单引号创建字符串。大多数情况下这两种字符串的效果是一样的:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s1">'foo'</span> <span class="c"># 单引号创建的字符串</span>
<span class="gp">=> </span><span class="s2">"foo"</span>
<span class="gp">>> </span><span class="s1">'foo'</span> + <span class="s1">'bar'</span>
<span class="gp">=> </span><span class="s2">"foobar"</span>
</pre></div>
</div>
<p>不过两种方法还是有个很重要的区别:Ruby 不会对单引号字符串进行插值操作:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s1">'#{foo} bar'</span> <span class="c"># 单引号字符串不能进行插值操作</span>
<span class="gp">=> </span><span class="s2">"</span><span class="se">\#</span><span class="s2">{foo} bar"</span>
</pre></div>
</div>
<p>注意控制台是如何使用双引号返回结果的,需要使用反斜线转义特殊字符,例如 <code>#</code>。</p>
<p>如果双引号字符串可以做单引号所做的所有事,而且还能进行插值,那么单引号字符串存在的意义是什么呢?单引号字符串的用处在于它们真的就是字面值,只包含你输入的字符。例如,反斜线在很多系统中都很特殊,就像换行符(<code>\n</code>)一样。如果有一个变量需要包含一个反斜线,使用单引号就很简单:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s1">'\n'</span> <span class="c"># 反斜线和 n 字面值</span>
<span class="gp">=> </span><span class="s2">"</span><span class="se">\\</span><span class="s2">n"</span>
</pre></div>
</div>
<p>和前例的 <code>#</code> 字符一样,Ruby 要使用一个额外的反斜线来转义反斜线,在双引号字符串中,要表达一个反斜线就要使用两个反斜线。对简单的例子来说,这省不了多少事,不过如果有很多需要转义的字符就显得出它的作用了:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s1">'Newlines (\n) and tabs (\t) both use the backslash character \.'</span>
<span class="gp">=> </span><span class="s2">"Newlines (</span><span class="se">\\</span><span class="s2">n) and tabs (</span><span class="se">\\</span><span class="s2">t) both use the backslash character </span><span class="se">\\</span><span class="s2">."</span>
</pre></div>
</div>
<h3 id='section-4-2-3'><span>4.2.3</span> 对象及向其传递消息</h3>
<p>Ruby 中一切皆对象,包括字符串和 <code>nil</code> 都是。我们会在 <a href="chapter4.html#section-4-4-2">4.4.2 节</a>介绍对象技术层面上的意义,不过一般很难通过阅读一本书就理解对象,你要多看一些例子才能建立对对象的感性认识。</p>
<p>不过说出对象的作用就很简单:它可以响应消息。例如,一个字符串对象可以响应 <code>length</code> 这个消息,它返回字符串包含的字符数量:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"foobar"</span>.length <span class="c"># 把 length 消息传递给字符串</span>
<span class="gp">=> </span>6
</pre></div>
</div>
<p>这样传递给对象的消息叫做方法,它是在对象中定义的函数。<sup class="footnote" id="fnref-4-4"><a href="#fn-4-4" rel="footnote">4</a></sup>字符串还可以响应 <code>empty?</code> 方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"foobar"</span>.empty?
<span class="gp">=> </span><span class="nb">false</span>
<span class="gp">>> </span><span class="s2">""</span>.empty?
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>注意 <code>empty?</code> 方法末尾的问号,这是 Ruby 的一个约定,说明方法的返回值是布尔值:<code>true</code> 或 <code>false</code>。布尔值在流程控制中特别有用:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>s <span class="o">=</span> <span class="s2">"foobar"</span>
<span class="gp">>> </span><span class="k">if </span>s.empty?
<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>end
<span class="gp">=> </span><span class="s2">"The string is nonempty"</span>
</pre></div>
</div>
<p>布尔值还可以使用 <code>&&</code>(和)、<code>||</code>(或)和 <code>!</code>(非)操作符结合使用:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>x <span class="o">=</span> <span class="s2">"foo"</span>
<span class="gp">=> </span><span class="s2">"foo"</span>
<span class="gp">>> </span>y <span class="o">=</span> <span class="s2">""</span>
<span class="gp">=> </span><span class="s2">""</span>
<span class="gp">>> </span>puts <span class="s2">"Both strings are empty"</span> <span class="k">if </span>x.empty? <span class="o">&&</span> y.empty?
<span class="gp">=> </span>nil
<span class="gp">>> </span>puts <span class="s2">"One of the strings is empty"</span> <span class="k">if </span>x.empty? <span class="o">||</span> y.empty?
<span class="s2">"One of the strings is empty"</span>
<span class="gp">=> </span>nil
<span class="gp">>> </span>puts <span class="s2">"x is not empty"</span> <span class="k">if</span> !x.empty?
<span class="s2">"x is not empty"</span>
<span class="gp">=> </span>nil
</pre></div>
</div>
<p>因为 Ruby 中的一切都是对象,那么 <code>nil</code> 也是对象,所以它也可以响应方法。举个例子,<code>to_s</code> 方法基本上可以把任何对象转换成字符串:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>nil.to_s
<span class="gp">=> </span><span class="s2">""</span>
</pre></div>
</div>
<p>结果显然是个空字符串,我们可以通过下面的方法串联(chain)验证这一点:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>nil.empty?
NoMethodError: You have a nil object when you didn<span class="se">\'</span>t expect it!
You might have expected an instance of Array.
The error occurred <span class="k">while </span>evaluating nil.empty?
<span class="gp">>> </span>nil.to_s.empty? <span class="c"># 消息串联</span>
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>我们看到,<code>nil</code> 对象本身无法响应 <code>empty?</code> 方法,但是 <code>nil.to_s</code> 可以。</p>
<p>有一个特殊的方法可以测试对象是否为空,你应该能猜到这个方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"foo"</span>.nil?
<span class="gp">=> </span><span class="nb">false</span>
<span class="gp">>> </span><span class="s2">""</span>.nil?
<span class="gp">=> </span><span class="nb">false</span>
<span class="gp">>> </span>nil.nil?
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>下面的代码</p>
<div class="codeblock"><div class="highlight type-shell"><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 class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>string <span class="o">=</span> <span class="s2">"foobar"</span>
<span class="gp">>> </span>puts <span class="s2">"The string '#{string}' is nonempty."</span> unless string.empty?
The string <span class="s1">'foobar'</span> is nonempty.
<span class="gp">=> </span>nil
</pre></div>
</div>
<p>我们需要注意一下 <code>nil</code> 的特殊性,除了 <code>false</code> 本身之外,所有的 Ruby 对象中它是唯一一个布尔值为“假”的:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="k">if </span>nil
<span class="gp">>> </span><span class="nb">true</span>
<span class="gp">>> </span><span class="k">else</span>
<span class="gp">>> </span><span class="nb">false</span> <span class="c"># nil 是假值</span>
<span class="gp">>> </span>end
<span class="gp">=> </span><span class="nb">false</span>
</pre></div>
</div>
<p>基本上所有其他的 Ruby 对象都是“真”的,包括 0:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="k">if </span>0
<span class="gp">>> </span><span class="nb">true</span> <span class="c"># 0(除了 nil 和 false 之外的一切对象)是真值</span>
<span class="gp">>> </span><span class="k">else</span>
<span class="gp">>> </span><span class="nb">false</span>
<span class="gp">>> </span>end
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<h3 id='section-4-2-4'><span>4.2.4</span> 定义方法</h3>
<p>在控制台中,我们可以像定义 <code>home</code> 动作(代码 3.6)和 <code>full_title</code> 帮助方法(代码 4.2)一样进行方法定义。(在控制台中定义方法有点麻烦,我们一般会在文件中定义,不过用来演示还行。)例如,我们要定义一个名为 <code>string_message</code> 的方法,可以接受一个参数,返回值取决于参数是否为空:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>def string_message<span class="o">(</span>string<span class="o">)</span>
<span class="gp">>> </span><span class="k">if </span>string.empty?
<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>end
<span class="gp">>> </span>end
<span class="gp">=> </span>nil
<span class="gp">>> </span>puts string_message<span class="o">(</span><span class="s2">""</span><span class="o">)</span>
It<span class="se">\'</span>s an empty string!
<span class="gp">>> </span>puts string_message<span class="o">(</span><span class="s2">"foobar"</span><span class="o">)</span>
The string is nonempty.
</pre></div>
</div>
<p>注意 Ruby 方法会非显式的返回值:返回最后一个语句的值。在上面的这个例子中,返回的值会根据参数是否为空而返回两个字符串中的一个。Ruby 也支持显式的指定返回值,下面的代码和上面的效果一样:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>def string_message<span class="o">(</span>string<span class="o">)</span>
<span class="gp">>> </span><span class="k">return</span> <span class="s2">"It's an empty string!"</span> <span class="k">if </span>string.empty?
<span class="gp">>> </span><span class="k">return</span> <span class="s2">"The string is nonempty."</span>
<span class="gp">>> </span>end
</pre></div>
</div>
<p>细心的读者可能会发现其实这里第二个 <code>return</code> 不是必须的,作为方法的最后一个表达式,不管有没有 <code>return</code>,字符串 <code>"The string is nonempty."</code> 都会作为返回值。不过两处都加上 <code>return</code> 看起来更好看。</p>
<h3 id='section-4-2-5'><span>4.2.5</span> 回顾一下标题的帮助方法</h3>
<p>下面我们来理解一下代码 4.2 中的 <code>full_title</code> 帮助方法:<sup class="footnote" id="fnref-4-5"><a href="#fn-4-5" rel="footnote">5</a></sup></p>
<div class="codeblock"><div class="highlight type-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="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="p">.</span><span class="nf">empty?</span> <span class="c1"># 布尔测试</span>
<span class="n">base_title</span> <span class="c1"># 非显式返回值</span>
<span class="k">else</span>
<span class="s2">"</span><span class="si">#{</span><span class="n">base_title</span><span class="si">}</span><span class="s2"> | </span><span class="si">#{</span><span class="n">page_title</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># 字符串插值</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>方法定义、变量赋值、布尔测试、流程控制和字符串插值——组合在一起定义了一个可以在网站布局中使用的帮助方法。还用到了 <code>module ApplicationHelper</code>:module 为我们提供了一种把相关方法组织在一起的方式,稍后我们可以使用 <code>include</code> 把它插入其他的类中。编写一般的 Ruby 程序时,你要自己定义一个 module 然后再显式的将其引入类中,但是对于帮助方法所在的 module 就交由 Rails 来处理引入了,最终的结果是 <code>full_title</code> 方法<a href="http://catb.org/jargon/html/A/automagically.html">自动的</a>就可以在所有的视图中使用了。</p>
<h2 id='section-4-3'><span>4.3</span> 其他的数据类型</h2>
<p>虽然 Web 程序一般都是处理字符串,但也需要其他的数据类型来生成字符串。本节我们就来介绍一些对开发 Rails 应用程序很重要的 Ruby 中的其他数据类型。</p>
<h3 id='section-4-3-1'><span>4.3.1</span> 数组和 Range</h3>
<p>数组就是一组顺序特定的元素。本书尚且没有用过数组,不过理解了数组就能很好的理解 Hash (<a href="chapter4.html#section-4-3-3">4.3.3 节</a>),也有助于理解 Rails 中的数据模型(例如 <a href="chapter2.html#section-2-3-3">2.3.3 节</a>中用到的 <code>has_many</code> 关联,<a href="chapter10.html#section-10-1-3">10.1.3 节</a>会做详细介绍)。</p>
<p>目前我们已经花了很多的时间理解字符串,从字符串过渡到数组可以从 <code>split</code> 方法开始:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"foo bar baz"</span>.split <span class="c"># 把字符串分割成有三个元素的数组</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"foo"</span>, <span class="s2">"bar"</span>, <span class="s2">"baz"</span><span class="o">]</span>
</pre></div>
</div>
<p>上述代码的返回结果是一个有三个元素的数组。默认情况下,<code>split</code> 在空格处把字符串分割成数组,当然你几乎可以在任何地方进行分割:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"fooxbarxbazx"</span>.split<span class="o">(</span><span class="s1">'x'</span><span class="o">)</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"foo"</span>, <span class="s2">"bar"</span>, <span class="s2">"baz"</span><span class="o">]</span>
</pre></div>
</div>
<p>和其他编程语言的习惯一样,Ruby 中数组的索引(index)也是从零开始的,数组中第一个元素的索引是 0,第二个元素的索引是 1,依此类推:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a <span class="o">=</span> <span class="o">[</span>42, 8, 17]
<span class="gp">=> </span><span class="o">[</span>42, 8, 17]
<span class="gp">>> </span>a[0] <span class="c"># Ruby 使用方括号获取数组元素</span>
<span class="gp">=> </span>42
<span class="gp">>> </span>a[1]
<span class="gp">=> </span>8
<span class="gp">>> </span>a[2]
<span class="gp">=> </span>17
<span class="gp">>> </span>a[-1] <span class="c"># 索引还可以是负数</span>
<span class="gp">=> </span>17
</pre></div>
</div>
<p>我们看到,在 Ruby 中是使用方括号来获取数组元素的。除了这种方法,Ruby 还为一些常用的元素获取操作提供了别名(synonym):<sup class="footnote" id="fnref-4-6"><a href="#fn-4-6" rel="footnote">6</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a <span class="c"># 只是为了看一下 a 的值是什么</span>
<span class="gp">=> </span><span class="o">[</span>42, 8, 17]
<span class="gp">>> </span>a.first
<span class="gp">=> </span>42
<span class="gp">>> </span>a.second
<span class="gp">=> </span>8
<span class="gp">>> </span>a.last
<span class="gp">=> </span>17
<span class="gp">>> </span>a.last <span class="o">==</span> a[-1] <span class="c"># 用 == 进行对比</span>
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>最后一行介绍了相等比较操作符 <code>==</code>,Ruby 和其他语言一样还提供了对应的 <code>!=</code>(不等)等其他的操作符:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>x <span class="o">=</span> a.length <span class="c"># 和字符串一样,数组也可以响应 length 方法</span>
<span class="gp">=> </span>3
<span class="gp">>> </span>x <span class="o">==</span> 3
<span class="gp">=> </span><span class="nb">true</span>
<span class="gp">>> </span>x <span class="o">==</span> 1
<span class="gp">=> </span><span class="nb">false</span>
<span class="gp">>> </span>x !<span class="o">=</span> 1
<span class="gp">=> </span><span class="nb">true</span>
<span class="gp">>> </span>x ><span class="o">=</span> 1
<span class="gp">=> </span><span class="nb">true</span>
<span class="gp">>> </span>x < 1
<span class="gp">=> </span><span class="nb">false</span>
</pre></div>
</div>
<p>除了 <code>length</code>(上述代码的第一行)之外,数组还可以响应一堆其他的方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a
<span class="gp">=> </span><span class="o">[</span>42, 8, 17]
<span class="gp">>> </span>a.sort
<span class="gp">=> </span><span class="o">[</span>8, 17, 42]
<span class="gp">>> </span>a.reverse
<span class="gp">=> </span><span class="o">[</span>17, 8, 42]
<span class="gp">>> </span>a.shuffle
<span class="gp">=> </span><span class="o">[</span>17, 42, 8]
<span class="gp">>> </span>a
<span class="gp">=> </span><span class="o">[</span>42, 8, 17]
</pre></div>
</div>
<p>注意,上面的方法都没有修改 <code>a</code> 的值。如果你想修改数组的值要使用对应的“炸弹(bang)”方法(之所以这么叫是因为这里的感叹号经常都读作“bang”):</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a
<span class="gp">=> </span><span class="o">[</span>42, 8, 17]
<span class="gp">>> </span>a.sort!
<span class="gp">=> </span><span class="o">[</span>8, 17, 42]
<span class="gp">>> </span>a
<span class="gp">=> </span><span class="o">[</span>8, 17, 42]
</pre></div>
</div>
<p>你还可以使用 <code>push</code> 方法向数组中添加元素,或者使用等价的 <code><<</code> 操作符:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a.push<span class="o">(</span>6<span class="o">)</span> <span class="c"># 把 6 加到数组结尾</span>
<span class="gp">=> </span><span class="o">[</span>42, 8, 17, 6]
<span class="gp">>> </span>a <span class="sh"><< 7 # 把 7 加到数组结尾
=> [42, 8, 17, 6, 7]
>> a << "foo" << "bar" # 串联操作
=> [42, 8, 17, 6, 7, "foo", "bar"]
</span></pre></div>
</div>
<p>最后一个例子说明你可以把添加操作串在一起操作;同时也说明,Ruby 不像很多其他的语言,数组可以包含不同类型的数据(本例中是数字和字符串混合)。</p>
<p>前面我们用 <code>split</code> 把字符串分割成字符串,我们还可以使用 <code>join</code> 方法进行相反的操作:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a
<span class="gp">=> </span><span class="o">[</span>42, 8, 17, 7, <span class="s2">"foo"</span>, <span class="s2">"bar"</span><span class="o">]</span>
<span class="gp">>> </span>a.join <span class="c"># 没有连接符</span>
<span class="gp">=> </span><span class="s2">"428177foobar"</span>
<span class="gp">>> </span>a.join<span class="o">(</span><span class="s1">', '</span><span class="o">)</span> <span class="c"># 连接符是一个逗号和空格</span>
<span class="gp">=> </span><span class="s2">"42, 8, 17, 7, foo, bar"</span>
</pre></div>
</div>
<p>和数组有点类似的是 Range,使用 <code>to_a</code> 方法把它转换成数组或许更好理解:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>0..9
<span class="gp">=> </span>0..9
<span class="gp">>> </span>0..9.to_a <span class="c"># 错了,to_a 在 9 上调用了</span>
NoMethodError: undefined method <span class="sb">`</span>to_a<span class="se">\'</span> <span class="k">for </span>9:Fixnum
<span class="gp">>> </span><span class="o">(</span>0..9<span class="o">)</span>.to_a <span class="c"># 调用 to_a 要用括号包住 Range</span>
<span class="gp">=> </span><span class="o">[</span>0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
</pre></div>
</div>
<p>虽然 <code>0..9</code> 是一个合法的 Range,不过上面第二个表达式告诉我们调用方法时要加上括号。</p>
<p>Range 经常被用来获取一组数组元素:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a <span class="o">=</span> %w[foo bar baz quux] <span class="c"># %w 创建一个元素为字符串的数组</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"foo"</span>, <span class="s2">"bar"</span>, <span class="s2">"baz"</span>, <span class="s2">"quux"</span><span class="o">]</span>
<span class="gp">>> </span>a[0..2]
<span class="gp">=> </span><span class="o">[</span><span class="s2">"foo"</span>, <span class="s2">"bar"</span>, <span class="s2">"baz"</span><span class="o">]</span>
</pre></div>
</div>
<p>Range 也可使用字母:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'e'</span><span class="o">)</span>.to_a
<span class="gp">=> </span><span class="o">[</span><span class="s2">"a"</span>, <span class="s2">"b"</span>, <span class="s2">"c"</span>, <span class="s2">"d"</span>, <span class="s2">"e"</span><span class="o">]</span>
</pre></div>
</div>
<h3 id='section-4-3-2'><span>4.3.2</span> 块</h3>
<p>数组和 Range 可以响应的方法中有很多都可以跟着一个块(block),这是 Ruby 中最强大也是最难理解的功能:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="o">(</span>1..5<span class="o">)</span>.each <span class="o">{</span> |i| puts 2 <span class="k">*</span> i <span class="o">}</span>
2
4
6
8
10
<span class="gp">=> </span>1..5
</pre></div>
</div>
<p>这个代码在 Range <code>(1..5)</code> 上调用了 <code>each</code> 方法,然后又把 <code>{ |i| puts 2*i }</code> 这个块传递给 <code>each</code> 方法。<code>|i|</code> 两边的竖杠在 Ruby 句法中是用来定义块变量的。只有这个方法才知道如何处理后面跟着的块。本例中,Range 的 <code>each</code> 方法会处理后面的块,块中有一个本地变量 <code>i</code>,<code>each</code> 会将 Range 中的各个值传进块中然后执行相应的操作。</p>
<p>花括号是一种定义块的方法,还有另一种方法可用:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="o">(</span>1..5<span class="o">)</span>.each <span class="k">do</span> |i|
<span class="gp">?> </span>puts 2 <span class="k">*</span> i
<span class="gp">>> </span>end
2
4
6
8
10
<span class="gp">=> </span>1..5
</pre></div>
</div>
<p>块可以多于一行,也经常是多于一行的。本书中我们会遵照一个常用的约定,当块只有一行简单的代码时使用花括号形式;当块是一行很长的代码,或者多行时使用 <code>do..end</code> 形式:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="o">(</span>1..5<span class="o">)</span>.each <span class="k">do</span> |number|
<span class="gp">?> </span>puts 2 <span class="k">*</span> number
<span class="gp">>> </span>puts <span class="s1">'--'</span>
<span class="gp">>> </span>end
2
--
4
--
6
--
8
--
10
--
<span class="gp">=> </span>1..5
</pre></div>
</div>
<p>上面的代码用 <code>number</code> 代替了 <code>i</code>,我想告诉你的是任何变量名都可以使用。</p>
<p>除非你已经有了一些编程知识,否则对块的理解是没有捷径的。你要做的是多看,看得多了最后你就会习惯它的用法了。<sup class="footnote" id="fnref-4-7"><a href="#fn-4-7" rel="footnote">7</a></sup> 幸好人类擅长于从实例中归纳出一般性。下面是一些例子,其中几个用到了 <code>map</code> 方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>3.times <span class="o">{</span> puts <span class="s2">"Betelgeuse!"</span> <span class="o">}</span> <span class="c"># 3.times 后跟的块没有变量</span>
<span class="s2">"Betelgeuse!"</span>
<span class="s2">"Betelgeuse!"</span>
<span class="s2">"Betelgeuse!"</span>
<span class="gp">=> </span>3
<span class="gp">>> </span><span class="o">(</span>1..5<span class="o">)</span>.map <span class="o">{</span> |i| i<span class="k">**</span>2 <span class="o">}</span> <span class="c"># ** 表示幂</span>
<span class="gp">=> </span><span class="o">[</span>1, 4, 9, 16, 25]
<span class="gp">>> </span>%w[a b c] <span class="c"># 再说一下,%w 可以创建元素为字符串的数组</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"a"</span>, <span class="s2">"b"</span>, <span class="s2">"c"</span><span class="o">]</span>
<span class="gp">>> </span>%w[a b c].map <span class="o">{</span> |char| char.upcase <span class="o">}</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"A"</span>, <span class="s2">"B"</span>, <span class="s2">"C"</span><span class="o">]</span>
<span class="gp">>> </span>%w[A B C].map <span class="o">{</span> |char| char.downcase <span class="o">}</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"a"</span>, <span class="s2">"b"</span>, <span class="s2">"c"</span><span class="o">]</span>
</pre></div>
</div>
<p>上面的代码说明,<code>map</code> 方法返回的是在数组或 Range 的每个元素上执行块中代码后的结果。</p>
<p>现在我们就可以来理解一下我在 <a href="chapter1.html#section-1-4-4">1.4.4 节</a>中用来生成随机二级域名的那行 Ruby 代码了:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'z'</span><span class="o">)</span>.to_a.shuffle[0..7].join
</pre></div>
</div>
<p>我们一步一步分解一下:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'z'</span><span class="o">)</span>.to_a <span class="c"># 字母表数组</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"a"</span>, <span class="s2">"b"</span>, <span class="s2">"c"</span>, <span class="s2">"d"</span>, <span class="s2">"e"</span>, <span class="s2">"f"</span>, <span class="s2">"g"</span>, <span class="s2">"h"</span>, <span class="s2">"i"</span>, <span class="s2">"j"</span>, <span class="s2">"k"</span>, <span class="s2">"l"</span>, <span class="s2">"m"</span>, <span class="s2">"n"</span>, <span class="s2">"o"</span>,
<span class="s2">"p"</span>, <span class="s2">"q"</span>, <span class="s2">"r"</span>, <span class="s2">"s"</span>, <span class="s2">"t"</span>, <span class="s2">"u"</span>, <span class="s2">"v"</span>, <span class="s2">"w"</span>, <span class="s2">"x"</span>, <span class="s2">"y"</span>, <span class="s2">"z"</span><span class="o">]</span>
<span class="gp">>> </span><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'z'</span><span class="o">)</span>.to_a.shuffle <span class="c"># 打乱数组</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"c"</span>, <span class="s2">"g"</span>, <span class="s2">"l"</span>, <span class="s2">"k"</span>, <span class="s2">"h"</span>, <span class="s2">"z"</span>, <span class="s2">"s"</span>, <span class="s2">"i"</span>, <span class="s2">"n"</span>, <span class="s2">"d"</span>, <span class="s2">"y"</span>, <span class="s2">"u"</span>, <span class="s2">"t"</span>, <span class="s2">"j"</span>, <span class="s2">"q"</span>,
<span class="s2">"b"</span>, <span class="s2">"r"</span>, <span class="s2">"o"</span>, <span class="s2">"f"</span>, <span class="s2">"e"</span>, <span class="s2">"w"</span>, <span class="s2">"v"</span>, <span class="s2">"m"</span>, <span class="s2">"a"</span>, <span class="s2">"x"</span>, <span class="s2">"p"</span><span class="o">]</span>
<span class="gp">>> </span><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'z'</span><span class="o">)</span>.to_a.shuffle[0..7] <span class="c"># 取出前面的 8 个元素</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"f"</span>, <span class="s2">"w"</span>, <span class="s2">"i"</span>, <span class="s2">"a"</span>, <span class="s2">"h"</span>, <span class="s2">"p"</span>, <span class="s2">"c"</span>, <span class="s2">"x"</span><span class="o">]</span>
<span class="gp">>> </span><span class="o">(</span><span class="s1">'a'</span>..<span class="s1">'z'</span><span class="o">)</span>.to_a.shuffle[0..7].join <span class="c"># 将取出的元素合并成字符串</span>
<span class="gp">=> </span><span class="s2">"mznpybuj"</span>
</pre></div>
</div>
<h3 id='section-4-3-3'><span>4.3.3</span> Hash 和 Symbol</h3>
<p>Hash 本质上就是数组,只不过它的索引不局限于使用数字。(实际上在一些语言中,特别是 Perl,因为这个原因就把 Hash 叫做关联数组(associative array)。)Hash 的索引(或者叫“键”)几乎可以是任何对象。例如,我们可以使用字符串当键:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>user <span class="o">=</span> <span class="o">{}</span> <span class="c"># {} 是一个空 Hash</span>
<span class="gp">=> </span><span class="o">{}</span>
<span class="gp">>> </span>user[<span class="s2">"first_name"</span><span class="o">]</span> <span class="o">=</span> <span class="s2">"Michael"</span> <span class="c"># 键为 "first_name",值为 "Michael"</span>
<span class="gp">=> </span><span class="s2">"Michael"</span>
<span class="gp">>> </span>user[<span class="s2">"last_name"</span><span class="o">]</span> <span class="o">=</span> <span class="s2">"Hartl"</span> <span class="c"># 键为 "last_name",值为 "Hartl"</span>
<span class="gp">=> </span><span class="s2">"Hartl"</span>
<span class="gp">>> </span>user[<span class="s2">"first_name"</span><span class="o">]</span> <span class="c"># 获取元素的方式类似数组</span>
<span class="gp">=> </span><span class="s2">"Michael"</span>
<span class="gp">>> </span>user <span class="c"># Hash 的字面量形式</span>
<span class="gp">=> </span><span class="o">{</span><span class="s2">"last_name"</span><span class="o">=</span>><span class="s2">"Hartl"</span>, <span class="s2">"first_name"</span><span class="o">=</span>><span class="s2">"Michael"</span><span class="o">}</span>
</pre></div>
</div>
<p>Hash 通过一对花括号中包含一些键值对的形式表示,只有一对花括号而没有键值对(<code>{}</code>)就是一个空 Hash。需要注意,Hash 中的花括号和块中的花括号不是一个概念。(是的,这可能会让你迷惑。)不过,Hash 虽然和数组类似,但却有一个很重要的区别:Hash 的元素没有特定的顺序。<sup class="footnote" id="fnref-4-8"><a href="#fn-4-8" rel="footnote">8</a></sup> 如果顺序很重要的话就要使用数组了。</p>
<p>通过方括号的形式每次定义一个元素的方式不太敏捷,使用 <code>=></code> 分隔的键值对这种字面量的形式定义 Hash 要简洁得多,我们称后一种方式为“hashrocket”:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>user <span class="o">=</span> <span class="o">{</span> <span class="s2">"first_name"</span> <span class="o">=</span>> <span class="s2">"Michael"</span>, <span class="s2">"last_name"</span> <span class="o">=</span>> <span class="s2">"Hartl"</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span><span class="s2">"last_name"</span><span class="o">=</span>><span class="s2">"Hartl"</span>, <span class="s2">"first_name"</span><span class="o">=</span>><span class="s2">"Michael"</span><span class="o">}</span>
</pre></div>
</div>
<p>在上面的代码中我用到了一个 Ruby 句法约定,在左花括号后面和右花括号前面加入了一个空格,控制台会忽略这些空格。(不要问我为什么这些空格是约定俗成的,或许是某个 Ruby 编程大牛喜欢这种形式,然后约定就产生了。)</p>
<p>目前为止我们的键用的都是字符串,但在 Rails 中用 Symbol 当键却很常见。Symbol 看起来像字符串,只不过没有包含在一对引号中,而是在前面加一个冒号。例如,<code>:name</code> 就是一个 Symbol。你可以把 Symbol 看成没有约束的字符串:<sup class="footnote" id="fnref-4-9"><a href="#fn-4-9" rel="footnote">9</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span><span class="s2">"name"</span>.split<span class="o">(</span><span class="s1">''</span><span class="o">)</span>
<span class="gp">=> </span><span class="o">[</span><span class="s2">"n"</span>, <span class="s2">"a"</span>, <span class="s2">"m"</span>, <span class="s2">"e"</span><span class="o">]</span>
<span class="gp">>> </span>:name.split<span class="o">(</span><span class="s1">''</span><span class="o">)</span>
NoMethodError: undefined method <span class="sb">`</span>split<span class="s1">' for :name:Symbol
>> "foobar".reverse
=> "raboof"
>> :foobar.reverse
NoMethodError: undefined method `reverse'</span> <span class="k">for</span> :foobar:Symbol
</pre></div>
</div>
<p>Symbol 是 Ruby 特有的一个数据类型,其他语言很少用到,初看起来感觉很奇怪,不过 Rails 经常用到它,所以你很快就会习惯的。</p>
<p>用 Symbol 当键,我们可以按照如下的方式定义一个 <code>user</code> Hash:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>user <span class="o">=</span> <span class="o">{</span> :name <span class="o">=</span>> <span class="s2">"Michael Hartl"</span>, :email <span class="o">=</span>> <span class="s2">"michael@example.com"</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span>:name<span class="o">=</span>><span class="s2">"Michael Hartl"</span>, :email<span class="o">=</span>><span class="s2">"michael@example.com"</span><span class="o">}</span>
<span class="gp">>> </span>user[:name] <span class="c"># 获取 :name 对应的值</span>
<span class="gp">=> </span><span class="s2">"Michael Hartl"</span>
<span class="gp">>> </span>user[:password] <span class="c"># 获取一个未定义的键对应的值</span>
<span class="gp">=> </span>nil
</pre></div>
</div>
<p>从上面的例子我们可以看出,Hash 中没有定义的键对应的值是 <code>nil</code>。</p>
<p>因为 Symbol 当键的情况太普遍了,Ruby 1.9 干脆就为这种情况定义了一个新的句法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>h1 <span class="o">=</span> <span class="o">{</span> :name <span class="o">=</span>> <span class="s2">"Michael Hartl"</span>, :email <span class="o">=</span>> <span class="s2">"michael@example.com"</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span>:name<span class="o">=</span>><span class="s2">"Michael Hartl"</span>, :email<span class="o">=</span>><span class="s2">"michael@example.com"</span><span class="o">}</span>
<span class="gp">>> </span>h2 <span class="o">=</span> <span class="o">{</span> name: <span class="s2">"Michael Hartl"</span>, email: <span class="s2">"michael@example.com"</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span>:name<span class="o">=</span>><span class="s2">"Michael Hartl"</span>, :email<span class="o">=</span>><span class="s2">"michael@example.com"</span><span class="o">}</span>
<span class="gp">>> </span>h1 <span class="o">==</span> h2
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>第二个命令把 hashrocket 形式的键值对变成了键后跟着一个冒号然后再跟着一个值的形式:</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="p">{</span> <span class="ss">name: </span><span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email: </span><span class="s2">"michael@example.com"</span> <span class="p">}</span>
</pre></div>
</div>
<p>这种结构更好的沿袭了其他语言(例如 JavaScript)中 Hash 的表现方式,在 Rails 社区中也越来越受欢迎。这两种方式现在都在使用,所以你要能识别它们。</p>
<p>Hash 元素的值可以是任何对象,甚至是一个 Hash,如代码 4.6 所示。</p>
<div class="codeblock has-caption" id="codeblock-4-6"><p class="caption"><span>代码 4.6:</span>Hash 嵌套</p><div class="highlight type-shell"><pre><span class="gp">>> </span>params <span class="o">=</span> <span class="o">{}</span> <span class="c"># 定义一个名为 params(parameters 的简称)的 Hash</span>
<span class="gp">=> </span><span class="o">{}</span>
<span class="gp">>> </span>params[:user] <span class="o">=</span> <span class="o">{</span> name: <span class="s2">"Michael Hartl"</span>, email: <span class="s2">"mhartl@example.com"</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span>:name<span class="o">=</span>><span class="s2">"Michael Hartl"</span>, :email<span class="o">=</span>><span class="s2">"mhartl@example.com"</span><span class="o">}</span>
<span class="gp">>> </span>params
<span class="gp">=> </span><span class="o">{</span>:user<span class="o">=</span>><span class="o">{</span>:name<span class="o">=</span>><span class="s2">"Michael Hartl"</span>, :email<span class="o">=</span>><span class="s2">"mhartl@example.com"</span><span class="o">}}</span>
<span class="gp">>> </span>params[:user][:email]
<span class="gp">=> </span><span class="s2">"mhartl@example.com"</span>
</pre></div>
</div>
<p>这种 Hash 中有 Hash 的形式(或称为 Hash 嵌套)在 Rails 中大量的使用,我们从 <a href="chapter7.html#section-7-3">7.3 节</a>开始会接触到。</p>
<p>与数组和 Range 一样,Hash 也可以响应 <code>each</code> 方法。例如,一个名为 <code>flash</code> 的 Hash,它的键是两个条件判断,<code>:success</code> 和 <code>:error</code>:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>flash <span class="o">=</span> <span class="o">{</span> success: <span class="s2">"It worked!"</span>, error: <span class="s2">"It failed."</span> <span class="o">}</span>
<span class="gp">=> </span><span class="o">{</span>:success<span class="o">=</span>><span class="s2">"It worked!"</span>, :error<span class="o">=</span>><span class="s2">"It failed."</span><span class="o">}</span>
<span class="gp">>> </span>flash.each <span class="k">do</span> |key, value|
<span class="gp">?> </span>puts <span class="s2">"Key #{key.inspect} has value #{value.inspect}"</span>
<span class="gp">>> </span>end
Key :success has value <span class="s2">"It worked!"</span>
Key :error has value <span class="s2">"It failed."</span>
</pre></div>
</div>
<p>注意,数组的 <code>each</code> 方法后面的块只有一个变量,而 Hash 的 <code>each</code> 后面的块接受两个变量,<code>key</code> 和 <code>value</code>。所以 Hash 的 <code>each</code> 方法每次遍历都会以一个键值对为基本单位进行。</p>
<p>上面的示例中用到了很有用的 <code>inspect</code> 方法,返回被调用对象的字符串字面量表现形式:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>puts <span class="o">(</span>1..5<span class="o">)</span>.to_a <span class="c"># 把数组作为字符串输出</span>
1
2
3
4
5
<span class="gp">>> </span>puts <span class="o">(</span>1..5<span class="o">)</span>.to_a.inspect <span class="c"># 输出一个数组字面量形式</span>
<span class="o">[</span>1, 2, 3, 4, 5]
<span class="gp">>> </span>puts :name, :name.inspect
name
:name
<span class="gp">>> </span>puts <span class="s2">"It worked!"</span>, <span class="s2">"It worked!"</span>.inspect
It worked!
<span class="s2">"It worked!"</span>
</pre></div>
</div>
<p>顺便说一下,因为使用 <code>inspect</code> 输出对象的方式经常使用,为此还有一个专门的快捷方式,<code>p</code> 方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>p :name <span class="c"># 等价于 puts :name.inspect</span>
:name
</pre></div>
</div>
<h3 id='section-4-3-4'><span>4.3.4</span> 重温引入 CSS 的代码</h3>
<p>现在我们要重新认识一下代码 4.1 中在布局中引入 CSS 的代码:</p>
<div class="codeblock"><div class="highlight type-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="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>
</pre></div>
</div>
<p>我们现在基本上可以理解这行代码了。在 <a href="chapter4.html#section-4-1">4.1 节</a>中简单的提到过,Rails 定义了一个特殊的函数用来引入样式表,下面的代码</p>
<div class="codeblock"><div class="highlight type-erb"><pre>stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true
</pre></div>
</div>
<p>就是对这个函数的调用。不过还有几个奇怪的地方。第一,括号哪儿去了?因为在 Ruby 中,括号是可以省略的,下面的两行代码是等价的:</p>
<div class="codeblock"><div class="highlight type-erb"><pre># Parentheses on function calls are optional.
stylesheet_link_tag("application", media: "all",
"data-turbolinks-track" => true)
stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true
</pre></div>
</div>
<p>第二,<code>media</code> 部分显然是一个 Hash,但是怎么没用花括号?因为在调用函数时,如果 Hash 是最后一个参数,它的花括号是可以省略的。下面的两行代码是等价的:</p>
<div class="codeblock"><div class="highlight type-erb"><pre># Curly braces on final hash arguments are optional.
stylesheet_link_tag "application", { media: "all",
"data-turbolinks-track" => true }
stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true
</pre></div>
</div>
<p>还有,为什么 <code>data-turbolinks-track</code> 这个键值对使用旧句法?因为如果使用新句法,写成</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="n">data</span><span class="o">-</span><span class="n">turbolinks</span><span class="o">-</span><span class="ss">track: </span><span class="kp">true</span>
</pre></div>
</div>
<p>因为其中包含了连字符,所以这个 Symbol 是不合法的。所以就只能使用旧句法,写成</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="s2">"data-turbolinks-track"</span> <span class="o">=></span> <span class="kp">true</span>
</pre></div>
</div>
<p>最后,为什么下面这两行代码</p>
<div class="codeblock"><div class="highlight type-ruby"><pre><span class="n">stylesheet_link_tag</span> <span class="s2">"application"</span><span class="p">,</span> <span class="ss">media: </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>
</pre></div>
</div>
<p>为什么可以这么写,中间有空格也可以?当然可以,Ruby 不关心有没有换行。[^1-10]我之所以把代码拆成两行,是要保持每行代码不超过 80 列。[^1-11]</p>
<p>所以我们就看到了这样一行代码</p>
<div class="codeblock"><div class="highlight type-erb"><pre>stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true
</pre></div>
</div>
<p>它调用了 <code>stylesheet_link_tag</code> 函数,传进两个参数:一个是字符串,指明样式表的路径;另一个是 Hash,包含两个元素,指明媒介类型,并启用 <a href="https://github.com/rails/turbolinks">Turbolink</a> 功能(Rails 4 的新功能,本书的后续草稿会深入介绍)。因为使用的是 <code><%= %></code>,函数的执行结果会通过 ERb 插入模板中,如果你在浏览器中查看网页的源代码就会看到引入样式表所用的 HTML(参见代码 4.7)。(你可能会在 CSS 的文件名后看到额外的字符,例如 <code>?body=1</code>。这是 Rails 加入的,可以确保 CSS 修改后浏览器会重新加载它。)</p>
<div class="codeblock has-caption" id="codeblock-4-7"><p class="caption"><span>代码 4.7:</span>引入 CSS 的代码生成的 HTML</p><div class="highlight type-html"><pre><span class="nt"><link</span> <span class="na">data-turbolinks-track=</span><span class="s">"true"</span> <span class="na">href=</span><span class="s">"/assets/application.css"</span> <span class="na">media=</span><span class="s">"all"</span>
<span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="nt">/></span>
</pre></div>
</div>
<p>如果你打开 <a href="http://localhost:3000/assets/application.css">http://localhost:3000/assets/application.css</a> 查看 CSS 的话,会发现是空的(除了一些注释)。在<a href="chapter5.html">第 5 章</a>中我们会看介绍如何添加样式。</p>
<h2 id='section-4-4'><span>4.4</span> Ruby 类</h2>
<p>我们之前已经说过 Ruby 中的一切都是对象,本节我们就会自己定义一些对象。Ruby 和其他面向对象(object-oriented)编程语言一样,使用类来组织方法。然后实例化(instantiate)类创建对象。如果你刚接触面向对象编程,这些都似天书一般,那么让我们来看一些实际的例子吧。</p>
<h3 id='section-4-4-1'><span>4.4.1</span> 构造器</h3>
<p>我们看过很多例子使用类初始化对象,不过还没有正式的初始化过。例如,我们使用一个双引号初始化一个字符串,双引号是字符串的字面构造器(literal constructor):</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>s <span class="o">=</span> <span class="s2">"foobar"</span> <span class="c"># 使用双引号的字面构造器</span>
<span class="gp">=> </span><span class="s2">"foobar"</span>
<span class="gp">>> </span>s.class
<span class="gp">=> </span>String
</pre></div>
</div>
<p>我们看到字符串可以响应 <code>class</code> 方法,返回的结果是字符串所属的类。</p>
<p>除了使用字面构造器之外,我们还可以使用等价的具名构造器(named constructor),即在类名上调用 <code>new</code> 方法:<sup class="footnote" id="fnref-4-12"><a href="#fn-4-12" rel="footnote">10</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>s <span class="o">=</span> String.new<span class="o">(</span><span class="s2">"foobar"</span><span class="o">)</span> <span class="c"># 字符串的具名构造器</span>
<span class="gp">=> </span><span class="s2">"foobar"</span>
<span class="gp">>> </span>s.class
<span class="gp">=> </span>String
<span class="gp">>> </span>s <span class="o">==</span> <span class="s2">"foobar"</span>
<span class="gp">=> </span><span class="nb">true</span>
</pre></div>
</div>
<p>上面的代码和字面构造器是等价的,只是更能表现我们的意图。</p>
<p>数组和字符串类似:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>a <span class="o">=</span> Array.new<span class="o">([</span>1, 3, 2]<span class="o">)</span>
<span class="gp">=> </span><span class="o">[</span>1, 3, 2]
</pre></div>
</div>
<p>不过 Hash 就有些不同了。数组的构造器 <code>Array.new</code> 可接受一个可选的参数指明数组的初始值,<code>Hash.new</code> 可接受一个参数指明元素的默认值,就是当键不存在时返回的值:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>h <span class="o">=</span> Hash.new
<span class="gp">=> </span><span class="o">{}</span>
<span class="gp">>> </span>h[:foo] <span class="c"># 试图获取不存在的键 :foo 对应的值</span>
<span class="gp">=> </span>nil
<span class="gp">>> </span>h <span class="o">=</span> Hash.new<span class="o">(</span>0<span class="o">)</span> <span class="c"># 设置不存在的键返回 0 而不是 nil</span>
<span class="gp">=> </span><span class="o">{}</span>
<span class="gp">>> </span>h[:foo]
<span class="gp">=> </span>0
</pre></div>
</div>
<p>在类上调用的方法,如本例的 <code>new</code>,我们称之为类方法(class method)。在类上调用 <code>new</code> 得到的结果是这个类的一个对象,也叫做这个类的实例(instance)。在实例上调用的方法,例如 <code>length</code>,叫做实例方法(instance method)。</p>
<h3 id='section-4-4-2'><span>4.4.2</span> 类的继承</h3>
<p>学习类时,理清类的继承关系会很有用,我们可以使用 <code>superclass</code> 方法:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">>> </span>s <span class="o">=</span> String.new<span class="o">(</span><span class="s2">"foobar"</span><span class="o">)</span>
<span class="gp">=> </span><span class="s2">"foobar"</span>
<span class="gp">>> </span>s.class <span class="c"># 查找 s 所属的类</span>
<span class="gp">=> </span>String
<span class="gp">>> </span>s.class.superclass <span class="c"># 查找 String 的父类</span>
<span class="gp">=> </span>Object
<span class="gp">>> </span>s.class.superclass.superclass <span class="c"># Ruby 1.9 使用 BasicObject 作为基类</span>