-
Notifications
You must be signed in to change notification settings - Fork 3
/
chapter3.html
1728 lines (1460 loc) · 139 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.5.1"/>
<link rel="stylesheet" type="text/css" href="//railstutorial-china.org/assets/css/main.css"/>
<link rel="stylesheet" type="text/css" href="book.css"/>
<script type="text/javascript" src="//railstutorial-china.org/assets/js/global.js"></script>
</head>
<body class="book-page">
<nav class="navbar">
<div class="container">
<div class="clearfix">
<a class="navbar-brand hidden-sm-up" href="//railstutorial-china.org/" title="Ruby on Rails 教程">Ruby on Rails 教程</a>
<button class="navbar-toggler hidden-sm-up pull-xs-right" type="button" data-toggle="collapse" data-target="#main-nav">☰</button>
</div>
<a class="navbar-brand hidden-xs-down" href="//railstutorial-china.org/" title="Ruby on Rails 教程">Ruby on Rails 教程</a>
<div class="collapse navbar-toggleable-xs pull-sm-right" id="main-nav">
<ul class="nav navbar-nav">
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/" title="首页">首页</a></li>
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/blog/" title="博客">博客</a></li>
<li class="nav-item active"><a class="nav-link" href="//railstutorial-china.org/book/" title="阅读">阅读</a></li>
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/#ebook" title="电子书">电子书</a></li>
</ul>
</div>
</div>
</nav>
<div class="content">
<div class="container">
<div class="row">
<div class="col-lg-offset-2 col-lg-8">
<div class="book-versions">
选择版本:
<a class="btn btn-primary" href="//railstutorial-china.org/book/" title="Ruby on Rails 教程(原书第 4 版,针对 Rails 5)">Rails 5</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails42/" title="Ruby on Rails 教程(原书第 3 版,针对 Rails 4.2)">Rails 4.2</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails4/" title="Ruby on Rails 教程(原书第 3 版,针对 Rails 4.0)">Rails 4.0</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails3/" title="Ruby on Rails 教程(原书第 2 版,针对 Rails 3.2)">Rails 3.2</a>
</div>
<div class="alert alert-warning">
<p>在线版的内容可能落后于电子书,如果想及时获得更新,请<a href="//railstutorial-china.org/#ebook" title="购买电子书">购买电子书</a>。</p>
</div>
<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 class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>一样,我们将先创建一个新 Rails 项目,名为 <code>sample_app</code>,如<a class="xref-link" 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><code><span class="nv">$ </span><span class="nb">cd</span> ~/environment
<span class="nv">$ </span>rails _5.1.6_ new sample_app
<span class="nv">$ </span><span class="nb">cd </span>sample_app/
</code></pre></div>
</div>
<p>(与 <a class="xref-link" href="chapter2.html#planning-the-application">2.1 节</a>一样,如果使用云端 IDE,可以在同一个工作空间中创建这个应用,没必要再新建一个工作空间。)</p>
<p>注意:为了便于参考,本书实现的完整演示应用可以在 <a href="https://bitbucket.org/railstutorial/sample_app_4th_ed" class="external-link">Bitbucket</a> 中查看。</p>
<p>类似 <a class="xref-link" href="chapter2.html#planning-the-application">2.1 节</a>,接下来我们要用文本编辑器打开并编辑 <code>Gemfile</code> 文件,写入应用所需的 gem。<a class="xref-link" href="#listing-gemfile-sample-app">代码清单 3.2</a> 与<a class="xref-link" href="chapter1.html#listing-gemfile-sqlite-version">代码清单 1.5</a> 和<a class="xref-link" href="chapter2.html#listing-demo-gemfile-sqlite-version-redux">代码清单 2.1</a> 一样,不过 <code>test</code> 组中的 gem 有所不同,这些是高级测试设置(<a class="xref-link" href="#advanced-testing-setup">3.6 节</a>)和集成测试(<a class="xref-link" href="chapter5.html#layout-link-tests">5.3.4 节</a>)所需的。注意,如果现在你想安装这个应用使用的所有 gem,要写入<a class="xref-link" href="chapter13.html#listing-final-gemfile">代码清单 13.72</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><code><span class="nb">source</span> <span class="s1">'https://rubygems.org'</span>
gem <span class="s1">'rails'</span>, <span class="s1">'5.1.6'</span>
gem <span class="s1">'puma'</span>, <span class="s1">'3.9.1'</span>
gem <span class="s1">'sass-rails'</span>, <span class="s1">'5.0.6'</span>
gem <span class="s1">'uglifier'</span>, <span class="s1">'3.2.0'</span>
gem <span class="s1">'coffee-rails'</span>, <span class="s1">'4.2.2'</span>
gem <span class="s1">'jquery-rails'</span>, <span class="s1">'4.3.1'</span>
gem <span class="s1">'turbolinks'</span>, <span class="s1">'5.0.1'</span>
gem <span class="s1">'jbuilder'</span>, <span class="s1">'2.7.0'</span>
group :development, :test <span class="k">do</span>
<span class="k"> </span>gem <span class="s1">'sqlite3'</span>, <span class="s1">'1.3.13'</span>
gem <span class="s1">'byebug'</span>, <span class="s1">'9.0.6'</span>, platform: :mri
end
group :development <span class="k">do</span>
<span class="k"> </span>gem <span class="s1">'web-console'</span>, <span class="s1">'3.5.1'</span>
gem <span class="s1">'listen'</span>, <span class="s1">'3.0.8'</span>
gem <span class="s1">'spring'</span>, <span class="s1">'2.0.2'</span>
gem <span class="s1">'spring-watcher-listen'</span>, <span class="s1">'2.0.1'</span>
end
group :test <span class="k">do</span>
<span class="k"> </span>gem <span class="s1">'rails-controller-testing'</span>, <span class="s1">'1.0.2'</span>
gem <span class="s1">'minitest-reporters'</span>, <span class="s1">'1.1.14'</span>
gem <span class="s1">'guard'</span>, <span class="s1">'2.13.0'</span>
gem <span class="s1">'guard-minitest'</span>, <span class="s1">'2.4.4'</span>
end
group :production <span class="k">do</span>
<span class="k"> </span>gem <span class="s1">'pg'</span>, <span class="s1">'0.20.0'</span>
end
<span class="c"># Windows does not include zoneinfo files, so bundle the tzinfo-data gem</span>
gem <span class="s1">'tzinfo-data'</span>, platforms: <span class="o">[</span>:mingw, :mswin, :x64_mingw, :jruby]
</code></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><code><span class="nv">$ </span>bundle <span class="nb">install</span> <span class="nt">--without</span> production
</code></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><code><span class="nv">$ </span>bundle update
</code></pre></div>
</div>
<p>最后,我们要初始化 Git 仓库:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git init
<span class="nv">$ </span>git add <span class="nt">-A</span>
<span class="nv">$ </span>git commit <span class="nt">-m</span> <span class="s2">"Initialize repository"</span>
</code></pre></div>
</div>
<p>与第一个应用一样,我建议你更新一下 <code>README</code> 文件,更好地描述这个应用。我们先把这个文件中的内容删掉,然后换成<a class="xref-link" href="#listing-sample-app-readme">代码清单 3.3</a> 中的 Markdown 内容。注意,README 文件中说明了如何安装这个应用。(直到<a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>才会执行 <code>rails db:migrate</code> 命令,不过现在写上也无妨。)</p>
<div id="listing-sample-app-readme" data-type="listing">
<h5><span class="title-label">代码清单 3.3</span>:修改这个演示应用的 <code>README</code> 文件</h5>
<div class="source-file">README.md</div>
<div class="highlight language-text"><pre><code># Ruby on Rails Tutorial sample application
This is the sample application for
[*Ruby on Rails Tutorial:
Learn Web Development with Rails*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/).
## License
All source code in the [Ruby on Rails Tutorial](http://railstutorial.org/)
is available jointly under the MIT License and the Beerware License. See
[LICENSE.md](LICENSE.md) for details.
## Getting started
To get started with the app, clone the repo and then install the needed gems:
```
$ bundle install --without production
```
Next, migrate the database:
```
$ rails db:migrate
```
Finally, run the test suite to verify that everything is working correctly:
```
$ rails test
```
If the test suite passes, you'll be ready to run the app in a local server:
```
$ rails server
```
For more information, see the
[*Ruby on Rails Tutorial* book](http://www.railstutorial.org/book).
</code></pre></div>
</div>
<p>然后,提交改动:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="nv">$ </span>git commit <span class="nt">-am</span> <span class="s2">"Improve the README"</span></code></pre>
</div>
<p>你可能还记得,在 <a class="xref-link" href="chapter1.html#git-commands">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" class="external-link">在 Bitbucket 中新建一个仓库</a>,把这个应用推送上去:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git remote add origin [email protected]:<username>/sample_app.git
<span class="nv">$ </span>git push <span class="nt">-u</span> origin <span class="nt">--all</span> <span class="c"># 首次推送这个应用</span>
</code></pre></div>
</div>
<p>为了避免以后遇到焦头烂额的问题,在这个早期阶段也可以把应用部署到 Heroku 中。参照<a class="xref-link" href="chapter1.html#beginning">第 1 章</a>和<a class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>,我建议像<a class="xref-link" href="#listing-hello-action-redux-2">代码清单 3.4</a> 和<a class="xref-link" href="#listing-hello-root-route-redux-2">代码清单 3.5</a> 那样做,创建一个显示“hello, world!”的首页。(之所以这样做是因为 Rails 的默认页面往往无法在 Heroku 中显示,很难判断部署成功还是失败。)</p>
<div id="listing-hello-action-redux-2" data-type="listing">
<h5><span class="title-label">代码清单 3.4</span>:在 <code>Application</code> 控制器中添加 <code>hello</code> 动作</h5>
<div class="source-file">app/controllers/application_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o"><</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">Base</span>
<span class="n">protect_from_forgery</span> <span class="ss">with: :exception</span>
<span class="k">def</span> <span class="nf">hello</span>
<span class="hll"> <span class="n">render</span> <span class="ss">html: </span><span class="s2">"hello, world!"</span></span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
</div>
<div id="listing-hello-root-route-redux-2" data-type="listing">
<h5><span class="title-label">代码清单 3.5</span>:设置根路由</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">root</span> <span class="s1">'application#hello'</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>然后提交改动,推送到 Bitbucket 和 Heroku 中:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git commit <span class="nt">-am</span> <span class="s2">"Add hello"</span>
<span class="nv">$ </span>git push
<span class="nv">$ </span>heroku create
<span class="nv">$ </span>git push heroku master
</code></pre></div>
</div>
<p>与 <a class="xref-link" href="chapter1.html#deploying">1.5 节</a>一样,你可能会看到一些警告消息,现在暂且不管,<a class="xref-link" href="chapter7.html#professional-grade-deployment">7.5 节</a>会解决。除了 Heroku 为应用分配的地址之外,看到的页面应该和<a class="xref-link" href="chapter1.html#fig-heroku-app">图 1.24</a> 一样。</p>
<p>在阅读本书的过程中,我建议你定期推送和部署,这样不仅能在远程仓库中备份,还能尽早发现在生产环境中可能出现的问题。如果遇到与 Heroku 有关的问题,可以查看生产环境中的日志,试着找出问题所在:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>heroku logs
</code></pre></div>
</div>
<p>注意,如果你决定把真实的应用部署到 Heroku 中,一定要按照 <a class="xref-link" href="chapter7.html#professional-grade-deployment">7.5 节</a>介绍的方法配置生产环境的 Web 服务器。</p>
<h4 id="exercises-sample-app-setup" class="discrete">练习</h4>
<ol class="arabic">
<li>
<p>确认 Bitbucket 把<a class="xref-link" href="#listing-sample-app-readme">代码清单 3.3</a> 中的 Markdown 渲染成了 HTML。</p>
</li>
<li>
<p>访问生产环境中应用的根路由,确认成功部署到 Heroku 中了。</p>
</li>
</ol>
</section>
<section data-type="sect1" id="static-pages">
<h1><span class="title-label">3.2</span> 静态页面</h1>
<p>前一节的准备工作做好之后,我们可以开始开发这个演示应用了。本节,我们要向开发动态页面迈出第一步:创建一些 Rails 动作和视图,但只包含静态 HTML。<sup>[<a id="fn-ref-4" href="#fn-4">4</a>]</sup>Rails 动作放在控制器中(MVC 中的 C,参见 <a class="xref-link" href="chapter1.html#mvc">1.3.3 节</a>),用于组织相关的功能。<a class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了控制器,全面熟悉 <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer" class="external-link">REST 架构</a>之后(从<a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>开始),你会更深入地理解控制器。回想一下 <a class="xref-link" href="chapter1.html#the-hello-application">1.3 节</a>介绍的 Rails 项目目录结构(<a class="xref-link" href="chapter1.html#fig-directory-structure-rails">图 1.7</a>),会对我们有所帮助。这一节主要在 <code>app/controllers</code> 和 <code>app/views</code> 两个目录中工作。</p>
<p><a class="xref-link" href="chapter1.html#git-commands">1.4.4 节</a>说过,使用 Git 时最好在单独的主题分支中完成工作,不要直接使用主分支。如果你使用 Git 做版本控制,现在应该执行下述命令,切换到一个主题分支,然后再创建静态页面:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git checkout <span class="nt">-b</span> static-pages
</code></pre></div>
</div>
<section data-type="sect2" id="generated-static-pages">
<h2><span class="title-label">3.2.1</span> 生成静态页面</h2>
<p>下面我们要使用<a class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>用来生成脚手架的 <code>generate</code> 命令生成一个控制器。既然这个控制器用来处理静态页面,那就把它命名为 <code>StaticPages</code> 吧。可以看出,控制器的名字使用<a href="https://en.wikipedia.org/wiki/CamelCase" class="external-link">驼峰式命名法</a>。我们计划创建“首页”、“帮助”页面和“关于”页面,对应的动作名分别为 <code>home</code>、<code>help</code> 和 <code>about</code>。<code>generate</code> 命令可以接收一个可选的参数列表,指定要创建的动作。我们将在命令行中指定 <code>home</code> 和 <code>help</code> 动作,故意不指定 <code>about</code> 动作,<a class="xref-link" href="#getting-started-with-testing">3.3 节</a>再介绍怎么添加。生成 <code>StaticPages</code> 控制器的命令如<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a> 所示。</p>
<div id="listing-generating-pages" data-type="listing">
<h5><span class="title-label">代码清单 3.6</span>:生成 <code>StaticPages</code> 控制器</h5>
<div class="highlight language-sh"><pre><code><span class="hll"><span class="nv">$ </span>rails generate controller StaticPages home <span class="nb">help</span></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
invoke assets
invoke coffee
create app/assets/javascripts/static_pages.coffee
invoke scss
create app/assets/stylesheets/static_pages.scss
</code></pre></div>
</div>
<p>顺便说一下,<code>rails generate</code> 可以简写成 <code>rails g</code>。除此之外,Rails 还提供了几个命令的简写形式,参见<a class="xref-link" href="#table-shortcuts">表 3.1</a>。为了明确表述,本书会一直使用命令的完整形式,但在实际使用中,大多数 Rails 开发者或多或少都会使用<a class="xref-link" href="#table-shortcuts">表 3.1</a> 中的简写形式。<sup>[<a id="fn-ref-5" href="#fn-5">5</a>]</sup></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>$ rails test</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rails t</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>
</tbody>
</table>
<p>在继续之前,如果你使用 Git,最好把 <code>StaticPages</code> 控制器对应的文件推送到远程仓库:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git add <span class="nt">-A</span>
<span class="nv">$ </span>git commit <span class="nt">-m</span> <span class="s2">"Add a Static Pages controller"</span>
<span class="nv">$ </span>git push <span class="nt">-u</span> origin static-pages
</code></pre></div>
</div>
<p>最后一个命令的意思是,把 <code>static-pages</code> 主题分支推送到 Bitbucket。以后再推送时,可以省略后面的参数,简写成:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="nv">$ </span>git push</code></pre>
</div>
<p>在现实的开发过程中,我一般都会先提交再推送,但是为了行文简洁,从这往后我们会省略提交这一步。</p>
<p>注意,在<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a> 中,我们传入的控制器名使用驼峰式(因为像<a href="https://zh.wikipedia.org/wiki/%E5%8F%8C%E5%B3%B0%E9%AA%86%E9%A9%BC" class="external-link">骆驼的双峰</a>一样),创建的控制器文件名则是<a href="https://en.wikipedia.org/wiki/Snake_case" class="external-link">蛇底式</a>。所以,传入“StaticPages”得到的文件是 <code>static_pages_controller.rb</code>。这只是一种约定。其实在命令行中也可以使用蛇底式:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails generate controller static_pages ...
</code></pre></div>
</div>
<p>这个命令也会生成名为 <code>static_pages_controller.rb</code> 的控制器文件。因为 Ruby 的类名使用驼峰式(<a class="xref-link" href="chapter4.html#ruby-classes">4.4 节</a>),所以提到控制器时我会使用驼峰式,不过这是我的个人选择。(因为 Ruby 文件名一般使用蛇底式,所以 Rails 生成器使用 <a href="http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore" class="external-link"><code>underscore</code></a> 方法把驼峰式转换成蛇底式。)</p>
<p>顺便说一下,如果在生成代码时出错了,知道如何撤销操作就很有用了。<a class="xref-link" href="#aside-undoing-things">旁注 3.1</a> 中介绍了一些在 Rails 中撤销操作的方法。</p>
<div data-type="sidebar" id="aside-undoing-things" class="sidebar">
<h5><span class="title-label">旁注 3.1</span>:撤销操作</h5>
<p>即使再小心,在开发 Rails 应用的过程中也可能会犯错。幸好 Rails 提供了一些工具能够帮助我们还原操作。</p>
<p>举例来说,一个常见的情况是更改控制器的名字,这时你得删除生成的文件。生成控制器时,除了控制器文件本身之外,Rails 还会生成很多其他文件(参见<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a>)。撤销生成的文件不仅仅要删除控制器文件,还要删除不少辅助文件。(在 <a class="xref-link" href="chapter2.html#demo-users-resource">2.2 节</a>和 <a class="xref-link" href="chapter2.html#demo-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><code><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>
</code></pre></div>
</div>
<p><a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>会使用下面的命令生成模型:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails generate model User name:string email:string
</code></pre></div>
</div>
<p>这个操作可以使用下面的命令撤销:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails destroy model User
</code></pre></div>
</div>
<p>(这里,我们可以省略命令行中其余的参数。读到<a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>时,看看你能否发现为什么可以这样做。)</p>
<p>对模型来说,还涉及到撤销迁移。<a class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了迁移,<a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>开始会深入说明。迁移通过下面的命令改变数据库的状态:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails db:migrate
</code></pre></div>
</div>
<p>我们可以使用下面的命令撤销前一个迁移操作:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails db:rollback
</code></pre></div>
</div>
<p>如果要回到最开始的状态,可以使用:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails db:migrate <span class="nv">VERSION</span><span class="o">=</span>0
</code></pre></div>
</div>
<p>你可能猜到了,把数字 0 换成其他数字就会回到相应的版本,这些版本数字是按照迁移执行的顺序排列的。</p>
<p>知道这些技术,我们就能得心应对开发过程中遇到的各种问题了。</p>
</div>
<p><a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a> 中生成 <code>StaticPages</code> 控制器的命令会自动修改路由文件(<code>config/routes.rb</code>)。我们在 <a class="xref-link" href="chapter1.html#hello-world">1.3.4 节</a>已经简略介绍过这个文件,它的作用是实现 URL 和网页之间的对应关系(<a class="xref-link" href="chapter2.html#fig-mvc-detailed">图 2.11</a>)。路由文件在 <code>config</code> 目录中。Rails 在这个目录中存放应用的配置文件(<a class="xref-link" href="#fig-config-directory-rails">图 3.1</a>)。</p>
<p>因为生成控制器时我们指定了 <code>home</code> 和 <code>help</code> 动作,所以路由文件中已经添加了相应的规则,如<a class="xref-link" href="#listing-pages-routes">代码清单 3.7</a> 所示。</p>
<div id="listing-pages-routes" data-type="listing">
<h5><span class="title-label">代码清单 3.7</span>:<code>StaticPages</code> 控制器中 <code>home</code> 和 <code>help</code> 动作的路由</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-sh"><pre><code>Rails.application.routes.draw <span class="k">do</span>
<span class="hll"><span class="k"> </span>get <span class="s1">'static_pages/home'</span></span>
<span class="hll"> get <span class="s1">'static_pages/help'</span></span>
root <span class="s1">'application#hello'</span>
end
</code></pre></div>
</div>
<div id="fig-config-directory-rails" class="figure"><img src="images/chapter3/config_directory_4th_edition.png" alt="config directory 4th edition" /><div class="figcaption"><span class="title-label">图 3.1</span>:演示应用 <code>config</code> 目录中的内容</div></div>
<p>如下的规则</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><span class="n">get</span> <span class="s1">'static_pages/home'</span>
</code></pre></div>
</div>
<p>把发给 /static_pages/home 的请求映射到 <code>StaticPages</code> 控制器的 <code>home</code> 动作上。另外,<code>get</code> 表明这个路由响应的是 GET 请求。GET 是 HTTP(Hypertext Transfer Protocol,超文本传输协议)支持的基本请求方法之一(<a class="xref-link" href="#aside-get-etc">旁注 3.2</a>)。这里,当我们在 <code>StaticPages</code> 控制器中生成 <code>home</code> 动作时,就自动在 /static_pages/home 地址上获得了一个页面。若想查看这个页面,按照 <a class="xref-link" href="chapter1.html#rails-server">1.3.2 节</a>中的方法,启动 Rails 开发服务器:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails server
</code></pre></div>
</div>
<p>然后访问 /static_pages/home,如<a class="xref-link" 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>:简陋的首页(/static_pages/home)</div></div>
<div data-type="sidebar" id="aside-get-etc" class="sidebar">
<h5><span class="title-label">旁注 3.2</span>:<code>GET</code> 等</h5>
<p>超文本传输协议(<a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods" class="external-link">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 class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>已经简要介绍了 REST,从<a class="xref-link" href="chapter7.html#sign-up">第 7 章</a>开始会更详细地说明。</p>
<p><code>GET</code> 是最常用的 HTTP 操作,用于读取网络中的数据。它的意思是“读取一个网页”,当你访问 <a href="http://www.google.com" class="external-link bare">http://www.google.com</a> 或 <a href="http://www.wikipedia.org" class="external-link 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>要想弄明白这个页面是怎么来的,我们先在文本编辑器中看一下 <code>StaticPages</code> 控制器文件。你应该会看到类似<a class="xref-link" href="#listing-static-pages-controller">代码清单 3.8</a> 所示的内容。你可能注意到了,与<a class="xref-link" href="chapter2.html#a-toy-app">第 2 章</a>中的 <code>Users</code> 和 <code>Microposts</code> 控制器不同,<code>StaticPages</code> 控制器没使用标准的 REST 动作。这对静态页面来说是很常见的,毕竟 REST 架构不能解决所有问题。</p>
<div id="listing-static-pages-controller" data-type="listing">
<h5><span class="title-label">代码清单 3.8</span>:<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a> 生成的 <code>StaticPages</code> 控制器</h5>
<div class="source-file">app/controllers/static_pages_controller.rb</div>
<div class="highlight language-ruby"><pre><code><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>
</code></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 class="xref-link" href="chapter2.html#inheritance-hierarchies">2.3.4 节</a>说过,尖括号 <code><</code> 表示 <code>StaticPagesController</code> 继承自 <code>ApplicationController</code> 类;稍后你会看到,这意味着我们定义的页面拥有了 Rails 提供的大量功能。(我们会在 <a class="xref-link" href="chapter4.html#ruby-classes">4.4 节</a>更详细地介绍类和继承。)</p>
<p>现在,<code>StaticPages</code> 控制器中的两个方法都是空的:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><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>
</code></pre></div>
</div>
<p>如果是普通的 Ruby 代码,这两个方法什么也做不了。不过在 Rails 中就不一样了。<code>StaticPagesController</code> 是一个 Ruby 类,但是因为它继承自 <code>ApplicationController</code>,其中的方法对 Rails 来说就有了特殊意义:访问 /static_pages/home 时,Rails 会在 <code>StaticPages</code> 控制器中寻找 <code>home</code> 动作,然后执行该动作,再渲染相应的视图(MVC 中的 V,参见 <a class="xref-link" href="chapter1.html#mvc">1.3.3 节</a>)。这里,<code>home</code> 动作是空的,所以访问 /static_pages/home 后只会渲染视图。那么,视图是什么样子,怎么才能找到它呢?</p>
<p>如果你再看一下<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a> 的输出,或许能猜到动作和视图之间的对应关系:<code>home</code> 动作对应的视图是 <code>home.html.erb</code>。<a class="xref-link" href="#slightly-dynamic-pages">3.4 节</a>会告诉你 <code>.erb</code> 是什么意思。看到 <code>.html</code> 你或许就不奇怪了,这个文件基本上就是 HTML,如<a class="xref-link" href="#listing-raw-home-view">代码清单 3.9</a> 所示。</p>
<div id="listing-raw-home-view" data-type="listing">
<h5><span class="title-label">代码清单 3.9</span>:为“首页”生成的视图</h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-erb"><pre><code><span class="nt"><h1></span>StaticPages#home<span class="nt"></h1></span>
<span class="nt"><p></span>Find me in app/views/static_pages/home.html.erb<span class="nt"></p></span>
</code></pre></div>
</div>
<p><code>help</code> 动作的视图类似,如<a class="xref-link" href="#listing-raw-help-view">代码清单 3.10</a> 所示。</p>
<div id="listing-raw-help-view" data-type="listing">
<h5><span class="title-label">代码清单 3.10</span>:为“帮助”页面生成的视图</h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-erb"><pre><code><span class="nt"><h1></span>StaticPages#help<span class="nt"></h1></span>
<span class="nt"><p></span>Find me in app/views/static_pages/help.html.erb<span class="nt"></p></span>
</code></pre></div>
</div>
<p>这两个视图都只是占位用的,它们的内容中都有一个一级标题(<code>h1</code> 标签)和一个显示视图文件完整路径的段落(<code>p</code> 标签)。</p>
<h5 id="exercises-generated-static-pages" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>生成含有 <code>bar</code> 和 <code>baz</code> 两个动作的 <code>Foo</code> 控制器。</p>
</li>
<li>
<p>使用<a class="xref-link" href="#aside-undoing-things">旁注 3.1</a> 中介绍的技术删除 <code>Foo</code> 控制器及相关的动作。</p>
</li>
</ol>
</section>
<section data-type="sect2" id="custom-static-pages">
<h2><span class="title-label">3.2.2</span> 修改静态页面中的内容</h2>
<p>我们会在 <a class="xref-link" href="#slightly-dynamic-pages">3.4 节</a>添加一些简单的动态内容。现在,这些静态内容的存在是为了强调一件很重要的事:Rails 的视图可以只包含静态的 HTML。所以我们甚至无需了解 Rails 就可以修改“首页”和“帮助”页面的内容,如<a class="xref-link" href="#listing-custom-home-page">代码清单 3.11</a> 和<a class="xref-link" href="#listing-custom-help-page">代码清单 3.12</a> 所示。</p>
<div id="listing-custom-home-page" data-type="listing">
<h5><span class="title-label">代码清单 3.11</span>:修改“首页”的 HTML</h5>
<div class="source-file">app/views/static_pages/home.html.erb</div>
<div class="highlight language-html"><pre><code><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>
</code></pre></div>
</div>
<div id="listing-custom-help-page" data-type="listing">
<h5><span class="title-label">代码清单 3.12</span>:修改“帮助”页面的 HTML</h5>
<div class="source-file">app/views/static_pages/help.html.erb</div>
<div class="highlight language-html"><pre><code><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 page<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>
</code></pre></div>
</div>
<p>修改之后,这两个页面显示的内容如<a class="xref-link" href="#fig-custom-home-page">图 3.3</a> 和 <a class="xref-link" 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_4th_edition.png" alt="custom help page 4th 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-6" href="#fn-6">6</a>]</sup>,十分热闹。TDD 是一种测试技术,程序员要先编写失败的测试,然后再编写应用代码,让测试通过。本书采用一种轻量级、符合直觉的测试方案,只在适当的时候才使用 TDD,而不严格遵守 TDD 理念(<a class="xref-link" href="#aside-when-to-test">旁注 3.3</a>)。</p>
<div data-type="sidebar" id="aside-when-to-test" class="sidebar">
<h5><span class="title-label">旁注 3.3</span>:何时测试</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 class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>开始编写)和集成测试(<a class="xref-link" 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 class="xref-link" href="#aside-when-to-test">旁注 3.3</a>中的指导方针,我们先编写测试,然后使用失败的测试驱动我们编写应用代码。</p>
<p>着手测试是件具有挑战的事情,要求对 Rails 和 Ruby 都有深入的了解。这么早就编写测试可能有点儿让你害怕。不过,Rails 已经为我们解决了最难的部分,因为执行 <code>rails generate controller</code> 命令时(<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a>)自动生成了一个测试文件,我们可以从这个文件入手:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span><span class="nb">ls test</span>/controllers/
static_pages_controller_test.rb
</code></pre></div>
</div>
<p>我们来看一下这个文件的内容,如<a class="xref-link" href="#listing-default-controller-test">代码清单 3.13</a> 所示。</p>
<div id="listing-default-controller-test" data-type="listing">
<h5><span class="title-label">代码清单 3.13</span>:默认为 <code>StaticPages</code> 控制器生成的测试 <span class="green">GREEN</span></h5>
<div class="source-file">test/controllers/static_pages_controller_test.rb</div>
<div class="highlight language-ruby"><pre><code><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">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</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="n">static_pages_home_url</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="n">static_pages_help_url</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>现在无需理解详细的句法,不过可以看出,其中有两个测试,对应我们在命令行中传入的两个动作(<a class="xref-link" href="#listing-generating-pages">代码清单 3.6</a>)。各个测试先访问 URL,然后(通过断言)确认得到的是成功响应。其中,<code>get</code> 表示测试期望这两个页面是普通的网页,可以通过 <code>GET</code> 请求访问(<a class="xref-link" href="#aside-get-etc">旁注 3.2</a>);<code>:success</code> 响应(表示 <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success" class="external-link">200 OK</a>)是对 HTTP <a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes" class="external-link">响应码</a>的抽象表示。也就是说,下面这个测试的意思是:为了测试首页,向 <code>StaticPages</code> 控制器中 <code>home</code> 动作对应的 URL 发起 <code>GET</code> 请求,确认得到的是表示成功的响应码。</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><span class="nb">test</span> <span class="s2">"should get home"</span> <span class="k">do</span>
<span class="n">get</span> <span class="n">static_pages_home_url</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>测试循环的第一步是运行测试组件,确认测试现在可以通过。我们要执行下述命令:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.14</span>:<strong class="green">GREEN</strong></h5>
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails <span class="nb">test</span>
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
</code></pre></div>
</div>
<p>与预期一样,一开始测试组件可以通过(<strong class="green">GREEN</strong>)。(如果没按照 <a class="xref-link" href="#minitest-reporters">3.6.1 节</a>的说明添加 MiniTest 报告程序,不会看到绿色。不过,即使看不到真正的绿色,我们也经常这样表述。)在某些系统中,测试要花相当长的时间才能启动,这是因为(1)要启动 Spring 服务器,预载部分 Rails 环境,不过只有首次启动时会受此影响;(2)启动 Ruby 要花点儿时间。(第二点可以使用 <a class="xref-link" href="#guard">3.6.2 节</a>推荐的 Guard 改善。)</p>
</section>
<section data-type="sect2" id="red">
<h2><span class="title-label">3.3.2</span> 遇红</h2>
<p>我们在<a class="xref-link" href="#aside-when-to-test">旁注 3.3</a> 中说过,测试驱动开发流程是先编写一个失败测试,然后编写应用代码让测试通过,最后再根据需要重构代码。因为很多测试工具都使用红色表示失败的测试,使用绿色表示通过的测试,所以这个流程有时也叫“遇红-变绿-重构”循环。这一节我们先完成这个循环的第一步,编写一个失败测试,即“遇红”。<a class="xref-link" href="#green">3.3.3 节</a>“变绿”,<a class="xref-link" href="#layouts-and-embedded-ruby">3.4.3 节</a>“重构”。<sup>[<a id="fn-ref-7" href="#fn-7">7</a>]</sup></p>
<p>首先,我们要为“关于”页面编写一个失败测试。参照<a class="xref-link" href="#listing-default-controller-test">代码清单 3.13</a>,你或许能猜到该怎么写,如<a class="xref-link" href="#listing-about-test">代码清单 3.15</a> 所示。</p>
<div id="listing-about-test" data-type="listing">
<h5><span class="title-label">代码清单 3.15</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><code><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">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</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="n">static_pages_home_url</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="n">static_pages_help_url</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="n">static_pages_about_url</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>
</code></pre></div>
</div>
<p>如高亮显示的那几行所示,为“关于”页面编写的测试与首页和“帮助”页面的测试类似,只不过把“home”或“help”换成了“about”。</p>
<p>与预期一样,这个测试现在失败:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.16</span>:<strong class="red">RED</strong></h5>
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails <span class="nb">test</span>
3 tests, 2 assertions, 0 failures, 1 errors, 0 skips
</code></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>先看一下这个失败测试给出的错误消息:</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><code><span class="nv">$ </span>rails <span class="nb">test</span>
NameError: undefined <span class="nb">local </span>variable or method <span class="sb">`</span>static_pages_about_url<span class="s1">'</span>
</code></pre></div>
</div>
<p>这个错误消息说,未定义获取“关于”页面地址的 Rails 代码,其实就是提示我们要在路由文件中添加一个规则。参照<a class="xref-link" href="#listing-pages-routes">代码清单 3.7</a>,我们可以编写如<a class="xref-link" href="#listing-about-route">代码清单 3.18</a> 所示的路由。</p>
<div id="listing-about-route" data-type="listing">
<h5><span class="title-label">代码清单 3.18</span>:添加 <code>about</code> 路由 <span class="red">RED</span></h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">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="n">root</span> <span class="s1">'application#hello'</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>这段代码中高亮的那行告诉 Rails,把发给 /static_pages/about 页面的 <code>GET</code> 请求交给 <code>StaticPages</code> 控制器的 <code>about</code> 动作处理。这条规则会自动创建一个辅助方法:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><span class="n">static_pages_about_url</span>
</code></pre></div>
</div>
<p>再次运行测试组件,仍然无法通过,不过错误消息变了:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.19</span>:<strong class="red">RED</strong></h5>
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails <span class="nb">test</span>
AbstractController::ActionNotFound:
The action <span class="s1">'about'</span> could not be found <span class="k">for </span>StaticPagesController
</code></pre></div>
</div>
<p>这个错误消息的意思是,<code>StaticPages</code> 控制器缺少 <code>about</code> 动作。我们可以参照<a class="xref-link" href="#listing-static-pages-controller">代码清单 3.8</a> 中的 <code>home</code> 和 <code>help</code> 编写这个动作,如<a class="xref-link" href="#listing-adding-the-about-page">代码清单 3.20</a> 所示。</p>
<div id="listing-adding-the-about-page" data-type="listing">
<h5><span class="title-label">代码清单 3.20</span>:在 <code>StaticPages</code> 控制器中添加 <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><code><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>
</code></pre></div>
</div>
<p>现在测试依旧失败,不过测试消息又变了:</p>
<div data-type="listing">
<pre class="highlight language-sh"><code><span class="nv">$ </span>rails <span class="nb">test
</span>ActionController::UnknownFormat: StaticPagesController#about is missing
a template <span class="k">for </span>this request format and variant.</code></pre>
</div>
<p>这表明没有模板。在 Rails 中,模板就是视图。<a class="xref-link" 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)" class="external-link">Unix <code>touch</code> 命令</a>,如下所示:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span><span class="nb">touch </span>app/views/static_pages/about.html.erb
</code></pre></div>
</div>
<p>如《<a href="https://www.learnenough.com/command-line-tutorial#sec-listing" class="external-link">Learn Enough Command Line to Be Dangerous</a>》所讲,<code>touch</code> 的作用是更新文件或文件夹的修改时间戳,但有个副作用:如果文件不存在,它会新建一个空文件。(如果使用云端 IDE,或许要刷新文件树,参见 <a class="xref-link" href="chapter1.html#bundler">1.3.1 节</a>。这也体现了“<a class="xref-link" href="chapter1.html#aside-technical-sophistication">技术是复杂的</a>”。)</p>
<p>在正确的目录中创建 <code>about.html.erb</code> 文件之后,写入<a class="xref-link" href="#listing-custom-about-page">代码清单 3.21</a> 中的内容。</p>
<div id="listing-custom-about-page" data-type="listing">
<h5><span class="title-label">代码清单 3.21</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><code><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>
</code></pre></div>
</div>
<p>现在执行 <code>rails test</code> 命令,会看到测试能通过了:</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.22</span>:<strong class="green">GREEN</strong></h5>
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails <span class="nb">test</span>
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
</code></pre></div>
</div>
<p>当然,我们还可以在浏览器中查看这个页面(<a class="xref-link" 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>:新添加的“关于”页面(/static_pages/about)</div></div>
</section>
<section data-type="sect2" id="refactor">
<h2><span class="title-label">3.3.4</span> 重构</h2>
<p>现在测试已经变绿,我们可以自信地尽情重构了。开发应用时,代码经常会“变味”(意思是代码会变得丑陋、啰嗦,有大量重复)。电脑不会在意,但是人类会,所以经常重构,把代码变简洁一些是很重要的事情。我们的演示应用现在还很小,没什么可重构的,不过代码无时无刻不在变味,所以 <a class="xref-link" 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 class="xref-link" 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 class="xref-link" href="#table-static-pages">表 3.2</a>)。</p>
<p><code>rails new</code> 命令创建了一个布局文件,不过现在最好不用。我们重命名这个文件:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span><span class="nb">mv </span>app/views/layouts/application.html.erb layout_file <span class="c"># 临时改动</span>
</code></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 class="xref-link" href="#listing-html-structure">代码清单 3.23</a> 所示。(《<a href="http://learnenough.com/html-tutorial" class="external-link">Learn Enough HTML to Be Dangerous</a>》对这个话题有深入说明。)</p>
<div id="listing-html-structure" data-type="listing">
<h5><span class="title-label">代码清单 3.23</span>:网页一般的 HTML 结构</h5>
<div class="highlight language-html"><pre><code><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>
</code></pre></div>
</div>
<p>这段代码的最顶部是文档类型声明(document type declaration,简称 doctype),作用是告诉浏览器使用哪个 HTML 版本(这里使用的是 <a href="http://en.wikipedia.org/wiki/HTML5" class="external-link">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 class="xref-link" href="#table-static-pages">表 3.2</a> 中的每个标题编写简单的测试,然后合并到<a class="xref-link" href="#listing-about-test">代码清单 3.15</a> 中。<code>assert_select</code> 方法的作用是检查有没有指定的 HTML 标签。这种方法有时也叫“选择符”(selector),因此才为这个方法取这么一个名称。<sup>[<a id="fn-ref-9" href="#fn-9">9</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><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>
</code></pre></div>
</div>
<p>这行代码的作用是检查有没有 <code><title></code> 标签,以及其中的内容是不是“Home | Ruby on Rails Tutorial Sample App”字符串。把这样的代码分别放到三个页面的测试中,得到的结果如<a class="xref-link" href="#listing-title-tests">代码清单 3.24</a> 所示。</p>
<div id="listing-title-tests" data-type="listing">
<h5><span class="title-label">代码清单 3.24</span>:加入标题测试后的 <code>StaticPages</code> 控制器测试 <span class="red">RED</span></h5>
<div class="source-file">test/controllers/static_pages_controller_test.rb</div>
<div class="highlight language-ruby"><pre><code><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">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</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="n">static_pages_home_url</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="n">static_pages_help_url</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="n">static_pages_about_url</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>
</code></pre></div>
</div>
<p>写好测试之后,应该确认一下现在测试组件是失败的(<strong class="red">RED</strong>):</p>
<div data-type="listing">
<h5><span class="title-label">代码清单 3.25</span>:<strong class="red">RED</strong></h5>
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails <span class="nb">test</span>
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips
</code></pre></div>
</div>
</section>
<section data-type="sect2" id="adding-page-titles">
<h2><span class="title-label">3.4.2</span> 添加页面标题(变绿)</h2>
<p>现在,我们要为每个页面添加标题,让前一节的测试通过。参照<a class="xref-link" href="#listing-html-structure">代码清单 3.23</a> 中的 HTML 结构,把<a class="xref-link" href="#listing-custom-home-page">代码清单 3.11</a> 中的首页内容换成<a class="xref-link" href="#listing-home-view-full-html">代码清单 3.26</a> 中的内容。</p>
<div id="listing-home-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.26</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><code><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>
</code></pre></div>
</div>
<p class="pagebreak-before">修改之后的首页如<a class="xref-link" 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_4th_ed.png" alt="home view full html 4th ed" /><div class="figcaption"><span class="title-label">图 3.6</span>:添加标题后的首页</div></div>
<p>使用类似的方式修改“帮助”页面和“关于”页面,得到的代码如<a class="xref-link" href="#listing-help-view-full-html">代码清单 3.27</a> 和<a class="xref-link" href="#listing-about-view-full-html">代码清单 3.28</a> 所示。</p>
<div id="listing-help-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.27</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><code><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
page<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>
</code></pre></div>
</div>
<div id="listing-about-view-full-html" data-type="listing">
<h5><span class="title-label">代码清单 3.28</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><code><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>
</code></pre></div>
</div>
<p>现在,测试组件能通过了:</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><code><span class="nv">$ </span>rails <span class="nb">test</span>
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
</code></pre></div>
</div>
<h5 id="exercises-adding-page-titles" class="discrete">练习</h5>
<p>从本节开始,我们将修改应用代码,而且这些改动不会体现在以后的代码清单中。这么做是为了让没有做练习的读者能读懂正文,因为解答练习所需的代码与正文有差异。这也体现了“<a class="xref-link" href="chapter1.html#aside-technical-sophistication">技术是复杂的</a>”。</p>
<ol class="arabic">
<li>
<p>你可能注意到了,<code>StaticPages</code> 控制器的测试(<a class="xref-link" href="#listing-title-tests">代码清单 3.24</a>)中有些重复,每个标题测试中都有“Ruby on Rails Tutorial Sample App”。我们可以使用特殊的函数 <code>setup</code> 去除重复。这个函数在每个测试运行之前执行。请你确认<a class="xref-link" href="#listing-base-title-test">代码清单 3.30</a> 中的测试仍能通过。(<a class="xref-link" href="#listing-base-title-test">代码清单 3.30</a> 中使用了一个实例变量,<a class="xref-link" href="chapter2.html#mvc-in-action">2.2.2 节</a>简单介绍过,<a class="xref-link" href="chapter4.html#a-user-class">4.4.5 节</a>会进一步说明。这段代码还使用了字符串插值操作,<a class="xref-link" href="chapter4.html#strings">4.2.2 节</a>会做进一步说明。)</p>
</li>
</ol>
<div id="listing-base-title-test" data-type="listing">
<h5><span class="title-label">代码清单 3.30</span>:使用一个基标题的 <code>StaticPages</code> 控制器测试 <span class="green">GREEN</span></h5>