forked from railstutorial-china/rails42
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chapter3.html
1614 lines (1381 loc) · 128 KB
/
chapter3.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 教程 - 第 3 章 基本静态的页面</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.alpha.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">
<article class="article">
<section data-type="chapter" id="mostly-static-pages">
<h1><span class="title-label">第 3 章</span> 基本静态的页面</h1>
<p>从本章开始,我们要开发一个专业级演示应用,本书后续内容会一直开发这个应用。最终完成的应用包含用户、微博功能,以及完整的登录和用户身份认证系统,不过我们先从一个看似功能有限的话题出发——创建静态页面。这看似简单的一件事却是一个很好的锻炼,极具意义,对这个初建的应用而言也是个很好的开端。</p>
<p>虽然 Rails 被设计出来是为了开发基于数据库的动态网站,不过它也能胜任使用纯 HTML 创建的静态页面。其实,使用 Rails 创建静态页面有一个好处:添加少量动态内容十分容易。这一章就教你怎么做。在这个过程中,我们会一窥自动化测试(automated testing)的面目,自动化测试可以让我们相信自己编写的代码是正确的。而且,编写一个好的测试组件还可以让我们信心十足地重构代码,修改实现过程但不影响功能。</p>
<section data-type="sect1" id="sample-app-setup">
<h1><span class="title-label">3.1.</span> 创建演示应用</h1>
<p>和<a href="chapter2.html#a-toy-app">第 2 章</a>一样,我们要先创建一个新 Rails 项目,名为 <code>sample_app</code>,如<a href="#listing-rails-new-sample-app">代码清单 3.1</a> 所示:<sup>[<a id="fn-ref-1" href="#fn-1">1</a>]</sup></p>
<div id="listing-rails-new-sample-app" data-type="listing">
<h5><span class="title-label">代码清单 3.1:</span>创建一个新应用</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span><span class="nb">cd</span> ~/workspace
<span class="nv">$ </span>rails _4.2.0_ new sample_app
<span class="nv">$ </span><span class="nb">cd </span>sample_app/
</pre></div>
</div>
<p>(和 <a href="chapter2.html#planning-the-application">2.1 节</a>一样,如果使用云端 IDE,可以在同一个工作空间中创建这个应用,没必要再新建一个工作空间。)</p>
<p>类似 <a href="chapter2.html#planning-the-application">2.1 节</a>,接下来我们要用文本编辑器打开并编辑 <code>Gemfile</code>,写入应用所需的 gem。<a href="#listing-gemfile-sample-app">代码清单 3.2</a> 与<a href="chapter1.html#listing-gemfile-sqlite-version">代码清单 1.5</a> 和<a href="chapter1.html#listing-demo-gemfile-sqlite-version-redux">代码清单 2.1</a> 一样,不过 <code>test</code> 组中的 gem 有所不同,稍后会做进一步设置(<a href="#advanced-testing-setup">3.7 节</a>)。注意,如果现在你想安装这个应用使用的所有 gem,要写入<a href="chapter11.html#listing-final-gemfile">代码清单 11.66</a> 中的内容。</p>
<div id="listing-gemfile-sample-app" data-type="listing">
<h5><span class="title-label">代码清单 3.2:</span>演示应用的 <code>Gemfile</code></h5>
<div class="highlight language-sh"><pre><span class="nb">source</span> <span class="s1">'https://rubygems.org'</span>
gem <span class="s1">'rails'</span>, <span class="s1">'4.2.0'</span>
gem <span class="s1">'sass-rails'</span>, <span class="s1">'5.0.0.beta1'</span>
gem <span class="s1">'uglifier'</span>, <span class="s1">'2.5.3'</span>
gem <span class="s1">'coffee-rails'</span>, <span class="s1">'4.1.0'</span>
gem <span class="s1">'jquery-rails'</span>, <span class="s1">'4.0.0.beta2'</span>
gem <span class="s1">'turbolinks'</span>, <span class="s1">'2.3.0'</span>
gem <span class="s1">'jbuilder'</span>, <span class="s1">'2.2.3'</span>
gem <span class="s1">'sdoc'</span>, <span class="s1">'0.4.0'</span>, group: :doc
group :development, :test <span class="k">do</span>
gem <span class="s1">'sqlite3'</span>, <span class="s1">'1.3.9'</span>
gem <span class="s1">'byebug'</span>, <span class="s1">'3.4.0'</span>
gem <span class="s1">'web-console'</span>, <span class="s1">'2.0.0.beta3'</span>
gem <span class="s1">'spring'</span>, <span class="s1">'1.1.3'</span>
end
group :test <span class="k">do</span>
gem <span class="s1">'minitest-reporters'</span>, <span class="s1">'1.0.5'</span>
gem <span class="s1">'mini_backtrace'</span>, <span class="s1">'0.1.3'</span>
gem <span class="s1">'guard-minitest'</span>, <span class="s1">'2.3.1'</span>
end
group :production <span class="k">do</span>
gem <span class="s1">'pg'</span>, <span class="s1">'0.17.1'</span>
gem <span class="s1">'rails_12factor'</span>, <span class="s1">'0.0.2'</span>
end
</pre></div>
</div>
<p>和前两章一样,我们要执行 <code>bundle install</code> 命令安装并导入 <code>Gemfile</code> 中指定的 gem,而且指定 <code>--without production</code> 选项,<sup>[<a id="fn-ref-2" href="#fn-2">2</a>]</sup>不安装生产环境使用的 gem:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle install --without production
</pre></div>
</div>
<p>运行这个命令后不会在开发环境中安装 PostgreSQL 所需的 <code>pg</code> gem,在生产环境和测试环境中我们使用 SQLite。Heroku 极力不建议在开发环境和生产环境中使用不同的数据库,但是对这个演示应用来说这两种数据库没什么差别,而且在本地安装配置 SQLite 比 PostgreSQL 容易得多。<sup>[<a id="fn-ref-3" href="#fn-3">3</a>]</sup>如果你之前安装了某个 gem(例如 Rails 本身)的其他版本,和 <code>Gemfile</code> 中指定的版本号不同,最好再执行 <code>bundle update</code> 命令,更新 gem,确保安装的版本和指定的一致:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle update
</pre></div>
</div>
<p>最后,我们还要初始化 Git 仓库:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git init
<span class="nv">$ </span>git add -A
<span class="nv">$ </span>git commit -m <span class="s2">"Initialize repository"</span>
</pre></div>
</div>
<p>和第一个应用一样,我建议你更新一下 <code>README</code> 文件(在应用的根目录中),更好的描述这个应用。我们先把这个文件的格式从 RDoc 改为 Markdown:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="gp">$ </span>git mv README.rdoc README.md</code></pre>
</div>
<p>然后写入<a href="#listing-sample-app-readme">代码清单 3.3</a> 中的内容。</p>
<div id="listing-sample-app-readme" data-type="listing">
<h5><span class="title-label">代码清单 3.3:</span>修改演示应用的 <code>README</code> 文件</h5>
<div class="highlight language-text"><pre># Ruby on Rails Tutorial: sample application
This is the sample application for the
[*Ruby on Rails Tutorial:
Learn Web Development with Rails*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/).
</pre></div>
</div>
<p>最后,提交这次改动:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="gp">$ </span>git commit -am <span class="s2">"Improve the README"</span></code></pre>
</div>
<p>你可能还记得,在 <a href="chapter1.html#branch-edit-commit-merge">1.4.4 节</a>,我们使用 <code>git commit -a -m "Message"</code> 命令,指定了“全部变化”的旗标 <code>-a</code> 和提交信息旗标 <code>-m</code>。如上面这个命令所示,我们可以把两个旗标合在一起,变成 <code>git commit -am "Message"</code>。</p>
<p>既然本书后续内容会一直使用这个演示应用,那么最好<a href="https://bitbucket.org/repo/create">在 Bitbucket 中新建一个仓库</a>,把这个应用推送上去:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git remote add origin [email protected]:<username>/sample_app.git
<span class="nv">$ </span>git push -u origin --all <span class="c"># 首次推送这个应用</span>
</pre></div>
</div>
<p>为了避免以后遇到焦头烂额的问题,在这个早期阶段也可以把应用部署到 Heroku 中。参照<a href="chapter1.html#from-zero-to-deploy">第 1 章</a>和<a href="chapter2.html#a-toy-app">第 2 章</a>,我建议使用<a href="chapter1.html#listing-hello-action">代码清单 1.8</a> 和<a href="chapter1.html#listing-default-root-route">代码清单 1.9</a> 中的代码,创建一个显示“hello, world!”的首页。然后提交改动,再推送到 Heroku 中:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git commit -am <span class="s2">"Add hello"</span>
<span class="nv">$ </span>heroku create
<span class="nv">$ </span>git push heroku master
</pre></div>
</div>
<p>(和 <a href="chapter1.html#deploying">1.5 节</a>一样,你可能会看到一些警告消息,现在暂且不管,<a href="chapter7.html#professional-grade-deployment">7.5 节</a>会解决。)除了 Heroku 为应用分配的地址之外,看到的页面应该和<a href="chapter1.html#fig-heroku-app">图 1.18</a> 一样。</p>
<p>在阅读本书的过程中,我建议你定期推送和部署,这样不仅能在远程仓库中备份,而且还能尽早发现在生产环境中可能出现的问题。如果遇到和 Heroku 有关的问题,可以查看生产环境中的日志,试着找出问题所在:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>heroku logs
</pre></div>
</div>
<p>注意,如果你决定把真实的应用放到 Heroku 中,一定要按照 <a href="chapter7.html#professional-grade-deployment">7.5 节</a>介绍的方法配置 Unicorn。</p>
</section>
<section data-type="sect1" id="static-pages">
<h1><span class="title-label">3.2.</span> 静态页面</h1>
<p>前一节的准备工作做好之后,我们可以开始开发这个演示应用了。本节,我们要向开发动态页面迈出第一步:创建一些 Rails 动作和视图,但只包含静态 HTML。Rails 动作放在控制器中(MVC 中的 C,参见 <a href="chapter1.html#model-view-controller">1.3.3 节</a>),其中的动作是为了实现相关的功能。<a href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了控制器,全面熟悉 <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">REST 架构</a>之后(从<a href="chapter6.html#modeling-users">第 6 章</a>开始),你会更深入地理解控制器。回想一下 <a href="chapter1.html#the-first-application">1.3 节</a>介绍的 Rails 项目文件夹结构(<a href="chapter1.html#fig-directory-structure-rails">图 1.4</a>),会对我们有所帮助。这一节主要在 <code>app/controllers</code> 和 <code>app/views</code> 两个文件夹中工作。</p>
<p>在 <a href="chapter1.html#branch-edit-commit-merge">1.4.4 节</a>我们说过,使用 Git 时最好在单独的主题分支中完成工作。如果你使用 Git 做版本控制,现在应该执行下述命令,切换到一个主题分支中,然后再创建静态页面:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git checkout master
<span class="nv">$ </span>git checkout -b static-pages
</pre></div>
</div>
<p>(第一个命令的作用是确保我们现在处于主分支中,这样才能基于 <code>master</code> 分支创建 <code>static-pages</code> 分支。如果你当前就在主分支中,可以不执行这个命令。)</p>
<section data-type="sect2" id="generated-static-pages">
<h2><span class="title-label">3.2.1.</span> 生成静态页面</h2>
<p>下面我们要使用第 2 章用来生成脚手架的 <code>generate</code> 命令生成一个控制器,既然这个控制器用来处理静态页面,那就把它命名为 <code>StaticPages</code> 吧。可以看出,控制器的名字使用<a href="https://en.wikipedia.org/wiki/CamelCase">驼峰式命名法</a>。我们计划创建“首页”,“帮助”页面和“关于”页面,对应的动作名分别为 <code>home</code>、<code>help</code> 和 <code>about</code>。<code>generate</code> 命令可以接收一个可选的参数列表,指定要创建的动作。我们要在命令行中指定“首页”和“帮助”页面的动作,但故意不指定“关于”页面的动作,在 <a href="#getting-started-with-testing">3.3 节</a>再介绍怎么添加这个动作。生成静态页面控制器的命令如<a href="#listing-generating-pages">代码清单 3.4</a> 所示。</p>
<div id="listing-generating-pages" data-type="listing">
<h5><span class="title-label">代码清单 3.4:</span>生成静态页面控制器</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails generate controller StaticPages home <span class="nb">help</span>
<span class="nb"> </span>create app/controllers/static_pages_controller.rb
route get <span class="s1">'static_pages/help'</span>
route get <span class="s1">'static_pages/home'</span>
invoke erb
create app/views/static_pages
create app/views/static_pages/home.html.erb
create app/views/static_pages/help.html.erb
invoke test_unit
create <span class="nb">test</span>/controllers/static_pages_controller_test.rb
invoke helper
create app/helpers/static_pages_helper.rb
invoke test_unit
create <span class="nb">test</span>/helpers/static_pages_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/static_pages.js.coffee
invoke scss
create app/assets/stylesheets/static_pages.css.scss
</pre></div>
</div>
<p>顺便说一下,<code>rails generate</code> 可以简写成 <code>rails g</code>。除此之外,Rails 还提供了几个命令的简写形式,参见<a href="#table-shortcuts">表 3.1</a>。为了表述明确,本书会一直使用命令的完整形式,但在实际使用中,大多数 Rails 开发者或多或少都会使用简写形式。</p>
<table id="table-shortcuts" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 3.1:</span>Rails 中一些命令的简写形式</caption>
<colgroup>
<col style="width: 50%;" />
<col style="width: 50%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">完整形式</th>
<th class="tableblock halign-left valign-top">简写形式</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails server</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails s</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails console</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails c</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails generate</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails g</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ bundle install</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ bundle</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rake test</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rake</code></p></td>
</tr>
</tbody>
</table>
<p>在继续之前,如果你使用 Git,最好把静态页面控制器对应的文件推送到远程仓库:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git status
<span class="nv">$ </span>git add -A
<span class="nv">$ </span>git commit -m <span class="s2">"Add a Static Pages controller"</span>
<span class="nv">$ </span>git push -u origin static-pages
</pre></div>
</div>
<p>最后一个命令的意思是,把 <code>static-pages</code> 主题分支推送到 Bitbucket。以后再推送时,可以省略后面的参数,简写成:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="gp">$ </span>git push</code></pre>
</div>
<p>在现实的开发过程中,我一般都会先提交再推送,但是为了行文简洁,从这往后我们会省略提交这一步。</p>
<p>注意,在<a href="#listing-generating-pages">代码清单 3.4</a> 中,我们传入的控制器名使用驼峰式,创建的控制器文件名使用<a href="https://en.wikipedia.org/wiki/Snake_case">蛇底式</a>。所以,传入“StaticPages”得到的文件是 <code>static_pages_controller.rb</code>。这只是一种约定。其实在命令行中也可以使用蛇底式:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails generate controller static_pages ...
</pre></div>
</div>
<p>这个命令也会生成名为 <code>static_pages_controller.rb</code> 的控制器文件。因为 Ruby 的类名使用驼峰式(<a href="chapter4.html#ruby-classes">4.4 节</a>),所以提到控制器时我会使用驼峰式,不过这是我的个人选择。(因为 Ruby 文件名一般使用蛇底式,所以 Rails 生成器使用 <a href="http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore"><code>underscore</code></a> 方法把驼峰式转换成蛇底式。)</p>
<p>顺便说一下,如果在生成代码时出现了错误,知道如何撤销操作就很有用了。<a href="#aside-undoing-things">旁注 3.1</a> 中介绍了一些如何在 Rails 中撤销操作的方法。</p>
<div data-type="sidebar" id="aside-undoing-things" class="sidebar">
<h5>旁注 3.1:撤销操作</h5>
<p>即使再小心,在开发 Rails 应用的过程中也可能会犯错。幸好 Rails 提供了一些工具能够帮助我们还原操作。</p>
<p>举例来说,一个常见的情况是,更改控制器的名字,这时你得删除生成的文件。生成控制器时,除了控制器文件本身之外,Rails 还会生成很多其他文件(参见<a href="#listing-generating-pages">代码清单 3.4</a>)。撤销生成的文件不仅仅要删除控制器文件,还要删除一些辅助的文件。(在 <a href="chapter2.html#the-users-resource">2.2 节</a>和 <a href="chapter2.html#the-microposts-resource">2.3 节</a>我们看到,<code>rails generate</code> 命令还会自动修改 <code>routes.rb</code> 文件,因此我们也想自动撤销这些修改。)在 Rails 中,我们可以使用 <code>rails destroy</code> 命令完成撤销操作。一般来说,下面这两个命令是相互抵消的:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails generate controller StaticPages home <span class="nb">help</span>
<span class="nv">$ </span>rails destroy controller StaticPages home <span class="nb">help</span>
</pre></div>
</div>
<p><a href="chapter6.html#modeling-users">第 6 章</a>会使用下面的命令生成模型:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails generate model User name:string email:string
</pre></div>
</div>
<p>这个操作可以使用下面的命令撤销:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails destroy model User
</pre></div>
</div>
<p>(在这个例子中,我们可以省略命令行中其余的参数。读到<a href="chapter6.html#modeling-users">第 6 章</a>时,看看你能否发现为什么可以这么做。)</p>
<p>对模型来说,还涉及到撤销迁移。<a href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了迁移,<a href="chapter6.html#modeling-users">第 6 章</a>开始会深入介绍。迁移通过下面的命令改变数据库的状态:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle <span class="nb">exec </span>rake db:migrate
</pre></div>
</div>
<p>我们可以使用下面的命令撤销前一个迁移操作:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle <span class="nb">exec </span>rake db:rollback
</pre></div>
</div>
<p>如果要回到最开始的状态,可以使用:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>bundle <span class="nb">exec </span>rake db:migrate <span class="nv">VERSION</span><span class="o">=</span>0
</pre></div>
</div>
<p>你可能猜到了,把数字 0 换成其他的数字就会回到相应的版本,这些版本数字是按照迁移执行的顺序排列的。</p>
<p>知道这些技术,我们就可以得心应对开发过程中遇到的各种问题了。</p>
</div>
<p><a href="#listing-generating-pages">代码清单 3.4</a> 中生成静态页面控制器的命令会自动修改路由文件(<code>config/routes.rb</code>)。我们在 <a href="chapter1.html#hello-world">1.3.4 节</a>已经简略介绍过路由文件,它的作用是实现 URL 和网页之间的对应关系(<a href="chapter2.html#fig-mvc-detailed">图 2.11</a>)。路由文件在 <code>config</code> 文件夹中。Rails 在这个文件夹中存放应用的配置文件(<a href="#fig-config-directory-rails">图 3.1</a>)。</p>
<div id="fig-config-directory-rails" class="figure"><img src="images/chapter3/config_directory_3rd_edition.png" alt="config directory 3rd edition" /><div class="figcaption"><span class="title-label">图 3.1:</span>演示应用 <code>config</code> 文件夹中的内容</div></div>
<p>因为生成控制器时我们指定了 <code>home</code> 和 <code>help</code> 动作,所以在路由文件中已经添加了相应的规则,如<a href="#listing-pages-routes">代码清单 3.5</a> 所示。</p>
<div id="listing-pages-routes" data-type="listing">
<h5><span class="title-label">代码清单 3.5:</span>静态页面控制器中 <code>home</code> 和 <code>help</code> 动作的路由</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-sh"><pre>Rails.application.routes.draw <span class="k">do</span>
<span class="hll"> get <span class="s1">'static_pages/home'</span>
</span><span class="hll"> get <span class="s1">'static_pages/help'</span>
</span> .
.
.
end
</pre></div>
</div>
<p>如下的规则</p>
<div data-type="listing">
<pre class="highlight language-rb"><code><span class="n">get</span> <span class="s1">'static_pages/home'</span></code></pre>
</div>
<p>把发给 /static_pages/home 的请求映射到静态页面控制器中的 <code>home</code> 动作上。另外,<code>get</code> 表明这个路由响应 GET 请求。GET 是 HTTP(超文本传输协议,Hypertext Transfer Protocol)支持的基本请求方法之一(<a href="#aside-get-etc">旁注 3.2</a>)。在这个例子中,当我们在静态页面控制器中生成 <code>home</code> 动作时,就自动在 /static_pages/home 地址上获得了一个页面。若想查看这个页面,按照 <a href="chapter1.html#rails-server">1.3.2 节</a>中的方法,启动 Rails 开发服务器:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails server -b <span class="nv">$IP</span> -p <span class="nv">$PORT</span> <span class="c"># 如果在自己的电脑中,只需执行 `rails server`</span>
</pre></div>
</div>
<p>然后访问 <a href="http://localhost:3000/static_pages/home">/static_pages/home</a>,如<a href="#fig-raw-home-view">图 3.2</a> 所示。</p>
<div id="fig-raw-home-view" class="figure"><img src="images/chapter3/raw_home_view_3rd_edition.png" alt="raw home view 3rd edition" /><div class="figcaption"><span class="title-label">图 3.2:</span>简陋的首页(<a href="http://localhost:3000/static_pages/home">/static_pages/home</a>)</div></div>
<div data-type="sidebar" id="aside-get-etc" class="sidebar">
<h5>旁注3.2:GET 等</h5>
<p>超文本传输协议(<a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods">HTTP</a>)定义了几个基本操作,<code>GET</code>、<code>POST</code>、<code>PATCH</code> 和 <code>DELETE</code>。这四个动词表示客户端电脑(通常安装了一种浏览器,例如 Chrome、Firefox 或 Safari)和服务器(通常会运行一个 Web 服务器,例如 Apache 或 Nginx)之间的操作。(有一点很重要,你要知道,在本地电脑中开发 Rails 应用时,客户端和服务器在同一台物理设备中,但是二者是不同的概念。)受 REST 架构影响的 Web 框架(包括 Rails)都很重视对 HTTP 动词的实现,我们在<a href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了 REST,从<a href="chapter7.html#sign-up">第 7 章</a>开始会做更详细的介绍。</p>
<p><code>GET</code> 是最常用的 HTTP 操作,用来读取网络中的数据。它的意思是“读取一个网页”,当你访问 <a href="http://www.google.com" class="bare">http://www.google.com</a> 或 <a href="http://www.wikipedia.org" class="bare">http://www.wikipedia.org</a> 时,浏览器发送的就是 <code>GET</code> 请求。<code>POST</code> 是第二种最常用的操作,当你提交表单时浏览器发送的就是 <code>POST</code> 请求。在 Rails 应用中,<code>POST</code> 请求一般用来创建某个东西(不过 HTTP 也允许 <code>POST</code> 执行更新操作)。例如,提交注册表单时发送的 <code>POST</code> 请求会在网站中创建一个新用户。另外两个动词,<code>PATCH</code> 和 <code>DELETE</code>,分别用来更新和销毁服务器上的某个东西。这两个操作没 <code>GET</code> 和 <code>POST</code> 那么常用,因为浏览器没有内建对这两种请求的支持,不过有些 Web 框架(包括 Rails)通过一些聪明的处理方式,让它看起来就像是浏览器发出的一样。所以,这四种请求类型 Rails 都支持。</p>
</div>
<p>要想弄明白这个页面是怎么来的,我们先在文本编辑器中看一下静态页面控制器文件。你应该会看到类似<a href="#listing-static-pages-controller">代码清单 3.6</a> 所示的内容。你可能注意到了,不像<a href="chapter2.html#a-toy-app">第 2 章</a>中的用户和微博控制器,静态页面控制器没使用标准的 REST 动作。这对静态页面来说是很常见的,毕竟 REST 架构不能解决所有问题。</p>
<div id="listing-static-pages-controller" data-type="listing">
<h5><span class="title-label">代码清单 3.6:</span>代码清单 3.4 生成的静态页面控制器</h5>
<div class="source-file">app/controllers/static_pages_controller.rb</div>
<div class="highlight language-ruby"><pre><span class="k">class</span> <span class="nc">StaticPagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">home</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">help</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>从上面代码中的 <code>class</code> 可以看出,<code>static_pages_controller.rb</code> 文件中定义了一个类,名为 <code>StaticPagesController</code>。类是一种组织函数(也叫方法)的有效方式,例如 <code>home</code> 和 <code>help</code> 动作就是方法,使用 <code>def</code> 关键字定义。<a href="chapter2.html#inheritance-hierarchies">2.3.4</a> 节说过,尖括号 <code><</code> 表示 <code>StaticPagesController</code> 继承自 <code>ApplicationController</code> 类,这就意味着我们定义的页面拥有了 Rails 提供的大量功能。(我们会在 <a href="chapter4.html#ruby-classes">4.4 节</a>更详细的介绍类和继承。)</p>
<p>在本例中,静态页面控制器中的两个方法默认都是空的:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="k">def</span> <span class="nf">home</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">help</span>
<span class="k">end</span>
</pre></div>
</div>
<p>如果是普通的 Ruby 代码,这两个方法什么也做不了。不过在 Rails 中就不一样了,<code>StaticPagesController</code> 是一个 Ruby 类,但是因为它继承自 <code>ApplicationController</code>,其中的方法对 Rails 来说就有了特殊意义:访问 /static_pages/home 时,Rails 会在静态页面控制器中寻找 <code>home</code> 动作,然后执行该动作,再渲染相应的视图(MVC 中的 V,参见 <a href="chapter1.html#model-view-controller">1.3.3 节</a>)。在本例中,<code>home</code> 动作是空的,所以访问 /static_pages/home 后只会渲染视图。那么,视图是什么样子,怎么才能找到它呢?</p>
<p>如果你再看一下<a href="#listing-generating-pages">代码清单 3.4</a> 的输出,或许能猜到动作和视图之间的对应关系:<code>home</code> 动作对应的视图是 <code>home.html.erb</code>。<a href="#slightly-dynamic-pages">3.4 节</a>会告诉你 <code>.erb</code> 是什么意思。看到 <code>.html</code> 你或许就不奇怪了,这个文件基本上就是 HTML,如<a href="#listing-raw-home-view">代码清单 3.7</a> 所示。</p>
<div id="listing-raw-home-view" data-type="listing">
<h5><span class="title-label">代码清单 3.7:</span>为“首页”生成的视图</h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-erb"><pre><span class="x"><h1>StaticPages#home</h1></span>
<span class="x"><p>Find me in app/views/static_pages/home.html.erb</p></span>
</pre></div>
</div>
<p><code>help</code> 动作的视图类似,如<a href="#listing-raw-help-view">代码清单 3.8</a> 所示。</p>
<div id="listing-raw-help-view" data-type="listing">
<h5><span class="title-label">代码清单 3.8:</span>为“帮助”页面生成的视图</h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-erb"><pre><span class="x"><h1>StaticPages#help</h1></span>
<span class="x"><p>Find me in app/views/static_pages/help.html.erb</p></span>
</pre></div>
</div>
<p>这两个视图都只是占位用的,它们的内容中都有一个一级标题(<code>h1</code> 标签)和一个显示视图文件完整路径的段落(<code>p</code> 标签)。</p>
</section>
<section data-type="sect2" id="custom-static-pages">
<h2><span class="title-label">3.2.2.</span> 修改静态页面中的内容</h2>
<p>我们会在 <a href="#slightly-dynamic-pages">3.4 节</a>添加一些简单的动态内容。现在,这些静态内容的存在是为了强调一件很重要的事:Rails 的视图可以只包含静态的 HTML。所以我们甚至无需了解 Rails 就可以修改“首页”和“帮助”页面的内容,如<a href="#listing-custom-home-page">代码清单 3.9</a> 和 <a href="#listing-custom-help-page">3.10</a> 所示。</p>
<div id="listing-custom-home-page" data-type="listing">
<h5><span class="title-label">代码清单 3.9:</span>修改“首页”的 HTML</h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-html"><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://www.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>
<div id="listing-custom-help-page" data-type="listing">
<h5><span class="title-label">代码清单 3.10:</span>修改“帮助”页面的 HTML</h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-html"><pre><span class="nt"><h1></span>Help<span class="nt"></h1></span>
<span class="nt"><p></span>
Get help on the Ruby on Rails Tutorial at the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/#help"</span><span class="nt">></span>Rails Tutorial help section<span class="nt"></a></span>.
To get help on this sample app, see the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/book"</span><span class="nt">><em></span>Ruby on Rails Tutorial<span class="nt"></em></span>
book<span class="nt"></a></span>.
<span class="nt"></p></span>
</pre></div>
</div>
<p>修改之后,这两个页面显示的内容如<a href="#fig-custom-home-page">图 3.3</a> 和 <a href="#fig-custom-help-page">3.4</a> 所示。</p>
<div id="fig-custom-home-page" class="figure"><img src="images/chapter3/custom_home_page.png" alt="custom home page" /><div class="figcaption"><span class="title-label">图 3.3:</span>修改后的“首页”</div></div>
<div id="fig-custom-help-page" class="figure"><img src="images/chapter3/custom_help_page_3rd_edition.png" alt="custom help page 3rd edition" /><div class="figcaption"><span class="title-label">图 3.4:</span>修改后的“帮助”页面</div></div>
</section>
</section>
<section data-type="sect1" id="getting-started-with-testing">
<h1><span class="title-label">3.3.</span> 开始测试</h1>
<p>我们创建并修改了“首页”和“帮助”页面的内容,下面要添加“关于”页面。做这样的改动时,最好编写自动化测试确认实现的方法正确。对本书开发的应用来说,我们编写的测试组件有两个作用:其一,是一种安全防护措施;其二,作为源码的文档。虽然要编写额外的代码,但是如果方法得当,测试能协助我们快速开发,因为有了测试查找问题所用的时间会变少。不过,我们要善于编写测试才行,所以要尽早开始练习。</p>
<p>几乎每个 Rails 开发者都认同测试是好习惯,但具体的作法多种多样。最近有一场针对“测试驱动开发”(Test-Driven Development,简称 TDD)的辩论<sup>[<a id="fn-ref-4" href="#fn-4">4</a>]</sup>,十分热闹。TDD 是一种测试技术,程序员要先编写失败的测试,然后再编写应用的代码,让测试通过。本书采用一种轻量级,符合直觉的测试方案,只在适当的时候才使用 TDD,而不严格遵守 TDD 理念(<a href="#aside-when-to-test">旁注 3.3</a>)。</p>
<div data-type="sidebar" id="aside-when-to-test" class="sidebar">
<h5>旁注 3.3:什么时候测试</h5>
<p>判断何时以及如何测试之前,最好弄明白为什么要测试。在我看来,编写自动化测试主要有三个好处:</p>
<ol class="arabic">
<li>
<p>测试能避免“回归”(regression),即由于某些原因之前能用的功能不能用了;</p>
</li>
<li>
<p>有测试,重构(改变实现方式,但功能不变)时更有自信;</p>
</li>
<li>
<p>测试是应用代码的客户,因此可以协助我们设计,以及决定如何与系统的其他组件交互。</p>
</li>
</ol>
<p>以上三个好处都不要求先编写测试,但在很多情况下,TDD 仍有它的价值。何时以及如何测试,部分取决于你编写测试的熟练程度。很多开发者发现,熟练之后,他们更倾向于先编写测试。除此之外,还取决于测试较之应用代码有多难,你对想实现的功能有多深的认识,以及未来在什么情况下这个功能会遭到破坏。</p>
<p>现在,最好有一些指导方针,告诉我们什么时候应该先写测试(以及什么时候完全不用测试)。根据我自己的经验,给出一些建议:</p>
<ul>
<li>
<p>和应用代码相比,如果测试代码特别简短,倾向于先编写测试;</p>
</li>
<li>
<p>如果对想实现的功能不是特别清楚,倾向于先编写应用代码,然后再编写测试,改进实现的方式;</p>
</li>
<li>
<p>安全是头等大事,保险起见,要为安全相关的功能先编写测试;</p>
</li>
<li>
<p>只要发现一个问题,就编写一个测试重现这种问题,以避免回归,然后再编写应用代码修正问题;</p>
</li>
<li>
<p>尽量不为以后可能修改的代码(例如 HTML 结构的细节)编写测试;</p>
</li>
<li>
<p>重构之前要编写测试,集中测试容易出错的代码。</p>
</li>
</ul>
<p>在实际的开发中,根据上述方针,我们一般先编写控制器和模型测试,然后再编写集成测试(测试模型、视图和控制器结合在一起时的表现)。如果应用代码很容易出错,或者经常会变动(视图就是这样),我们就完全不测试。</p>
</div>
<p>我们主要编写的测试类型是控制器测试(本节开始编写),模型测试(<a href="chapter6.html#modeling-users">第 6 章</a>开始编写)和集成测试(<a href="chapter7.html#sign-up">第 7 章</a>开始编写)。集成测试的作用特别大,它能模拟用户在浏览器中和应用交互的过程,最终会成为我们的主要关注对象,不过控制器测试更容易上手。</p>
<section data-type="sect2" id="our-first-test">
<h2><span class="title-label">3.3.1.</span> 第一个测试</h2>
<p>现在我们要在这个应用中添加一个“关于”页面。我们会看到,这个测试很简短,所以按照<a href="#aside-when-to-test">旁注 3.3</a>中的指导方针,我们要先编写测试。然后使用失败的测试驱动我们编写应用代码。</p>
<p>着手测试是件具有挑战的事情,要求对 Rails 和 Ruby 都有深入的了解。这么早就编写测试可能有点儿吓人。不过,Rails 已经为我们解决了最难的部分,因为执行 <code>rails generate controller</code> 命令时(<a href="#listing-generating-pages">代码清单 3.4</a>)自动生成了一个测试文件,我们可以从这个文件入手:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>ls <span class="nb">test</span>/controllers/
static_pages_controller_test.rb
</pre></div>
</div>
<p>我们看一下这个文件的内容,如<a href="#listing-default-controller-test">代码清单 3.11</a> 所示。</p>
<div id="listing-default-controller-test" data-type="listing">
<h5><span class="title-label">代码清单 3.11:</span>为静态页面控制器生成的测试 <span class="green">GREEN</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="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="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>现在无需理解详细的句法,不过可以看出,其中有两个测试,对应我们在命令行中传入的两个动作(<a href="#listing-generating-pages">代码清单 3.4</a>)。在每个测试中,先访问动作,然后确认(通过“断言”)得到正确的响应。其中,<code>get</code> 表示测试期望这两个页面是普通的网页,可以通过 <code>GET</code> 请求访问(<a href="#aside-get-etc">旁注 3.2</a>);<code>:success</code> 响应是对 HTTP <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">响应码</a>的抽象表示(在这里表示 <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success">200 OK</a>)。也就是说,下面这个测试</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><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="k">end</span>
</pre></div>
</div>
<p>它的意思是:我们要测试首页,那么就向 <code>home</code> 动作发起一个 <code>GET</code> 请求,确认得到的是表示成功的响应码。</p>
<p>下面我们要运行测试组件,确认测试现在可以通过。方法是,按照下面的方式运行 <code>rake</code> 任务(<a href="chapter2.html#aside-rake">旁注 2.1</a>):<sup>[<a id="fn-ref-5" href="#fn-5">5</a>]</sup></p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.12:</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>
<span class="m">2</span> tests, <span class="m">2</span> assertions, <span class="m">0</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
<p>按照需求,一开始测试组件可以通过(<strong class="green">GREEN</strong>)。(如果没按照 <a href="#minitest-reporters">3.7.1 节</a>的说明添加 MiniTest 报告程序,不会看到绿色。)顺便说一下,测试要花点时间启动,因为(1)要启动 Spring 服务器预加载部分 Rails 环境,不过这一步只在首次启动时执行;(2)启动 Ruby 也要花点儿时间。(第二点可以使用 <a href="#automated-tests-with-guard">3.7.3 节</a>推荐的 Guard 改善。)</p>
</section>
<section data-type="sect2" id="red">
<h2><span class="title-label">3.3.2.</span> 遇红</h2>
<p>我们在<a href="#aside-when-to-test">旁注 3.3</a>中说过,TDD 流程是,先编写一个失败测试,然后编写应用代码让测试通过,最后再按需重构代码。因为很多测试工具都使用红色表示失败的测试,使用绿色表示通过的测试,所以这个流程有时也叫“遇红-变绿-重构”循环。这一节我们先完成这个循环的第一步,编写一个失败测试,“遇红”。然后在 <a href="#green">3.3.3 节</a>变绿,<a href="#layouts-and-embedded-ruby">3.4.3 节</a>重构。<sup>[<a id="fn-ref-6" href="#fn-6">6</a>]</sup></p>
<p>首先,我们要为“关于”页面编写一个失败测试。参照<a href="#listing-default-controller-test">代码清单 3.11</a>,你或许能猜到应该怎么写,如<a href="#listing-about-test">代码清单 3.13</a> 所示。</p>
<div id="listing-about-test" data-type="listing">
<h5><span class="title-label">代码清单 3.13:</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="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="k">end</span>
<span class="hll"> <span class="nb">test</span> <span class="s2">"should get about"</span> <span class="k">do</span>
</span><span class="hll"> <span class="n">get</span> <span class="ss">:about</span>
</span><span class="hll"> <span class="n">assert_response</span> <span class="ss">:success</span>
</span><span class="hll"> <span class="k">end</span>
</span><span class="k">end</span>
</pre></div>
</div>
<p>如高亮显示的那几行所示,为“关于”页面编写的测试与首页和“帮助”页面的测试一样,只不过把“home”或“help”换成了“about”。</p>
<p>这个测试现在失败:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.14:</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">2</span> assertions, <span class="m">0</span> failures, <span class="m">1</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
</section>
<section data-type="sect2" id="green">
<h2><span class="title-label">3.3.3.</span> 变绿</h2>
<p>现在有了一个失败测试(<strong class="red">RED</strong>),我们要在这个失败测试的错误消息指示下,让测试通过(<strong class="green">GREEN</strong>),也就是要实现一个可以访问的“关于”页面。</p>
<p>我们先看一下这个失败测试给出的错误消息:<sup>[<a id="fn-ref-7" href="#fn-7">7</a>]</sup></p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.15:</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>
ActionController::UrlGenerationError:
No route matches <span class="o">{</span>:action<span class="o">=</span>><span class="s2">"about"</span>, :controller<span class="o">=</span>><span class="s2">"static_pages"</span><span class="o">}</span>
</pre></div>
</div>
<p>这个错误消息说,没有找到需要的动作和控制器组合,其实就是提示我们要在路由文件中添加一个规则。参照<a href="#listing-pages-routes">代码清单 3.5</a>,我们可以编写如<a href="#listing-about-route">代码清单 3.16</a> 所示的路由。</p>
<div id="listing-about-route" data-type="listing">
<h5><span class="title-label">代码清单 3.16:</span>添加 <code>about</code> 路由 <span class="red">RED</span></h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
<span class="n">get</span> <span class="s1">'static_pages/home'</span>
<span class="n">get</span> <span class="s1">'static_pages/help'</span>
<span class="hll"> <span class="n">get</span> <span class="s1">'static_pages/about'</span>
</span> <span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="k">end</span>
</pre></div>
</div>
<p>这段代码中高亮显示的那行告诉 Rails,把发给 /static_pages/about 页面的 <code>GET</code> 请求交给静态页面控制器中的 <code>about</code> 动作处理。</p>
<p>然后再运行测试组件,仍然无法通过,不过错误消息变了:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.17:</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>
AbstractController::ActionNotFound:
The action <span class="s1">'about'</span> could not be found <span class="k">for</span> StaticPagesController
</pre></div>
</div>
<p>这个错误消息的意思是,静态页面控制器中缺少 <code>about</code> 动作。我们可以参照<a href="#listing-static-pages-controller">代码清单 3.6</a> 编写这个动作,如<a href="#listing-adding-the-about-page">代码清单 3.18</a> 所示。</p>
<div id="listing-adding-the-about-page" data-type="listing">
<h5><span class="title-label">代码清单 3.18:</span>在静态页面控制器中添加 <code>about</code> 动作 <span class="red">RED</span></h5>
<div class="source-file">app/controllers/static_pages_controller.rb</div>
<div class="highlight language-ruby"><pre><span class="k">class</span> <span class="nc">StaticPagesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">home</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">help</span>
<span class="k">end</span>
<span class="hll"> <span class="k">def</span> <span class="nf">about</span>
</span><span class="hll"> <span class="k">end</span>
</span><span class="k">end</span>
</pre></div>
</div>
<p>现在测试依旧失败,不过测试消息又变了:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="gp">$ </span>bundle <span class="nb">exec </span>rake <span class="nb">test
</span>ActionView::MissingTemplate: Missing template static_pages/about</code></pre>
</div>
<p>这表示没有模板。在 Rails 中,模板就是视图。<a href="#generated-static-pages">3.2.1 节</a>说过,<code>home</code> 动作对应的视图是 <code>home.html.erb</code>,保存在 <code>app/views/static_pages</code> 文件夹中。所以,我们要在这个文件夹中新建一个文件,并且要命名为 <code>about.html.erb</code>。</p>
<p>在不同的系统中新建文件有不同的方法,不过大多数情况下都可以在想要新建文件的文件夹中点击鼠标右键,然后在弹出的菜单中选择“新建文件”。或者,可以使用文本编辑器的“文件”菜单,新建文件后再选择保存的位置。除此之外,还可以使用我最喜欢的 <a href="http://en.wikipedia.org/wiki/Touch_(Unix)">Unix <code>touch</code> 命令</a>,用法如下:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>touch app/views/static_pages/about.html.erb
</pre></div>
</div>
<p><code>touch</code> 的主要作用是更新文件或文件夹的修改时间戳,别无其他效果,但有个副作用,如果文件不存在,就会新建一个。(如果使用云端 IDE,或许要刷新文件树,参见 <a href="chapter1.html#bundler">1.3.1 节</a>。)</p>
<p>在正确的文件夹中创建 <code>about.html.erb</code> 文件之后,要在其中写入<a href="#listing-custom-about-page">代码清单 3.19</a> 中的内容。</p>
<div id="listing-custom-about-page" data-type="listing">
<h5><span class="title-label">代码清单 3.19:</span>“关于”页面的内容 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/static_pages/about.html.erb</div>
<div class="highlight language-html"><pre><span class="nt"><h1></span>About<span class="nt"></h1></span>
<span class="nt"><p></span>
The <span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/"</span><span class="nt">><em></span>Ruby on Rails
Tutorial<span class="nt"></em></a></span> is a
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/book"</span><span class="nt">></span>book<span class="nt"></a></span> and
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://screencasts.railstutorial.org/"</span><span class="nt">></span>screencast series<span class="nt"></a></span>
to teach web development with
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://rubyonrails.org/"</span><span class="nt">></span>Ruby on Rails<span class="nt"></a></span>.
This is the sample application for the tutorial.
<span class="nt"></p></span>
</pre></div>
</div>
<p>现在,运行 <code>rake test</code>,会看到测试通过了:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.20:</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>
<span class="m">3</span> tests, <span class="m">3</span> assertions, <span class="m">0</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
<p>当然,我们还可以在浏览器中查看这个页面(<a href="#fig-about-us">图 3.5</a>),以防测试欺骗我们。</p>
<div id="fig-about-us" class="figure"><img src="images/chapter3/about_us_3rd_edition.png" alt="about us 3rd edition" /><div class="figcaption"><span class="title-label">图 3.5:</span>新添加的“关于”页面(<a href="http://localhost:3000/static_pages/about">/static_pages/about</a>)</div></div>
</section>
<section data-type="sect2" id="refactor">
<h2><span class="title-label">3.3.4.</span> 重构</h2>
<p>现在测试已经变绿了,我们可以自信地尽情重构了。开发应用时,代码经常会“变味”(意思是代码会变得丑陋、啰嗦,有大量的重复)。电脑不会在意,但是人类会,所以经常重构把代码变简洁一些是很重要的事情。我们的演示应用现在还很小,没什么可重构的,不过代码无时无刻不在变味,所以 <a href="#layouts-and-embedded-ruby">3.4.3</a> 节就要开始重构。</p>
</section>
</section>
<section data-type="sect1" id="slightly-dynamic-pages">
<h1><span class="title-label">3.4.</span> 有点动态内容的页面</h1>
<p>我们已经为一些静态页面创建了动作和视图,现在要稍微添加一些动态内容,根据所在的页面不同而变化:我们要让标题根据页面的内容变化。改变标题到底算不算真正动态还有争议,这么做能为<a href="chapter7.html#sign-up">第 7 章</a>实现的真正动态内容打下基础。</p>
<p>我们的计划是修改首页、“帮助”页面和“关于”页面,让每页显示的标题都不一样。为此,我们要在页面的视图中使用 <code><title></code> 标签。大多数浏览器都会在浏览器窗口的顶部显示标题中的内容,而且标题对“搜索引擎优化”(Search-Engine Optimization,简称 SEO)也有好处。我们要使用完整的“遇红-变绿-重构”循环:先为页面的标题编写一些简单的测试(<strong class="red">遇红</strong>),然后分别在三个页面中添加标题(<strong class="green">变绿</strong>),最后使用布局文件去除重复内容(重构)。本节结束时,三个静态页面的标题都会变成“<页面的名字> | Ruby on Rails Tutorial Sample App”这种形式(<a href="#table-static-pages">表 3.2</a>)。</p>
<p><code>rails new</code> 命令会创建一个布局文件,不过现在最好不用。我们重命名这个文件:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>mv app/views/layouts/application.html.erb layout_file <span class="c"># 临时移动</span>
</pre></div>
</div>
<p>在真实的应用中你不需要这么做,不过没有这个文件能让你更好地理解它的作用。</p>
<table id="table-static-pages" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 3.2:</span>演示应用中基本上是静态内容的页面</caption>
<colgroup>
<col style="width: 10%;" />
<col style="width: 25%;" />
<col style="width: 45%;" />
<col style="width: 20%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">页面</th>
<th class="tableblock halign-left valign-top">URL</th>
<th class="tableblock halign-left valign-top">基本标题</th>
<th class="tableblock halign-left valign-top">变动部分</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">首页</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/static_pages/home</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"Ruby on Rails Tutorial Sample App"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"Home"</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">帮助</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/static_pages/help</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"Ruby on Rails Tutorial Sample App"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"Help"</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">关于</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/static_pages/about</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"Ruby on Rails Tutorial Sample App"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"About"</code></p></td>
</tr>
</tbody>
</table>
<section data-type="sect2" id="testing-titles">
<h2><span class="title-label">3.4.1.</span> 测试标题(遇红)</h2>
<p>添加标题之前,我们要学习网页的一般结构,如<a href="#listing-html-structure">代码清单 3.21</a> 所示。</p>
<div id="listing-html-structure" data-type="listing">
<h5><span class="title-label">代码清单 3.21:</span>网页一般的 HTML 结构</h5>
<div class="highlight language-html"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Greeting<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><p></span>Hello, world!<span class="nt"></p></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<p>这段代码的最顶部是“文档类型声明”(document type declaration,简称 doctype),告诉浏览器使用哪个 HTML 版本(本例使用 <a href="http://en.wikipedia.org/wiki/HTML5">HTML5</a>)<sup>[<a id="fn-ref-8" href="#fn-8">8</a>]</sup>。随后是 <code>head</code> 部分,包含一个 <code>title</code> 标签,其中的内容是“Greeting”。然后是 <code>body</code> 部分,包含一个 <code>p</code> 标签(段落),其中的内容是“Hello, world!”。(内容的缩进是可选的,HTML 不会特别对待空白,制表符和空格都会被忽略,但缩进可以让文档结构更清晰。)</p>
<p>我们要使用 <code>assert_select</code> 方法分别为<a href="#table-static-pages">表 3.2</a> 中的每个标题编写简单的测试,合并到<a href="#listing-about-test">代码清单 3.13</a> 的测试中。<code>assert_select</code> 方法的作用是检查有没有指定的 HTML 标签。这种方法有时也叫“选择符”,从方法名可以看出这一点。<sup>[<a id="fn-ref-9" href="#fn-9">9</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="n">assert_select</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"Home | Ruby on Rails Tutorial Sample App"</span>
</pre></div>
</div>
<p>这行代码的作用是检查有没有 <code><title></code> 标签,以及其中的内容是不是字符串“Home | Ruby on Rails Tutorial Sample App”。把这样的代码分别放到三个页面的测试中,得到的结果如<a href="#listing-title-tests">代码清单 3.22</a> 所示。</p>
<div id="listing-title-tests" data-type="listing">
<h5><span class="title-label">代码清单 3.22:</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">"Home | 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="hll"> <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> <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="hll"> <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> <span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>(如果你觉得在标题中重复使用“Ruby on Rails Tutorial Sample App”不妥,可以看一下 <a href="#mostly-static-pages-exercises">3.6 节</a>的练习。)</p>
<p>写好测试之后,应该确认一下现在测试组件是失败的(<strong class="red">RED</strong>):</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.23:</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">3</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
</section>
<section data-type="sect2" id="adding-page-titles">
<h2><span class="title-label">3.4.2.</span> 添加页面标题(变绿)</h2>
<p>现在,我们要为每个页面添加标题,让前一节的测试通过。参照<a href="#listing-html-structure">代码清单 3.21</a> 中的 HTML 结构,把<a href="#listing-custom-home-page">代码清单 3.9</a> 中的首页内容换成<a href="#listing-home-view-full-html">代码清单 3.24</a> 中的内容。</p>
<div id="listing-home-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.24:</span>具有完整 HTML 结构的首页 <span class="red">RED</span></h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-html"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Home | Ruby on Rails Tutorial Sample App<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></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://www.railstutorial.org/"</span><span class="nt">></span>Ruby on Rails Tutorial<span class="nt"></a></span>
sample application.
<span class="nt"></p></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<p>修改之后,首页如<a href="#fig-home-view-full-html">图 3.6</a> 所示。<sup>[<a id="fn-ref-10" href="#fn-10">10</a>]</sup></p>
<div id="fig-home-view-full-html" class="figure"><img src="images/chapter3/home_view_full_html.png" alt="home view full html" /><div class="figcaption"><span class="title-label">图 3.6:</span>添加标题后的首页</div></div>
<p>然后使用类似的方式修改“帮助”页面和“关于”页面,得到的代码如<a href="#listing-help-view-full-html">代码清单 3.25</a> 和<a href="#listing-about-view-full-html">代码清单 3.26</a> 所示。</p>
<div id="listing-help-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.25:</span>具有完整 HTML 结构的“帮助”页面 <span class="red">RED</span></h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-html"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Help | Ruby on Rails Tutorial Sample App<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>Help<span class="nt"></h1></span>
<span class="nt"><p></span>
Get help on the Ruby on Rails Tutorial at the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/#help"</span><span class="nt">></span>Rails Tutorial help
section<span class="nt"></a></span>.
To get help on this sample app, see the
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/book"</span><span class="nt">><em></span>Ruby on Rails
Tutorial<span class="nt"></em></span> book<span class="nt"></a></span>.
<span class="nt"></p></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<div id="listing-about-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.26:</span>具有完整 HTML 结构的“关于”页面 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/static_pages/about.html.erb</div>
<div class="highlight language-html"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>About | Ruby on Rails Tutorial Sample App<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>About<span class="nt"></h1></span>
<span class="nt"><p></span>
The <span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/"</span><span class="nt">><em></span>Ruby on Rails
Tutorial<span class="nt"></em></a></span> is a
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.railstutorial.org/book"</span><span class="nt">></span>book<span class="nt"></a></span> and
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://screencasts.railstutorial.org/"</span><span class="nt">></span>screencast series<span class="nt"></a></span>
to teach web development with
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://rubyonrails.org/"</span><span class="nt">></span>Ruby on Rails<span class="nt"></a></span>.
This is the sample application for the tutorial.
<span class="nt"></p></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
</div>
<p>现在,测试组件能通过了(<strong class="green">GREEN</strong>):</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.27:</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>
<span class="m">3</span> tests, <span class="m">6</span> assertions, <span class="m">0</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
</section>
<section data-type="sect2" id="layouts-and-embedded-ruby">
<h2><span class="title-label">3.4.3.</span> 布局和嵌入式 Ruby(重构)</h2>
<p>到目前为止,本节已经做了很多事情,我们使用 Rails 控制器和动作生成了三个可用的页面,不过这些页面中的内容都是纯静态的 HTML,没有体现出 Rails 的强大之处。而且,代码中有着大量重复:</p>
<ul>
<li>
<p>页面的标题几乎(但不完全)是一模一样的;</p>
</li>
<li>
<p>每个标题中都有“Ruby on Rails Tutorial Sample App”;</p>
</li>
<li>
<p>整个 HTML 结构在每个页面都重复地出现了。</p>
</li>
</ul>
<p>重复的代码违反了很重要的“不要自我重复”(Don’t Repeat Yourself,简称 DRY)原则。本节要遵照 DRY 原则,去掉重复的代码。最后,我们要运行前一节编写的测试,确认显示的标题仍然正确。</p>
<p>不过,去除重复的第一步却是要增加一些代码,让页面的标题看起来是一样的。这样我们就能更容易地去掉重复的代码了。</p>
<p>在这个过程中,要在视图中使用嵌入式 Ruby(Embedded Ruby)。既然首页、“帮助”页面和“关于”页面的标题中有一个变动的部分,那我们就使用 Rails 提供的一个特别的函数 <code>provide</code>,在每个页面中设定不同的标题。通过把 <code>home.html.erb</code> 视图中标题的“Home”换成<a href="#listing-home-view-erb-title">代码清单 3.28</a> 所示的代码,我们可以看一下这个函数的作用。</p>
<div id="listing-home-view-erb-title" data-type="listing">
<h5><span class="title-label">代码清单 3.28:</span>标题中使用了嵌入式 Ruby 代码的首页视图 <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="hll"><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><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="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><span class="x"> </head></span>
<span class="x"> <body></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>
<span class="x"> </body></span>
<span class="x"></html></span>
</pre></div>
</div>
<p>在这段代码中我们第一次使用了嵌入式 Ruby,或者简称 ERb。(现在你应该知道为什么 HTML 视图文件的扩展名是 <code>.html.erb</code> 了。)ERb 是为网页添加动态内容主要使用的模板系统。<sup>[<a id="fn-ref-11" href="#fn-11">11</a>]</sup>下面的代码</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="s1">'Home'</span><span class="p">)</span> <span class="cp">%></span><span class="x"></span>
</pre></div>
</div>
<p>通过 <code><% … %></code> 调用 Rails 中的 <code>provide</code> 函数,把字符串 <code>"Home"</code> 赋给 <code>:title</code>。<sup>[<a id="fn-ref-12" href="#fn-12">12</a>]</sup>然后,在标题中,我们使用类似的符号 <code><%= … %></code>,通过 Ruby 的 <code>yield</code> 函数把标题插入模板中:<sup>[<a id="fn-ref-13" href="#fn-13">13</a>]</sup></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>(这两种嵌入 Ruby 代码的方式区别在于,<code><% … %></code> 只<strong>执行</strong>其中的代码;<code><%= … %></code> 也会执行其中的代码,而且会把执行的结果<strong>插入</strong>模板中。)最终得到的页面和以前一样,不过,现在标题中变动的部分通过 ERb 动态生成。</p>
<p>我们可以运行前一节编写的测试确认一下——测试还能通过(<strong class="green">GREEN</strong>):</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.29:</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>
<span class="m">3</span> tests, <span class="m">6</span> assertions, <span class="m">0</span> failures, <span class="m">0</span> errors, <span class="m">0</span> skips
</pre></div>
</div>
<p>然后,按照相同的方式修改“帮助”(<a href="#listing-help-view-erb-title">代码清单 3.30</a>)和“关于”页面(<a href="#listing-about-view-erb-title">代码清单 3.31</a>)。</p>
<div id="listing-help-view-erb-title" data-type="listing">
<h5><span class="title-label">代码清单 3.30:</span>标题中使用了嵌入式 Ruby 代码的“帮助”页面视图 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-erb"><pre><span class="hll"><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">"Help"</span><span class="p">)</span> <span class="cp">%></span><span class="x"></span>
</span><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="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><span class="x"> </head></span>
<span class="x"> <body></span>
<span class="x"> <h1>Help</h1></span>
<span class="x"> <p></span>
<span class="x"> Get help on the Ruby on Rails Tutorial at the</span>
<span class="x"> <a href="http://www.railstutorial.org/#help">Rails Tutorial help</span>
<span class="x"> section</a>.</span>
<span class="x"> To get help on this sample app, see the</span>
<span class="x"> <a href="http://www.railstutorial.org/book"><em>Ruby on Rails</span>
<span class="x"> Tutorial</em> book</a>.</span>
<span class="x"> </p></span>
<span class="x"> </body></span>
<span class="x"></html></span>
</pre></div>
</div>
<div id="listing-about-view-erb-title" data-type="listing">
<h5><span class="title-label">代码清单 3.31:</span>标题中使用了嵌入式 Ruby 代码的“关于”页面视图 <span class="green">GREEN</span></h5>
<div class="source-file">app/views/static_pages/about.html.erb</div>
<div class="highlight language-erb"><pre><span class="hll"><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">"About"</span><span class="p">)</span> <span class="cp">%></span><span class="x"></span>
</span><span class="x"><!DOCTYPE html></span>
<span class="x"><html></span>