-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathchapter1.html
1277 lines (1130 loc) · 112 KB
/
chapter1.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 教程 - 第 1 章 从零开始,完成一次部署</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="最好的 Ruby on Rails 入门教程"/>
<meta name="keywords" content="ruby, rails, tutorial"/>
<meta name="author" content="Michael Hartl"/>
<meta name="translator" content="安道"/>
<meta name="generator" content="persie 0.0.1.beta.3"/>
<link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/twitter-bootstrap/3.2.0/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="http://cdn.staticfile.org/font-awesome/4.2.0/css/font-awesome.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/style.css"/>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/twitter-bootstrap/3.2.0/js/collapse.min.js"></script>
<script type="text/javascript" src="assets/global.js"></script>
</head>
<body>
<header class="navbar navbar-default navbar-fixed-top navbar-book">
<div class="container">
<div class="navbar-header">
<a href="http://railstutorial-china.org" class="navbar-brand">Ruby on Rails 教程</a>
<button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target=".book-navbar-collapse">
<span class="sr-only">导航</span>
<i class="fa fa-bars"></i>
</button>
<a href="http://railstutorial-china.org/#purchase" id="navbar-purchase-xs" class="btn btn-warning navbar-btn visible-xs collapsed-purchase-btn">购买</a>
</div>
<nav class="collapse navbar-collapse book-navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="http://railstutorial-china.org" title="首页">首页</a></li>
<li class="active"><a href="http://railstutorial-china.org/read/" title="在线阅读">阅读</a></li>
<li><a href="http://railstutorial-china.org/blog/" title="最新消息">博客</a></li>
<li><a href="https://selfstore.io/products/189/topics" title="论坛">论坛</a></li>
<li class="hidden-xs"><div><a href="http://railstutorial-china.org/#purchase" id="navbar-purchase" class="btn btn-warning navbar-btn" title="购买电子书">购买</a></div></li>
</ul>
</nav>
</div>
</header>
<div class="content">
<div class="container">
<div class="row">
<div class="col-lg-offset-2 col-lg-8">
<div class="alert alert-warning">
<p>在线版的内容可能落后于电子书,如果想及时获得更新,请<a href="http://railstutorial-china.org/#purchase" title="购买电子书">购买电子书</a>。</p>
</div>
<article class="article">
<section data-type="chapter" id="from-zero-to-deploy">
<h1><span class="title-label">第 1 章</span> 从零开始,完成一次部署</h1>
<p>欢迎阅读《Ruby on Rails 教程:通过 Rails 学习 Web 开发》。本书的目的是教你如何开发 Web 应用,而我们选择的工具是流行的 <a href="http://rubyonrails.org">Ruby on Rails</a> Web 框架。如果你刚接触这一领域,本书会向你详细介绍 Web 应用开发的方方面面,包括 Ruby、Rails、HTML 和 CSS、数据库、版本控制、测试,以及部署的基本知识。学会这些知识足够为你赢得一份 Web 开发者的工作,或者还可以让你成为一名技术创业者。如果你已经了解 Web 开发,阅读本书能快速学会 Rails 框架的基础,包括 MVC 和 REST、生成器、迁移、路由,以及嵌入式 Ruby。不管怎样,读完本书之后,以你所掌握的知识,已经能够阅读讨论更高级话题的图书和博客,或者观看视频。这些都是旺盛的编程教学生态圈的一部分。<sup>[<a id="fn-ref-1" href="#fn-1">1</a>]</sup></p>
<p>本书采用一种综合式方法讲授 Web 开发,在学习的过程中我们要开发三个演示应用:第一个最简单,叫 <code>hello_app</code>(<a href="#the-first-application">1.3 节</a>);第二个功能多一些,叫 <code>toy_app</code>(<a href="chapter2.html#a-toy-app">第 2 章</a>),第三个是真正的演示程序,叫 <code>sample_app</code>(<a href="chapter3.html#mostly-static-pages">第 3 章</a>到<a href="chapter12.html#following-users">第 12 章</a>)。从这三个应用的名字可以看出,书中开发的应用不限定于某种特定类型的网站。不过,最后一个演示应用有点儿类似某个流行的<a href="https://twitter.com">社会化微博网站</a>(很巧,这个网站一开始也是使用 Rails 开发的)。本书的重点是介绍通用原则,所以不管你想开发什么样的 Web 应用,读完本书后,都能建立扎实的基础。</p>
<p>人们经常会问,我要具备多少背景知识才能阅读本书学习 Web 开发。<a href="#prerequisites">1.1.1 节</a>对此做了详细分析。Web 开发是个具有挑战性的学科,对没有任何背景知识的初学者来说挑战更大。我最初为本书设定的阅读对象是已经具有一定编程和 Web 开发经验的开发者,但后来发现读者中有很多都刚开始接触开发。所以,现在你看到的本书第三版做出了很多努力,尽量降低了入门 Rails 的门槛。</p>
<div data-type="sidebar" id="aside-barrier" class="sidebar">
<h5><span class="title-label">旁注 1.1</span>:降低门槛</h5>
<p>本书第三版采取了很多措施,降低入门 Rails 的门槛:</p>
<ul>
<li>
<p>使用云端标准的开发环境(<a href="#up-and-running">1.2 节</a>),规避了安装和配置新系统涉及到的很多问题;</p>
</li>
<li>
<p>合理利用 Rails 默认提供的组件,例如原生的 MiniTest 测试框架;</p>
</li>
<li>
<p>删掉了很多外部依赖件(RSpec,Cucumber,Capybara,Factory Girl);</p>
</li>
<li>
<p>使用一种更轻量级、更灵活的测试方式;</p>
</li>
<li>
<p>延后介绍,或者删除了较为复杂的配置选项(Guard,Spork,RubyTest);</p>
</li>
<li>
<p>弱化某个 Rails 版本特有的功能,更加强调 Web 开发的通用原则。</p>
</li>
</ul>
<p>我希望这些变化能让本书第三版获得比前一版更多的读者。</p>
</div>
<p>这第一章,我们要安装 Ruby on Rails 以及需要的所有软件,而且还要架设开发环境(<a href="#up-and-running">1.2 节</a>)。然后创建第一个 Rails 应用,<code>hello_app</code>。本书旨在介绍优秀的软件开发习惯,所以在创建第一个应用之后,我们会立即将它纳入版本控制系统 Git 中(<a href="#version-control-with-git">1.4 节</a>)。你可能不相信,在这一章,我们还要部署这个应用(<a href="#deploying">1.5 节</a>),把它放到外网上。</p>
<p><a href="chapter2.html#a-toy-app">第 2 章</a>会创建第二个项目,演示 Rails 应用的一些基本操作。为了速度,我们会使用脚手架(<a href="#aside-scaffolding">旁注 1.2</a>)创建这个应用(名为 <code>toy_app</code>)。因为生成的代码很丑也很复杂,所以<a href="chapter2.html#a-toy-app">第 2 章</a>将集中精力在浏览器中,使用 URI(经常称为 URL)<sup>[<a id="fn-ref-2" href="#fn-2">2</a>]</sup>和这个应用交互。</p>
<p>本书剩下的章节将集中精力开发一个真实的大型演示应用(名为 <code>sample_app</code>),所有代码都从零开始编写。在开发这个应用的过程中,我们会用到模拟技术,“测试驱动开发”(Test-driven Development,简称 TDD)理念,以及“集成测试”(integration test)。<a href="chapter3.html#mostly-static-pages">第 3 章</a>创建静态页面,然后增加一些动态内容。<a href="chapter4.html#rails-flavored-ruby">第 4 章</a>会简要介绍一下 Rails 使用的 Ruby 程序语言。<a href="chapter5.html#filling-in-the-layout">第 5 章</a>到<a href="chapter10.html#account-activation-and-password-reset">第 10 章</a>将逐步完善这个应用的低层结构,包括网站的布局,用户数据模型,完整的注册和认证系统(含有账户激活和密码重设功能)。最后,<a href="chapter11.html#user-microposts">第 11 章</a>和<a href="chapter12.html#following-users">第 12 章</a>将添加微博和社交功能,最终开发出一个可以正常运行的演示网站。</p>
<div data-type="sidebar" id="aside-scaffolding" class="sidebar">
<h5><span class="title-label">旁注 1.2</span>:脚手架——更快,更简单,更诱人</h5>
<p>Rails 出现伊始就吸引了众多目光,特别是 Rails 创始人 David Heinemeier Hansson 录制的著名的“<a href="http://v.youku.com/v_show/id_XNzg5MjUwOTU2.html">15分钟开发一个博客程序</a>”视频。这个视频及其衍生版本是窥探 Rails 强大功能一种很好的方式,我推荐你看一下这些视频。不过事先提醒一下,这些视频中的演示能控制在 15 分钟以内,得益于一种叫做“脚手架”(scaffold)的功能,通过 Rails 命令 <code>generate scaffold</code> 生成大量的代码。</p>
<p>写作本书时,我也想过使用脚手架,因为它<a href="http://en.wikipedia.org/wiki/Dark_side_(Star_Wars)">更快、更简单、更诱人</a>。不过脚手架生成的大量且复杂的代码会让初学者困惑。虽然能学会脚手架的用法,但并不明白到底发生了什么事。使用脚手架,你只是一个脚本生成器的使用者,无法提升你对 Rails 的认识。</p>
<p>本书将采用一种不同的方式,虽然<a href="chapter2.html#a-toy-app">第 2 章</a>会用脚手架开发一个小型的玩具应用,但本书的核心是从<a href="chapter3.html#mostly-static-pages">第 3 章</a>起开发的演示应用。在开发这个演示应用的每个阶段,我们只会编写少量的代码,易于理解但又具有一定的挑战性。通过这一过程,最终你会对 Rails 有较为深刻的理解,而且能灵活运用,开发几乎任何类型的 Web 应用。</p>
</div>
<section data-type="sect1" id="introduction">
<h1><span class="title-label">1.1</span> 简介</h1>
<p>Ruby on Rails(或者简称“Rails”)是一个 Web 开发框架,使用 Ruby 编程语言开发。自 2004 年出现之后,Rails 就迅速成为动态 Web 应用开发领域功能最强大、最受欢迎的框架之一。使用 Rails 的公司有很多,例如 <a href="http://airbnb.com/">Airbnb</a>、<a href="http://basecamp.com/">Basecamp</a>、<a href="http://disney.com">Disney</a>、<a href="http://github.com/">Github</a>、<a href="http://hulu.com/">Hulu</a>、<a href="http://kickstarter.com/">Kickstarter</a>、<a href="http://shopify.com/">Shopify</a>、<a href="http://twitter.com/">Twitter</a> 和 <a href="http://yellowpages.com/">Yellow Pages</a>。还有很多 Web 开发工作室专门从事 Rails 应用开发,例如 <a href="http://entp.com/">ENTP</a>、<a href="http://thoughtbot.com/">thoughtbot</a>、<a href="http://pivotallabs.com/">Pivotal Labs</a>、<a href="http://hashrocket.com/">Hashrocket</a> 和 <a href="http://www.happyfuncorp.com/">HappyFunCorp</a>。除此之外还有无数独立顾问,培训人员和项目承包商。</p>
<p>Rails 为何如此成功呢?首先,Rails 完全开源,基于 <a href="http://www.opensource.org/licenses/mit-license.php">MIT 协议</a>发布,可以免费下载、使用。Rails 的成功很大程度上得益于它优雅而紧凑的设计。Rails 熟谙 Ruby 语言的可扩展性,开发了一套用于编写 Web 应用的“<a href="http://en.wikipedia.org/wiki/Domain_Specific_Language">领域特定语言</a>”(Domain-specific Language,简称 DSL)。所以 Web 编程中很多常见的任务,例如生成 HTML,创建数据模型和 URL 路由,在 Rails 中都很容易实现,最终得到的应用代码简洁而且可读性高。</p>
<p>Rails 还会快速跟进 Web 开发领域最新的技术和框架设计方式。例如,Rails 是最早使用 REST 架构风格组织 Web 应用的框架之一(这个架构贯穿本书)。当其他框架开发出成功的新技术后,Rails 的创建者 <a href="http://loudthinking.com/">David Heinemeier Hansson</a> 及其<a href="http://rubyonrails.org/core">核心开发团队</a>会毫不犹豫的将其吸纳进来。或许最典型的例子是 Rails 和 Merb 两个项目的合并,从此 Rails 继承了 Merb 的模块化设计、稳定的 <a href="http://en.wikipedia.org/wiki/Application_programming_interface">API</a>,性能也得到了提升。</p>
<p>最后一点,Rails 有一个活跃而多元化的社区。社区中有数以百计的开源项目<a href="http://contributors.rubyonrails.org/">贡献者</a>,以及与会者众多的<a href="http://railsconf.com/">开发者大会</a>,而且还开发了大量的 <a href="https://rubygems.org/">gem</a>(代码库,一个 gem 解决一个特定的问题,例如分页和图片上传),有很多内容丰富的博客,以及一些讨论组和 IRC 频道。有如此众多的 Rails 程序员也使得处理程序错误变得简单了:在谷歌中搜索错误消息,几乎总能找到一篇相关的博客文章或讨论组中的话题。</p>
<section data-type="sect2" id="prerequisites">
<h2><span class="title-label">1.1.1</span> 预备知识</h2>
<p>阅读本书不需要具备特定的预备知识。本书不仅介绍 Rails,还涉及底层的 Ruby 语言,Rails 默认使用的测试框架(MiniTest),Unix 命令行,<a href="http://en.wikipedia.org/wiki/HTML">HTML</a>、<a href="http://en.wikipedia.org/wiki/CSS">CSS</a>,少量的 <a href="http://en.wikipedia.org/wiki/JavaScript">JavaScript</a>,以及一点 <a href="http://en.wikipedia.org/wiki/SQL">SQL</a>。我们要掌握的知识很多,所以我一般建议阅读本书之前先具备一些 HTML 和编程知识。说是这么说,但也有相当数量的初学者使用本书从零开始学习 Web 开发,所以即便你的经验有限,我还是建议你读一下试试。如果你招架不住了,随时可以翻回这里,使用下面列出的某个资源,从头学起。多位读者告诉我,他们建议跟着教程做两遍,第一遍毕竟学到的知识有限,但再做第二遍就简单多了。</p>
<p>学习 Rails 时经常有人问,要不要先学 Ruby。这个问题的答案取决于你个人的学习方式以及编程经验。如果你希望较为系统地彻底学习,或者你以前从未编程过,那么先学 Ruby 或许更合适。学习 Ruby,我推荐阅读 Chris Pine 写的《<a href="http://pragprog.com/book/ltp2/learn-to-program">Learn to Program</a>》和 Peter Cooper 写的《<a href="http://book.douban.com/subject/3836123/">Ruby 入门</a>》。很多 Rails 初学者很想立即开始开发 Web 应用,而不是在此之前读完一本介绍 Ruby 的书。如果你是这类人,我推荐你在 <a href="http://tryruby.org/">Try Ruby</a> 上学习一些简短的交互式教程,以便在阅读本书之前对 Ruby 有个大概的了解。如果你还是觉得本书太难,或许可以先看 Daniel Kehoe 写的《<a href="http://learn-rails.com/learn-ruby-on-rails.html">Learn Ruby on Rails</a>》,或者学习 <a href="https://onemonth.com">One Month Rails</a> 课程——它们更适合没有任何背景知识的初学者。</p>
<p>不管你从哪里开始,读完本书后都应该可以学习 Rails 中高级知识了。以下是我推荐的一些学习资源:</p>
<ul>
<li>
<p><a href="http://www.codeschool.com/">Code School</a>:很好的交互式编程课程;</p>
</li>
<li>
<p><a href="http://www.gotealeaf.com/railstutorial">Tealeaf Academy</a>:很好的在线 Rails 开发训练营(包含高级知识);</p>
</li>
<li>
<p><a href="http://www.thinkful.com/a/railstutorial">Thinkful</a>:在线课程,和本书的难度差不多;</p>
</li>
<li>
<p>Ryan Bates 主持的 <a href="http://railscasts.com/">RailsCasts</a>:优秀的 Rails 视频(大多数免费);</p>
</li>
<li>
<p><a href="https://tutorials.railsapps.org/hartl">RailsApps</a>:很多针对特定话题的 Rails 项目和教程,说明详细;</p>
</li>
<li>
<p><a href="http://guides.rubyonrails.org/">Rails 指南</a>:按话题编写的 Rails 参考,经常更新。<sup>[<a id="fn-ref-3" href="#fn-3">3</a>]</sup></p>
</li>
</ul>
</section>
<section data-type="sect2" id="conventions-in-this-book">
<h2><span class="title-label">1.1.2</span> 排版约定</h2>
<p>本书使用的排版方式,很多都不用再做解释。本节我要说一下那些意义不是很清晰的排版。</p>
<p>书中很多代码清单用到了命令行命令。为了行文简便,所有命令都使用 Unix 风格命令行提示符(一个美元符号),例如:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"hello, world!"</span>
hello, world!
</pre></div>
</div>
<p>在 <a href="#up-and-running">1.2 节</a>我会提到,不管你使用哪种操作系统(尤其是 Windows),我都建议使用云端开发环境(<a href="#development-environment">1.2.1 节</a>),这种环境都内置了 Unix(Linux)命令行。命令行十分有用,因为 Rails 提供了很多可以在命令行中运行的命令。例如,在 <a href="#rails-server">1.3.2 节</a>中,我们会使用 <code>rails server</code> 命令启动本地的 Web 开发服务器:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>rails server
</pre></div>
</div>
<p>和命令行提示符一样,本书也会使用 Unix 惯用的目录分隔符(即斜线 <code>/</code>)。例如,演示应用中的配置文件 <code>production.rb</code>,它的路径是:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre>config/environments/production.rb
</pre></div>
</div>
<p>这个文件路径相对于应用的根目录。在不同的系统中,根目录会有差别。在云端 IDE 中,根目录像下面这样:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre>/home/ubuntu/workspace/sample_app/
</pre></div>
</div>
<p>所以,<code>production.rb</code> 的完整路径是:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre>/home/ubuntu/workspace/sample_app/config/environments/production.rb
</pre></div>
</div>
<p>为了行文简洁,我一般都会省略应用的路径,写成 <code>config/environments/production.rb</code>。</p>
<p>本书经常需要显示一些来自其他程序(shell 命令,版本控制系统,Ruby 程序等)的输出。因为系统之间存在细微的差异,你看到的输出结果可能和书中显示的不完全一致,但是无需担心。而且,有些命令在某些操作系统中可能会导致错误,本书不会一一说明这些错误的解决方法,你可以在谷歌中搜索错误消息,自己尝试解决——这也是为现实中的软件开发做准备。如果你在阅读本书的过程中遇到了问题,我建议你看一下<a href="http://railstutorial.org#help">本书网站帮助区</a>中列出的资源。</p>
<p>在这个教程中我们要测试 Rails 应用,所以最好知道某段代码能让测试组件失败(使用红色表示)还是通过(使用绿色表示)。为了方便,导致测试失败的代码使用“<strong class="red">RED</strong>”标记,能让测试通过的代码使用“<strong class="green">GREEN</strong>”标记。</p>
<p>每一章都有一些练习,你可以自己决定要不要做,但推荐做。为了区分正文和练习,练习的解答不会和后续的内容混在一起。如果后面需要使用某个练习中的代码,我会在正文中指出来,并给出解答方法。</p>
<p>最后,为了方便,本书使用两种排版方式,让代码清单更易理解。第一种,有些代码清单中包含一到多个高亮的代码行,如下所示:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">validates</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">presence</span><span class="p">:</span> <span class="kp">true</span>
<span class="hll"> <span class="n">validates</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">presence</span><span class="p">:</span> <span class="kp">true</span>
</span><span class="k">end</span>
</pre></div>
</div>
<p>高亮的代码行一般用于标出这段代码清单中最重要的新代码,偶尔也用来表示当前代码清单和前一个代码清单的差异。第二种,为了行文简洁,书中很多代码清单中都有竖排的点号,如下所示:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="n">has_secure_password</span>
<span class="k">end</span>
</pre></div>
</div>
<p>这些点号表示省略的代码,不要直接复制。</p>
</section>
</section>
<section data-type="sect1" id="up-and-running">
<h1><span class="title-label">1.2</span> 搭建环境</h1>
<p>就算对经验丰富的 Rails 开发者来说,安装 Ruby、Rails,以及相关的所有软件,也要几经波折。这些问题是由环境的多样性导致的。不同的操作系统,版本号,文本编辑器的偏好设置和“集成开发环境”(Integrated Development Environment,简称 IDE)等,都会导致环境有所不同。如果你已经在本地电脑中配置好了开发环境,可以继续使用你的环境。但对于初学者,我更鼓励使用云端集成开发环境(<a href="#aside-barrier">旁注 1.1</a>中说过),这样可以避免安装和配置出现问题。云端 IDE 运行在普通的 Web 浏览器中,因此在不同的平台中表现一致,这对 Rails 开发一直很困难的操作系统(例如 Windows)来说尤其有用。如果你不怕挑战,仍想在本地开发环境中学习书中的教程,我建议你按照 <a href="http://installrails.com">InstallRails.com</a> 中的说明搭建环境。<sup>[<a id="fn-ref-4" href="#fn-4">4</a>]</sup></p>
<section data-type="sect2" id="development-environment">
<h2><span class="title-label">1.2.1</span> 开发环境</h2>
<p>不同的人有不同的喜好,每个 Rails 程序员都有一套自己的开发环境。为了避免问题复杂化,本书使用一个标准的云端开发环境,<a href="https://c9.io">Cloud9</a>。而且,为了第三版我还和 Cloud9 合作,专为本书量身打造了一个开发环境。这个开发环境预先安装好了 Rails 开发所需的大多数软件,包括 Ruby、RubyGems 和 Git(其实,唯有 Rails 要单独安装,而且这么做是有目的的,详情参见 <a href="#installing-rails">1.2.2 节</a>)。这个云端 IDE 还包含 Web 应用开发所需的三个基本组件:文本编辑器,文件系统浏览器,以及命令行终端(如<a href="#fig-ide-anatomy">图 1.1</a>)。云端 IDE 中的文本编辑器功能很多,其中一项是“在文件中查找”的全局搜索功能<sup>[<a id="fn-ref-5" href="#fn-5">5</a>]</sup>,我觉得这个功能对大型 Rails 项目来说是必备的。</p>
<div id="fig-ide-anatomy" class="figure"><img src="images/chapter1/ide_anatomy.png" alt="ide anatomy" /><div class="figcaption"><span class="title-label">图 1.1</span>:云端 IDE 的界面布局</div></div>
<p>这个云端开发环境的使用步骤如下:</p>
<ol class="arabic">
<li>
<p>在 Cloud9 中<a href="https://c9.io/web/sign-up/free">注册一个免费账户</a>;</p>
</li>
<li>
<p>点击“Go to your Dashboard”(进入控制台);</p>
</li>
<li>
<p>选择“Create New Workspace”(新建工作空间);</p>
</li>
<li>
<p>创建一个名为“rails-tutorial”(不是“rails_tutorial”)的工作空间,勾选“Private to the people I invite”(仅对我邀请的人开放),然后选择表示 Rails 教程的图标(不是表示 Ruby on Rails 那个图标),如<a href="#fig-cloud9-new-workspace">图 1.2</a> 所示。;</p>
</li>
<li>
<p>点击“Create”(创建);</p>
</li>
<li>
<p>Cloud9 配置工作空间完成后,选择这个工作空间,然后点击“Start editing”(开始编辑)。</p>
</li>
</ol>
<div id="fig-cloud9-new-workspace" class="figure"><img src="images/chapter1/cloud9_new_workspace.png" alt="cloud9 new workspace" /><div class="figcaption"><span class="title-label">图 1.2</span>:在 Cloud9 中新建一个工作空间</div></div>
<p>因为使用两个空格缩进几乎是 Ruby 圈通用的约定,所以我建议你修改编辑器的配置,把默认的四个空格改为两个。配置方法是,点击右上角的齿轮图标,然后选择“Code Editor (Ace)”(Ace 代码编辑器),编辑“Soft Tabs”(软制表符)设置,如<a href="#fig-cloud9-two-spaces">图 1.3</a> 所示。(注意,修改设置后立即生效,无需点击“Save”按钮。)</p>
<div id="fig-cloud9-two-spaces" class="figure"><img src="images/chapter1/cloud9_two_spaces.png" alt="cloud9 two spaces" /><div class="figcaption"><span class="title-label">图 1.3</span>:让 Cloud9 使用两个空格缩进</div></div>
</section>
<section data-type="sect2" id="installing-rails">
<h2><span class="title-label">1.2.2</span> 安装 Rails</h2>
<p>前一节创建的开发环境包含所有软件,但没有 Rails。<sup>[<a id="fn-ref-6" href="#fn-6">6</a>]</sup>为了安装 Rails,我们要使用包管理器 RubyGems 提供的 <code>gem</code> 命令,在命令行终端里输入<a href="#listing-installing-rails">代码清单 1.1</a> 所示的命令。(如果在本地系统中开发,在终端窗口中输入这个命令;如果使用云端 IDE,在<a href="#fig-ide-anatomy">图 1.1</a> 中的“命令行终端”输入这个命令。)</p>
<div id="listing-installing-rails" data-type="listing">
<h5><span class="title-label">代码清单 1.1</span>:安装 Rails,指定版本</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>gem install rails -v 4.2.2
</pre></div>
</div>
<p><code>-v</code> 旗标的作用是指定安装哪个 Rails 版本。你使用的版本必须和我一样,这样学习的过程中,你我得到的结果才相同。</p>
</section>
</section>
<section data-type="sect1" id="the-first-application">
<h1><span class="title-label">1.3</span> 第一个应用</h1>
<p>按照计算机编程领域<a href="http://www.catb.org/jargon/html/H/hello-world.html">长期沿用的传统</a>,第一个应用的目的是编写一个“hello, world”程序。具体说来,我们要创建一个简单的应用,在网页中显示字符串“hello, world!”,在开发环境(<a href="#hello-world">1.3.4 节</a>)和线上网站中(<a href="#deploying">1.5 节</a>)都是如此。</p>
<p>Rails 应用一般都从 <code>rails new</code> 命令开始,这个命令会在你指定的文件夹中创建一个 Rails 应用骨架。如果没使用<a href="#development-environment">1.2.1 节</a>推荐的 Cloud9 IDE,首先你要新建一个文件夹,命名为 <code>workspace</code>,然后进入这个文件夹,如<a href="#listing-mkdir-rails-projects">代码清单 1.2</a> 所示。(<a href="#listing-mkdir-rails-projects">代码清单 1.2</a> 中使用了 Unix 命令 <code>cd</code> 和 <code>mkdir</code>,如果你不熟悉这些命令,可以阅读<a href="#aside-unix-commands">旁注 1.3</a>。)</p>
<div id="listing-mkdir-rails-projects" data-type="listing">
<h5><span class="title-label">代码清单 1.2</span>:为 Rails 项目新建一个文件夹,命名为 <code>workspace</code>(在云端环境中不用这一步)</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span><span class="nb">cd</span> <span class="c"># 进入家目录</span>
<span class="nv">$ </span>mkdir workspace <span class="c"># 新建 workspace 文件夹</span>
<span class="nv">$ </span><span class="nb">cd </span>workspace/ <span class="c"># 进入 workspace 文件夹</span>
</pre></div>
</div>
<div data-type="sidebar" id="aside-unix-commands" class="sidebar">
<h5><span class="title-label">旁注 1.3</span>:Unix 命令行速成课</h5>
<p>使用 Windows 和 Mac OS X(数量较少,但增长势头迅猛)的用户可能对 Unix 命令行不熟悉。如果使用推荐的云端环境,很幸运,这个环境提供了 Unix(Linux)命令行——在标准的 <a href="http://en.wikipedia.org/wiki/Shell_(computing)">shell 命令行界面</a>中运行的 <a href="http://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash</a>。</p>
<p>命令行的基本思想很简单:使用简短的命令执行很多操作,例如创建文件夹(<code>mkdir</code>),移动和复制文件(<code>mv</code> 和 <code>cp</code>),以及变换目录浏览文件系统(<code>cd</code>)。主要使用图形化界面(Graphical User Interface,简称 GUI)的用户可能觉得命令行落后,其实是被表象蒙蔽了:命令行是开发者最强大的工具之一。其实,你经常会看到经验丰富的开发者开着多个终端窗口,运行命令行 shell。</p>
<p>这是一门很深的学问,但在本书中只会用到一些最常用的 Unix 命令行命令,如<a href="#table-unix-commands">表 1.1</a>所示。若想更深入地学习 Unix 命令行,请阅读 Mark Bates 写的《<a href="http://conqueringthecommandline.com/">Conquering the Command Line</a>》(可以<a href="http://conqueringthecommandline.com/book">免费在线阅读</a>,也可以<a href="http://conqueringthecommandline.com/#pricing">购买电子书和视频</a>)。</p>
</div>
<table id="table-unix-commands" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 1.1</span>:一些常用的 Unix 命令</caption>
<colgroup>
<col style="width: 33%;" />
<col style="width: 33%;" />
<col style="width: 33%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">作用</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"><code>ls</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ ls -l</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"><code>mkdir <dirname></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ mkdir workspace</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"><code>cd <dirname></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ cd workspace/</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">进入上层目录</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ cd ..</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">进入家目录</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$cd ~</code> 或 <code>$ cd</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">进入家目录中的文件夹</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ cd ~/workspace/</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"><code>mv <source> <target></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ mv README.rdoc README.md</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"><code>cp <source> <target></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ cp README.rdoc README.md</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"><code>rm <file></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rm README.rdoc</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"><code>rmdir <directory></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rmdir workspace/</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"><code>rm -rf <directory></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ rm -rf tmp/</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"><code>cat <file></code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>$ cat ~/.ssh/id_rsa.pub</code></p></td>
</tr>
</tbody>
</table>
<p>不管在本地环境,还是在云端 IDE 中,下一步都是使用<a href="#listing-rails-command">代码清单 1.3</a>中的命令创建第一个应用。注意,在这个代码清单中,我们明确指定了 Rails 版本(<code>4.2.2</code>)。这么做的目的是,确保使用<a href="#listing-installing-rails">代码清单 1.1</a>中安装的 Rails 版本来创建这个应用的文件夹结构。(执行<a href="#listing-rails-command">代码清单 1.3</a> 中的命令时,如果返回“Could not find ’railties”这样的错误,说明你没安装正确的 Rails 版本,再次确认你安装 Rails 时执行的命令和<a href="#listing-installing-rails">代码清单 1.1</a> 一模一样。)</p>
<div id="listing-rails-command" data-type="listing">
<h5><span class="title-label">代码清单 1.3</span>:执行 <code>rails new</code> 命令(明确指定版本号)</h5>
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span><span class="nb">cd</span> ~/workspace
</span><span class="hll"><span class="nv">$ </span>rails _4.2.2_ new hello_app
</span> create
create README.rdoc
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
create app/assets/javascripts/application.js
create app/assets/stylesheets/application.css
create app/controllers/application_controller.rb
.
.
.
create <span class="nb">test</span>/test_helper.rb
create tmp/cache
create tmp/cache/assets
create vendor/assets/javascripts
create vendor/assets/javascripts/.keep
create vendor/assets/stylesheets
create vendor/assets/stylesheets/.keep
run bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
Using rake 10.3.2
Using i18n 0.6.11
.
.
.
Your bundle is <span class="nb">complete</span>!
Use <span class="sb">`</span>bundle show <span class="o">[</span>gemname<span class="o">]</span><span class="sb">`</span> to see where a bundled gem is installed.
run bundle <span class="nb">exec </span>spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted
</pre></div>
</div>
<p>如代码清单 1.3 所示,执行 <code>rails new</code> 命令生成所有文件之后,会自动执行 <code>bundle install</code> 命令。我们会在 <a href="#bundler">1.3.1 节</a>说明这个命令的作用。</p>
<p>留意一下 <code>rails new</code> 命令创建的文件和文件夹。这个标准的文件夹结构(如<a href="#fig-directory-structure-rails">图 1.4</a>)是 Rails 的众多优势之一——让你从零开始快速的创建一个可运行的简单应用。而且,所有 Rails 应用都使用这种文件夹结构,所以阅读他人的代码时很快就能理清头绪。这些文件的作用如<a href="#table-rails-directory-structure">表 1.2</a> 所示,在本书的后续内容中将介绍其中大多数文件和文件夹。从 <a href="chapter5.html#the-asset-pipeline">5.2.1 节</a>开始,我们将介绍 <code>app/assets</code> 文件夹,它是 Asset Pipeline 的一部分。Asset Pipeline 把组织、部署 CSS 和 JavaScript 等资源文件变得异常简单。</p>
<div id="fig-directory-structure-rails" class="figure"><img src="images/chapter1/directory_structure_rails_3rd_edition.png" alt="directory structure rails 3rd edition" /><div class="figcaption"><span class="title-label">图 1.4</span>:新建 Rails 应用的文件夹结构</div></div>
<table id="table-rails-directory-structure" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 1.2</span>:Rails 文件夹结构简介</caption>
<colgroup>
<col style="width: 30%;" />
<col style="width: 70%;" />
</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>app/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的核心文件,包含模型、视图、控制器和辅助方法</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>app/assets</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的资源文件,例如层叠样式表(CSS)、JavaScript 和图片</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>bin/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">可执行二进制文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>config/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的配置</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>db/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">数据库相关的文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>doc/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的文档</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>lib/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">代码库模块文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>lib/assets</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">代码库的资源文件,例如 CSS、JavaScript 和图片</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>log/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的日志文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>public/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">公共(例如浏览器)可访问的文件,例如错误页面</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>bin/rails</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">生成代码、打开终端会话或启动本地服务器的程序</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>test/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用的测试</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>tmp/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">临时文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vendor/</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">第三方代码,例如插件和 gem</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vendor/assets</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">第三方资源文件,例如 CSS、JavaScript 和图片</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>README.rdoc</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用简介</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Rakefile</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">使用 <code>rake</code> 命令执行的实用任务</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Gemfile</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">应用所需的 gem</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Gemfile.lock</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">gem 列表,确保这个应用的副本使用相同版本的 gem</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>config.ru</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="http://rack.github.io/">Rack 中间件</a>的配置文件</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>.gitignore</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Git 忽略的文件</p></td>
</tr>
</tbody>
</table>
<section data-type="sect2" id="bundler">
<h2><span class="title-label">1.3.1</span> Bundler</h2>
<p>创建完一个新的 Rails 应用后,下一步是使用 Bundler 安装和包含该应用所需的 gem。在 <a href="#the-first-application">1.3 节</a>简单提到过,执行 <code>rails new</code> 命令时会自动运行 Bundler(通过 <code>bundle install</code> 命令)。不过这一节,我们要修改应用默认使用的 gem,然后再次运行 Bundler。首先,在文本编辑器中打开文件 <code>Gemfile</code>,虽然具体的版本号和内容或许有所不同,但大概与<a href="#listing-default-gemfile">代码清单 1.4</a> 和<a href="#fig-cloud9-gemfile">图 1.5</a> 差不多。(这个文件中的内容是 Ruby 代码,现在先不关心句法,<a href="chapter4.html#rails-flavored-ruby">第 4 章</a>会详细介绍 Ruby。)如果你没看到如图 1.5 所示的文件和文件夹,点击文件浏览器中的齿轮图标,然后选择“Refresh File Tree”(刷新文件树)。(如果没出现某个文件或文件夹,就可以刷新文件树。)</p>
<div id="listing-default-gemfile" data-type="listing">
<h5><span class="title-label">代码清单 1.4</span>:<code>hello_app</code> 中默认生成的 <code>Gemfile</code></h5>
<div class="highlight language-ruby"><pre><span class="n">source</span> <span class="s1">'https://rubygems.org'</span>
<span class="c1"># Bundle edge Rails instead: gem 'rails', github: 'rails/rails'</span>
<span class="n">gem</span> <span class="s1">'rails'</span><span class="p">,</span> <span class="s1">'4.2.2'</span>
<span class="c1"># Use sqlite3 as the database for Active Record</span>
<span class="n">gem</span> <span class="s1">'sqlite3'</span>
<span class="c1"># Use SCSS for stylesheets</span>
<span class="n">gem</span> <span class="s1">'sass-rails'</span><span class="p">,</span> <span class="s1">'~> 5.0'</span>
<span class="c1"># Use Uglifier as compressor for JavaScript assets</span>
<span class="n">gem</span> <span class="s1">'uglifier'</span><span class="p">,</span> <span class="s1">'>= 1.3.0'</span>
<span class="c1"># Use CoffeeScript for .js.coffee assets and views</span>
<span class="n">gem</span> <span class="s1">'coffee-rails'</span><span class="p">,</span> <span class="s1">'~> 4.1.0'</span>
<span class="c1"># See https://github.com/sstephenson/execjs#readme for more supported runtimes</span>
<span class="c1"># gem 'therubyracer', platforms: :ruby</span>
<span class="c1"># Use jquery as the JavaScript library</span>
<span class="n">gem</span> <span class="s1">'jquery-rails'</span>
<span class="c1"># Turbolinks makes following links in your web application faster. Read more:</span>
<span class="c1"># https://github.com/rails/turbolinks</span>
<span class="n">gem</span> <span class="s1">'turbolinks'</span>
<span class="c1"># Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder</span>
<span class="n">gem</span> <span class="s1">'jbuilder'</span><span class="p">,</span> <span class="s1">'~> 2.0'</span>
<span class="c1"># bundle exec rake doc:rails generates the API under doc/api.</span>
<span class="n">gem</span> <span class="s1">'sdoc'</span><span class="p">,</span> <span class="s1">'~> 0.4.0'</span><span class="p">,</span> <span class="ss">group</span><span class="p">:</span> <span class="ss">:doc</span>
<span class="c1"># Use ActiveModel has_secure_password</span>
<span class="c1"># gem 'bcrypt', '~> 3.1.7'</span>
<span class="c1"># Use Unicorn as the app server</span>
<span class="c1"># gem 'unicorn'</span>
<span class="c1"># Use Capistrano for deployment</span>
<span class="c1"># gem 'capistrano-rails', group: :development</span>
<span class="n">group</span> <span class="ss">:development</span><span class="p">,</span> <span class="ss">:test</span> <span class="k">do</span>
<span class="c1"># Call 'debugger' anywhere in the code to stop execution and get a</span>
<span class="c1"># debugger console</span>
<span class="n">gem</span> <span class="s1">'byebug'</span>
<span class="c1"># Access an IRB console on exceptions page and /console in development</span>
<span class="n">gem</span> <span class="s1">'web-console'</span><span class="p">,</span> <span class="s1">'~> 2.0'</span>
<span class="c1"># Spring speeds up development by keeping your application running in the</span>
<span class="c1"># background. Read more: https://github.com/rails/spring</span>
<span class="n">gem</span> <span class="s1">'spring'</span>
<span class="k">end</span>
</pre></div>
</div>
<div id="fig-cloud9-gemfile" class="figure"><img src="images/chapter1/cloud9_gemfile.png" alt="cloud9 gemfile" /><div class="figcaption"><span class="title-label">图 1.5</span>:在文本编辑器中打开默认生成的 <code>Gemfile</code></div></div>
<p>其中很多行代码都用 <code>#</code> 符号注释掉了,这些代码放在这是为了告诉你一些常用的 gem,也是为了展示 Bundler 的句法。现在,除了这些默认的 gem 之外,我们还不需要其他的 gem。</p>
<p>如果没在 <code>gem</code> 指令中指定版本号,Bundler 会自动最新版。下面就是一例:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="n">gem</span> <span class="s1">'sqlite3'</span>
</pre></div>
</div>
<p>还有两种常用的方法,用来指定 gem 版本的范围,一定程度上控制 Rails 使用的版本。首先看下面这行代码:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="n">gem</span> <span class="s1">'uglifier'</span><span class="p">,</span> <span class="s1">'>= 1.3.0'</span>
</pre></div>
</div>
<p>这行代码的意思是,安装版本号大于或等于 <code>1.3.0</code> 的最新版 <code>uglifier</code>(作用是压缩 Asset Pipeline 中的文件),就算是 <code>7.2</code> 版也会安装。第二种方法如下所示:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><span class="n">gem</span> <span class="s1">'coffee-rails'</span><span class="p">,</span> <span class="s1">'~> 4.0.0'</span>
</pre></div>
</div>
<p>这行代码的意思是,安装版本号大于 <code>4.0.0</code>,但小于 <code>4.1</code> 的 <code>coffee-rails</code>。也就是说,<code>>=</code> 符号的意思是始终安装最新版;<code>~> 4.0.0</code> 的意思是只安装补丁版本号变化的版本(例如从 <code>4.0.0</code> 到 <code>4.0.1</code>),而不安装次版本或主版本的更新(例如从 <code>4.0</code> 到 <code>4.1</code>)。不过,经验告诉我们,即使是补丁版本的升级也可能导致错误,所以在本教程中我们基本上会为所有的 gem 都指定精确的版本号。你可以使用任何 gem 的最新版本,还可以在 <code>Gemfile</code> 中使用 <code>~></code>(一般推荐有经验的用户使用),但事先提醒你,这可能会导致本教程开发的应用表现异常。</p>
<p>修改<a href="#listing-default-gemfile">代码清单 1.4</a> 中的 <code>Gemfile</code>,换用精确的版本号,得到的结果如<a href="#listing-gemfile-sqlite-version">代码清单 1.5</a> 所示。注意,借此机会我们还变动了 <code>sqlite3</code> 的位置,只在开发环境和测试环境(<a href="chapter7.html#debug-and-rails-environments">7.1.1 节</a>)中安装,避免和 Heroku 所用的数据库冲突(<a href="#deploying">1.5 节</a>)。</p>
<div id="listing-gemfile-sqlite-version" data-type="listing">
<h5><span class="title-label">代码清单 1.5</span>:每个 Ruby gem 都使用精确版本号的 <code>Gemfile</code></h5>
<div class="highlight language-ruby"><pre><span class="n">source</span> <span class="s1">'https://rubygems.org'</span>
<span class="n">gem</span> <span class="s1">'rails'</span><span class="p">,</span> <span class="s1">'4.2.2'</span>
<span class="n">gem</span> <span class="s1">'sass-rails'</span><span class="p">,</span> <span class="s1">'5.0.2'</span>
<span class="n">gem</span> <span class="s1">'uglifier'</span><span class="p">,</span> <span class="s1">'2.5.3'</span>
<span class="n">gem</span> <span class="s1">'coffee-rails'</span><span class="p">,</span> <span class="s1">'4.1.0'</span>
<span class="n">gem</span> <span class="s1">'jquery-rails'</span><span class="p">,</span> <span class="s1">'4.0.3'</span>
<span class="n">gem</span> <span class="s1">'turbolinks'</span><span class="p">,</span> <span class="s1">'2.3.0'</span>
<span class="n">gem</span> <span class="s1">'jbuilder'</span><span class="p">,</span> <span class="s1">'2.2.3'</span>
<span class="n">gem</span> <span class="s1">'sdoc'</span><span class="p">,</span> <span class="s1">'0.4.0'</span><span class="p">,</span> <span class="ss">group</span><span class="p">:</span> <span class="ss">:doc</span>
<span class="n">group</span> <span class="ss">:development</span><span class="p">,</span> <span class="ss">:test</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s1">'sqlite3'</span><span class="p">,</span> <span class="s1">'1.3.9'</span>
<span class="n">gem</span> <span class="s1">'byebug'</span><span class="p">,</span> <span class="s1">'3.4.0'</span>
<span class="n">gem</span> <span class="s1">'web-console'</span><span class="p">,</span> <span class="s1">'2.0.0.beta3'</span>
<span class="n">gem</span> <span class="s1">'spring'</span><span class="p">,</span> <span class="s1">'1.1.3'</span>
<span class="k">end</span>
</pre></div>
</div>
<p>把代码清单 1.5 中的内容写入应用的 <code>Gemfile</code> 文件之后,执行 <code>bundle install</code> 命令<sup>[<a id="fn-ref-7" href="#fn-7">7</a>]</sup>安装这些 gem:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span><span class="nb">cd </span>hello_app/
</span><span class="hll"><span class="nv">$ </span>bundle install
</span>Fetching <span class="nb">source </span>index <span class="k">for</span> https://rubygems.org/
.
.
.
</pre></div>
</div>
<p><code>bundle install</code> 命令可能要执行一会儿,不过结束后我们的应用就可以运行了。</p>
</section>
<section data-type="sect2" id="rails-server">
<h2><span class="title-label">1.3.2</span> <code>rails server</code></h2>
<p>运行完 <a href="#the-first-application">1.3 节</a>中的 <code>rails new</code> 命令和 <a href="#bundler">1.3.1 节</a> 中的 <code>bundle install</code> 命令之后,我们的应用就可以运行了,但是怎么运行呢?Rails 自带了一个命令行程序(或叫脚本),可以运行一个本地服务器,协助我们的开发工作。这个命令具体怎么执行,取决于你使用的环境:在本地系统中,直接执行 <code>rails server</code> 命令就行(<a href="#listing-local-server">代码清单 1.6</a>);而在 Cloud9 中,还要指定绑定的 IP 地址和<a href="http://en.wikipedia.org/wiki/TCP_and_UDP_port">端口号</a>,告诉 Rails 服务器外界可以通过哪个地址访问应用(<a href="#listing-cloud-server">代码清单 1.7</a>)。<sup>[<a id="fn-ref-8" href="#fn-8">8</a>]</sup>(Cloud9 使用特殊的环境变量 <code>$IP</code> 和 <code>$PORT</code> 动态指定 IP 地址和端口号。如果想查看这两个环境变量的值,可以在命令行中输入 <code>echo $IP</code> 和 <code>echo $PORT</code>。)如果系统提示缺少 JavaScript 运行时,访问 <a href="https://github.com/sstephenson/execjs">execjs 在 GitHub 中的项目主页</a>,查看解决方法。我非常推荐安装 <a href="http://nodejs.org/">Node.js</a>。</p>
<div id="listing-local-server" data-type="listing">
<h5><span class="title-label">代码清单 1.6</span>:在本地设备中运行 Rails 服务器</h5>
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span><span class="nb">cd</span> ~/workspace/hello_app/
</span><span class="hll"><span class="nv">$ </span>rails <span class="nv">server</span>
</span><span class="o">=</span>> Booting <span class="nv">WEBrick</span>
<span class="o">=</span>> Rails application starting on http://localhost:3000
<span class="o">=</span>> Run <span class="sb">`</span>rails server -h<span class="sb">`</span> <span class="k">for</span> more startup <span class="nv">options</span>
<span class="o">=</span>> Ctrl-C to shutdown server
</pre></div>
</div>
<div id="listing-cloud-server" data-type="listing">
<h5><span class="title-label">代码清单 1.7</span>:在云端 IDE 中运行 Rails 服务器</h5>
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span><span class="nb">cd</span> ~/workspace/hello_app/
</span><span class="hll"><span class="nv">$ </span>rails server -b <span class="nv">$IP</span> -p <span class="nv">$PORT</span>
</span><span class="o">=</span>> Booting <span class="nv">WEBrick</span>
<span class="o">=</span>> Rails application starting on http://0.0.0.0:8080
<span class="o">=</span>> Run <span class="sb">`</span>rails server -h<span class="sb">`</span> <span class="k">for</span> more startup <span class="nv">options</span>
<span class="o">=</span>> Ctrl-C to shutdown server
</pre></div>
</div>
<p>不管使用哪种环境,我都建议你在另一个终端选项卡中执行 <code>rails server</code> 命令,这样你就可以继续在第一个选项卡中执行其他命令了,如<a href="#fig-new-terminal-tab">图 1.6</a> 和<a href="#fig-rails-server-new-tab">图 1.7</a> 所示。(如果你已经在第一个选项卡中启动了服务器,可以按 Ctrl-C 键关闭服务器。)在本地环境中,在浏览器中打开 <a href="http://localhost:3000/">http://localhost:3000/</a>;在云端 IDE 中,打开“Share”(分享)面板,点击“Application”后的地址即可打开应用(如<a href="#fig-share-workspace">图 1.8</a>)。在这两种环境中,显示的页面应该都和<a href="#fig-riding-rails">图 1.9</a> 类似。</p>
<p>点击“About your application’s environment”可以查看应用的信息。你看到的版本号可能和我的不一样,但和<a href="#fig-riding-rails-environment">图 1.10</a> 差不多。当然了,从长远来看,我们不需要这个 Rails 默认页面,不过现在看到这个页面说明 Rails 可以正常运行了。我们会在<a href="#hello-world">1.3.4 节</a>删除这个页面,替换成我们自己写的首页。</p>
<div id="fig-new-terminal-tab" class="figure"><img src="images/chapter1/new_terminal_tab.png" alt="new terminal tab" /><div class="figcaption"><span class="title-label">图 1.6</span>:再打开一个终端选项卡</div></div>
<div id="fig-rails-server-new-tab" class="figure"><img src="images/chapter1/rails_server_new_tab.png" alt="rails server new tab" /><div class="figcaption"><span class="title-label">图 1.7</span>:在另一个选项卡中运行 Rails 服务器</div></div>
<div id="fig-share-workspace" class="figure"><img src="images/chapter1/share_workspace.png" alt="share workspace" /><div class="figcaption"><span class="title-label">图 1.8</span>:分享运行在云端工作空间中的本地服务器</div></div>
<div id="fig-riding-rails" class="figure"><img src="images/chapter1/riding_rails_3rd_edition.png" alt="riding rails 3rd edition" /><div class="figcaption"><span class="title-label">图 1.9</span>:执行 <code>rails server</code> 命令后看到的 Rails 默认页面</div></div>
<div id="fig-riding-rails-environment" class="figure"><img src="images/chapter1/riding_rails_environment_3rd_edition.png" alt="riding rails environment 3rd edition" /><div class="figcaption"><span class="title-label">图 1.10</span>:默认页面中显示应用的环境信息</div></div>
</section>
<section data-type="sect2" id="model-view-controller">
<h2><span class="title-label">1.3.3</span> 模型-视图-控制器</h2>
<p>在初期阶段,概览一下 Rails 应用的工作方式(<a href="#fig-mvc">图 1.11</a>)多少会有些帮助。你可能已经注意到了,在 Rails 应用的标准文件夹结构中有一个文件夹叫 <code>app/</code>(<a href="#fig-directory-structure-rails">图 1.4</a>),其中有三个子文件夹:<code>models</code>、<code>views</code> 和 <code>controllers</code>。这暗示 Rails 采用了“模型-视图-控制器”(简称 MVC)架构模式,这种模式把“域逻辑”(domain logic,也叫“业务逻辑”(business logic))与图形用户界面相关的输入和表现逻辑强制分开。在 Web 应用中,“域逻辑”一般是“用户”、“文章”和“商品”等数据模型,GUI 则是浏览器中的网页。</p>
<div id="fig-mvc" class="figure"><img src="images/chapter1/mvc_schematic.png" alt="mvc schematic" /><div class="figcaption"><span class="title-label">图 1.11</span>:MVC 架构图解</div></div>
<p>和 Rails 应用交互时,浏览器发出一个请求(request),Web 服务器收到这个请求之后将其传给 Rails 应用的控制器,由控制器决定下一步该做什么。某些情况下,控制器会立即渲染视图(view),生成 HTML,然后发送给浏览器。对于动态网站来说,更常见的是控制器和模型(model)交互。模型是一个 Ruby 对象,表示网站中的一个元素(例如一个用户),并且负责和数据库通信。和模型交互后,控制器再渲染视图,并把生成的 HTML 返回给浏览器。</p>
<p>如果你觉得这些内容有点抽象,不用担心,后面会经常讲到 MVC。在<a href="#hello-world">1.3.4 节</a>中,首次使用 MVC 架构编写应用;在 <a href="chapter2.html#mvc-in-action">2.2.2 节</a>中,会以一个应用为例较为深入地讨论 MVC;在最后那个演示应用中会使用完整的 MVC 架构。从 <a href="chapter3.html#static-pages">3.2 节</a>开始,介绍控制器和视图;从 <a href="chapter6.html#user-model">6.1 节</a>开始,介绍模型;<a href="chapter7.html#a-users-resource">7.1.2 节</a>则把这三部分放在一起使用。</p>
</section>
<section data-type="sect2" id="hello-world">
<h2><span class="title-label">1.3.4</span> Hello, world!</h2>
<p>接下来我们要对这个使用 MVC 框架开发的第一个应用做些小改动——添加一个控制器动作,渲染字符串“hello, world!”。(从 <a href="chapter2.html#mvc-in-action">2.2.2 节</a>开始会更深入的介绍控制器动作。)这一节的目的是,使用显示“hello, world!”的页面替换 Rails 默认的首页(<a href="#fig-riding-rails">图 1.9</a>)。</p>
<p>从“控制器动作”这个名字可以看出,动作在控制器中定义。我们要在 <code>ApplicationController</code> 中定义这个动作,并将其命名为 <code>hello</code>。其实,现在我们的应用只有 <code>ApplicationController</code> 这一个控制器。执行下面的命令可以验证这一点(从 <a href="chapter2.html#a-toy-app">第 2 章</a>开始,我们会创建自己的控制器。):</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>ls app/controllers/*_controller.rb
</pre></div>
</div>
<p><code>hello</code> 动作的定义体如<a href="#listing-hello-action">代码清单 1.8</a> 所示,调用 <code>render</code> 函数返回文本“hello, world!”。(现在先不管 Ruby 的句法,<a href="chapter4.html#rails-flavored-ruby">第 4 章</a>会详细介绍。)</p>
<div id="listing-hello-action" data-type="listing">
<h5><span class="title-label">代码清单 1.8</span>:在 <code>ApplicationController</code> 中添加 <code>hello</code> 动作</h5>
<div class="source-file">app/controllers/application_controller.rb</div>
<div class="highlight language-ruby"><pre><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="c1"># Prevent CSRF attacks by raising an exception.</span>
<span class="c1"># For APIs, you may want to use :null_session instead.</span>
<span class="n">protect_from_forgery</span> <span class="ss">with</span><span class="p">:</span> <span class="ss">:exception</span>
<span class="k">def</span> <span class="nf">hello</span>
<span class="hll"> <span class="n">render</span> <span class="ss">text</span><span class="p">:</span> <span class="s2">"hello, world!"</span>
</span> <span class="k">end</span>
<span class="k">end</span>
</pre></div>
</div>
<p>定义好返回所需字符串的动作之后,我们要告诉 Rails 使用这个动作,不再显示默认的首页(<a href="#fig-riding-rails-environment">图 1.10</a>)。为此,我们要修改 Rails 路由。路由在控制器之前(<a href="#fig-mvc">图 1.11</a>),决定浏览器发给应用的请求由哪个动作处理。(简单起见,<a href="#fig-mvc">图 1.11</a> 中省略了路由,从 <a href="chapter2.html#mvc-in-action">2.2.2 节</a>开始会详细介绍路由。)具体而言,我们要修改默认的首页,也就是根路由。这个路由决定根 URL 显示哪个页面。根 URL 是 http://www.example.com/ 这种形式,所以一般简化使用 /(斜线)表示。</p>
<p>如<a href="#listing-default-root-route">代码清单 1.9</a> 所示,Rails 应用的路由文件(<code>config/routes.rb</code>)中有一行注释,说明如何编写根路由。其中,“welcome”是控制器名,“index”是这个控制器中的动作名。去掉这行前面的 <code>#</code> 号,解除注释,这样根路由就可以定义了,然后再把内容替换成<a href="#listing-hello-root-route">代码清单 1.10</a> 中的代码,告诉 Rails 把根路由交给 <code>ApplicationController</code> 中的 <code>hello</code> 动作处理。(在 <a href="#conventions-in-this-book">1.1.2 节</a>说过,竖排的点号表示省略的代码,不要直接复制。)</p>
<div id="listing-default-root-route" data-type="listing">
<h5><span class="title-label">代码清单 1.9</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="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="c1"># You can have the root of your site routed with "root"</span>
<span class="hll"> <span class="c1"># root 'welcome#index'</span>
</span> <span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="k">end</span>
</pre></div>
</div>
<div id="listing-hello-root-route" data-type="listing">
<h5><span class="title-label">代码清单 1.10</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="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="c1"># You can have the root of your site routed with "root"</span>
<span class="hll"> <span class="n">root</span> <span class="s1">'application#hello'</span>
</span> <span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="k">end</span>
</pre></div>
</div>
<p>有了<a href="#listing-hello-action">代码清单 1.8</a> 和<a href="#listing-hello-root-route">代码清单 1.10</a> 中的代码,根路由就会按照我们的要求显示“hello, world!”了,如<a href="#fig-hello-world-hello-app">图 1.12</a> 所示。</p>
<div id="fig-hello-world-hello-app" class="figure"><img src="images/chapter1/hello_world_hello_app.png" alt="hello world hello app" /><div class="figcaption"><span class="title-label">图 1.12</span>:在浏览器中查看显示“hello, world!”的页面</div></div>
</section>
</section>
<section data-type="sect1" id="version-control-with-git">
<h1><span class="title-label">1.4</span> 使用 Git 做版本控制</h1>
<p>我们已经开发了一个可以运行的 Rails 应用,接下来要花点时间来做一件事。虽然这件事不是必须的,但是经验丰富的软件开发者都认为这是最基本的事情,即把应用的源代码纳入版本控制。版本控制系统可以跟踪项目中代码的变化,便于和他人协作,如果出现问题(例如不小心删除了文件)还可以回滚到以前的版本。每个专业级软件开发者都应该学习使用版本控制系统。</p>
<p>版本控制系统种类很多,Rails 社区基本都使用 <a href="http://git-scm.com/">Git</a>。Git 由 Linus Torvalds 开发,最初目的是存储 Linux 内核代码。Git 相关的知识很多,本书只会介绍一些皮毛。网络上有很多免费的资料,我特别推荐 Scott Chacon 写的《<a href="http://git-scm.com/book">Pro Git</a>》。<sup>[<a id="fn-ref-9" href="#fn-9">9</a>]</sup>之所以强烈推荐使用 Git 做版本控制,不仅因为 Rails 社区都在用,还因为使用 Git 分享代码更简单(<a href="#bitbucket">1.4.3 节</a>),而且也便于应用的部署(<a href="#deploying">1.5 节</a>)。</p>
<section data-type="sect2" id="installation-and-setup">
<h2><span class="title-label">1.4.1</span> 安装和设置</h2>
<p><a href="#development-environment">1.2.1 节</a>推荐使用的云端 IDE 默认已经集成 Git,不用再安装。如果你没使用云端 IDE,可以参照 <a href="http://installrails.com/">InstallRails.com</a> 中的说明,在自己的系统中安装 Git。</p>
<section data-type="sect3" id="first-time-system-setup">
<h3>第一次运行前要做的系统设置</h3>
<p>使用Git 前,要做一些一次性设置。这些设置对整个系统都有效,因此一台电脑只需设置一次:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git config --global user.name <span class="s2">"Your Name"</span>
<span class="nv">$ </span>git config --global user.email [email protected]
<span class="nv">$ </span>git config --global push.default matching
<span class="nv">$ </span>git config --global alias.co checkout
</pre></div>
</div>
<p>注意,在 Git 配置中设定的名字和电子邮件地址会在所有公开的仓库中显示。(前两个设置必须做。第三个设置是为了向前兼容未来的 Git 版本。第四个设置是可选的,如果设置了,就可以使用 <code>co</code> 代替 <code>checkout</code> 命令。为了最大程度上兼容没有设置 <code>co</code> 的系统,本书仍将继续使用全名 <code>checkout</code>,不过在现实中我基本都用 <code>git co</code>。)</p>
</section>
<section data-type="sect3" id="first-time-repository-setup">
<h3>第一次使用仓库前要做的设置</h3>
<p>下面的步骤每次新建仓库时都要执行。首先进入第一个应用的根目录,然后初始化一个新仓库:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git init
Initialized empty Git repository in /home/ubuntu/workspace/hello_app/.git/
</pre></div>
</div>
<p>然后执行 <code>git add -A</code> 命令,把项目中的所有文件都放到仓库中:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git add -A
</pre></div>
</div>
<p>这个命令会把当前目录中的所有文件都放到仓库中,但是匹配特殊文件 <code>.gitignore</code> 中模式的文件除外。<code>rails new</code> 命令会自动生成一个适用于 Rails 项目的 <code>.gitignore</code> 文件,而且你还可以添加其他模式。<sup>[<a id="fn-ref-10" href="#fn-10">10</a>]</sup></p>
<p>加入仓库的文件一开始位于“暂存区”(staging area),这一区用于存放待提交的内容。执行 <code>status</code> 命令可以查看暂存区中有哪些文件:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git status
On branch master
Initial commit
Changes to be committed:
<span class="o">(</span>use <span class="s2">"git rm --cached <file>..."</span> to unstage<span class="o">)</span>
new file: .gitignore
new file: Gemfile
new file: Gemfile.lock
new file: README.rdoc
new file: Rakefile
.
.
.
</pre></div>
</div>
<p>(显示的内容很多,所以我使用竖排点号省略了一些内容。)</p>
<p>如果想告诉 Git 保留这些改动,可以使用 <code>commit</code> 命令:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git commit -m <span class="s2">"Initialize repository"</span>
<span class="o">[</span>master <span class="o">(</span>root-commit<span class="o">)</span> df0a62f<span class="o">]</span> Initialize repository
.
.
.
</pre></div>
</div>
<p>旗标 <code>-m</code> 的意思是为这次提交添加一个说明。如果没指定 <code>-m</code> 旗标,Git 会打开系统默认使用的编辑器,让你在其中输入说明。(本书所有的示例都会使用 <code>-m</code> 旗标。)</p>
<p>有一点很重要要注意:Git 提交只发生在本地,也就是说只在执行提交操作的设备中存储内容。<a href="#branch-edit-commit-merge">1.4.4 节</a>会介绍如何把改动推送(使用 <code>git push</code> 命令)到远程仓库中。</p>
<p>顺便说一下,可以使用 <code>log</code> 命令查看提交的历史:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <[email protected]>
Date: Wed August <span class="m">20</span> 19:44:43 <span class="m">2014</span> +0000
Initialize repository
</pre></div>
</div>
<p>如果仓库的提交历史很多,可能需要输入 <code>q</code> 退出。</p>
</section>
</section>
<section data-type="sect2" id="what-good-does-git-do-you">
<h2><span class="title-label">1.4.2</span> 使用 Git 有什么好处</h2>
<p>如果以前从未用过版本控制,现在可能不完全明白版本控制的好处。那我举个例子说明一下吧。假如你不小心做了某个操作,例如把重要的 <code>app/controllers/</code> 文件夹删除了:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>ls app/controllers/
application_controller.rb concerns/
<span class="nv">$ </span>rm -rf app/controllers/
<span class="nv">$ </span>ls app/controllers/
ls: app/controllers/: No such file or directory
</pre></div>
</div>
<p>我们用 Unix 中的 <code>ls</code> 命令列出 <code>app/controllers/</code> 文件夹里的内容,然后用 <code>rm</code> 命令删除这个文件夹。旗标 <code>-rf</code> 的意思是“强制递归”,无需明确征求同意就递归删除所有文件、文件夹和子文件夹等。</p>
<p>查看一下状态,看看发生了什么:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git status
On branch master
Changed but not updated:
<span class="o">(</span>use <span class="s2">"git add/rm <file>..."</span> to update what will be committed<span class="o">)</span>
<span class="o">(</span>use <span class="s2">"git checkout -- <file>..."</span> to discard changes in working directory<span class="o">)</span>
deleted: app/controllers/application_controller.rb
no changes added to commit <span class="o">(</span>use <span class="s2">"git add"</span> and/or <span class="s2">"git commit -a"</span><span class="o">)</span>
</pre></div>
</div>
<p>可以看出,删除了一个文件。但是这个改动只发生在“工作树”中,还未提交到仓库。所以,我们可以使用 <code>checkout</code> 命令,并指定 <code>-f</code> 旗标,强制撤销这次改动:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git checkout -f
<span class="nv">$ </span>git status
<span class="c"># On branch master</span>
nothing to commit <span class="o">(</span>working directory clean<span class="o">)</span>
<span class="nv">$ </span>ls app/controllers/
application_controller.rb concerns/
</pre></div>
</div>
<p>删除的文件夹和文件又回来了,这下放心了!</p>
</section>
<section data-type="sect2" id="bitbucket">
<h2><span class="title-label">1.4.3</span> Bitbucket</h2>
<p>我们已经把项目纳入 Git 版本控制系统了,接下来可以把代码推送到 <a href="http://www.bitbucket.com/">Bitbucket</a> 中。Bitbucket 是一个专门用来托管和分享 Git 仓库的网站。(本书前几版使用 <a href="http://www.github.com/">GitHub</a>,换用 Bitbucket 的原因参见<a href="#aside-github-bitbucket">旁注 1.4</a>。)在 Bitbucket 中放一份 Git 仓库的副本有两个目的:其一,对代码做个完整备份(包括所有提交历史);其二,便于以后协作。</p>
<div data-type="sidebar" id="aside-github-bitbucket" class="sidebar">
<h5><span class="title-label">旁注 1.4</span>:GitHub 和 Bitbucket</h5>
<p>目前,托管 Git 仓库最受欢迎的网站是 GitHub 和 Bitbucket。这两个网站有很多相似之处:都可托管仓库,也可以协作,而且浏览和搜索仓库很方便。但二者之间有个重要的区别(对本书而言):GitHub 为开源项目提供无限量的免费仓库,但私有仓库收费;而 Bitbucket 提供了无限量的私有仓库,仅当协作者超过一定数量时才收费。所以,选择哪个网站,取决于具体的需求。</p>
<p>本书前几版使用 GitHub,因为它对开源项目来说有很多好用的功能,但我越来越关注安全,所以推荐所有 Web 应用都放在私有仓库中。因为 Web 应用的仓库中可能包含潜在的敏感信息,例如密钥和密码,可能会威胁到使用这份代码的网站的安全。当然,这类信息也有安全的处理方法,但是容易出错,而且需要很多专业知识。</p>
<p>本书开发的演示应用可以安全地公开,但这只是特例,不能推广。因此,为了尽量提高安全,我们不能冒险,还是默认就使用私有仓库保险。既然 GitHub 对私有仓库收费,而 Bitbucket 提供了不限量的免费私有仓库,就我们的需求来说,Bitbucket 比 Github 更合适。</p>
</div>
<p>Bitbucket 的使用方法很简单:</p>
<ol class="arabic">
<li>
<p>如果没有账户,先<a href="https://bitbucket.org/account/signup/">注册一个 Bitbucket 账户</a>;</p>
</li>
<li>
<p>把<a href="https://en.wikipedia.org/wiki/Public-key_cryptography">公钥</a>复制到剪切板。云端 IDE 用户可以使用 <code>cat</code> 命令查看公钥,如<a href="#listing-cat-public-key">代码清单 1.11</a> 所示,然后选中公钥,复制。如果你在自己的系统中,执行<a href="#listing-cat-public-key">代码清单 1.11</a> 中的命令后没有输出,请参照“<a href="https://confluence.atlassian.com/display/BITBUCKET/How+to+install+a+public+key+on+your+Bitbucket+account;jsessionid=C5CF09B1641344FE876045B35D720C94.node1">如何在你的 Bitbucket 账户中设定公钥</a>”;</p>
</li>
<li>
<p>点击右上角的头像,选择“Manage account”(管理账户),然后点击“SSH keys”(SSH 密钥),如<a href="#fig-add-public-key">图 1.13</a> 所示。</p>
</li>
</ol>
<div id="fig-add-public-key" class="figure"><img src="images/chapter1/add_public_key.png" alt="add public key" /><div class="figcaption"><span class="title-label">图 1.13</span>:添加 SSH 公钥</div></div>
<div id="listing-cat-public-key" data-type="listing">
<h5><span class="title-label">代码清单 1.11</span>:使用 <code>cat</code> 命令打印公钥</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>cat ~/.ssh/id_rsa.pub
</pre></div>
</div>
<p>添加公钥之后,点击“Create”(创建)按钮,<a href="https://bitbucket.org/repo/create">新建一个仓库</a>,如<a href="#fig-create_first_repository">图 1.14</a> 所示。填写项目的信息时,记得要选中“This is a private repository”(这是私有仓库)。填完后点击“Create repository”(创建仓库)按钮,然后按照“Command line > I have an existing project”(命令行 > 现有项目)下面的说明操作,如<a href="#listing-bitbucket-add-push">代码清单 1.12</a> 所示。(如果与代码清单 1.12 不同,可能是公钥没正确添加,我建议你再试一次。)推送仓库时,如果询问“Are you sure you want to continue connecting (yes/no)?”(确定继续连接吗?),输入“yes”。</p>
<div id="listing-bitbucket-add-push" data-type="listing">
<h5><span class="title-label">代码清单 1.12</span>:添加 Bitbucket,然后推送仓库</h5>
<div class="highlight language-sh"><pre><span class="nv">$ </span>git remote add origin [email protected]:<username>/hello_app.git
<span class="nv">$ </span>git push -u origin --all <span class="c"># 第一次推送仓库</span>
</pre></div>
</div>
<p>这段代码的意思是,先告诉 Git,你想添加 Bitbucket,作为这个仓库的源,然后再把仓库推送到这个远端的源。(别管 <code>-u</code> 旗标的意思,如果好奇,可以搜索“git set upstream”。)当然了,你要把 <code><username></code> 换成你自己的用户名。例如,我运行的命令是:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git remote add origin [email protected]:mhartl/hello_app.git
</pre></div>
</div>
<p>推送完毕后,在 Bitbucket 中会显示一个 <code>hello_app</code> 仓库的页面。在这个页面中可以浏览文件、查看完整的提交信息,除此之外还有很多其他功能(如<a href="#fig-bitbucket-repository-page">图 1.15</a> 所示)。</p>
<div id="fig-create_first_repository" class="figure"><img src="images/chapter1/create_first_repository_bitbucket.png" alt="create first repository bitbucket" /><div class="figcaption"><span class="title-label">图 1.14</span>:在 Bitbucket 中创建存放这个应用的仓库</div></div>
<div id="fig-bitbucket-repository-page" class="figure"><img src="images/chapter1/bitbucket_repository_page.png" alt="bitbucket repository page" /><div class="figcaption"><span class="title-label">图 1.15</span>:一个 Bitbucket 仓库的页面</div></div>
</section>
<section data-type="sect2" id="branch-edit-commit-merge">
<h2><span class="title-label">1.4.4</span> 分支,编辑,提交,合并</h2>
<p>如果你跟着 <a href="#bitbucket">1.4.3 节</a>中的步骤做,可能注意到了,Bitbucket 没有自动识别仓库中的 <code>README.rdoc</code> 文件,而在仓库的首页提醒没有 README 文件,如<a href="#fig-bitbucket-no-readme">图 1.16</a> 所示。这说明 <code>rdoc</code> 格式不常见,所以 Bitbucket 不支持。其实,我以及我认识的几乎所有人都使用 Markdown 格式。这一节,我们要把 <code>README.rdoc</code> 文件改成 <code>README.md</code>,顺便还要在其中添加一些针对本书的内容。在这个过程中,我们将首次演示我推荐在 Git 中使用的工作流程,即“分支,编辑,提交,合并”。<sup>[<a id="fn-ref-11" href="#fn-11">11</a>]</sup></p>
<div id="fig-bitbucket-no-readme" class="figure"><img src="images/chapter1/bitbucket_no_readme.png" alt="bitbucket no readme" /><div class="figcaption"><span class="title-label">图 1.16</span>:Bitbucket 提示没有 README 文件</div></div>
<section data-type="sect3" id="branch">
<h3>分支</h3>
<p>Git 中的分支功能很强大。分支是对仓库的高效复制,在分支中所做的改动(或许是实验性质的)不会影响父级文件。大多数情况下,父级仓库是 <code>master</code> 分支。我们可以使用 <code>checkout</code> 命令,并指定 <code>-b</code> 旗标,创建一个新“主题分支”(topic branch):</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span>git checkout -b modify-README
</span>Switched to a new branch <span class="s1">'modify-README'</span>
<span class="hll"><span class="nv">$ </span>git branch
</span> master
* modify-README
</pre></div>
</div>
<p>其中,第二个命令 <code>git branch</code> 的作用是列出所有本地分支。星号(<code>*</code>)表示当前所在的分支。注意,<code>git checkout -b modify-README</code> 命令先创建一个新分支,然后再切换到这个新分支——<code>modify-README</code> 分支前面的星号证明了这一点。(如果你在 <a href="#version-control-with-git">1.4 节</a>中设置了别名 <code>co</code>,就要使用 <code>git co -b modify-README</code>。)</p>
<p>只有多个开发者协作开发一个项目时,才能看出分支的全部价值。<sup>[<a id="fn-ref-12" href="#fn-12">12</a>]</sup>如果只有一个开发者,分支也有作用。一般情况下,要把主题分支的改动和主分支隔离开,这样即便搞砸了,随时都可以切换到主分支,然后删除主题分支,丢掉改动。本节末尾会介绍具体做法。</p>
<p>顺便说一下,像这种小改动,我一般不会新建分支。现在我这么做是为了让你养成好习惯。</p>
</section>
<section data-type="sect3" id="edit">
<h3>编辑</h3>
<p>创建主题分支后,我们要编辑 README 文件,让其更好地描述我们的项目。较之默认的 RDoc 格式,我更喜欢 <a href="http://daringfireball.net/projects/markdown/">Markdown 标记语言</a>。如果文件扩展名是 <code>.md</code>,Bitbucket 会自动排版其中的内容。首先,使用 Git 提供的 Unix <code>mv</code> 命令修改文件名:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="nv">$ </span>git mv README.rdoc README.md
</pre></div>
</div>
<p>然后把<a href="#listing-new-readme">代码清单 1.13</a> 中的内容写入 <code>README.md</code>。</p>
<div id="listing-new-readme" data-type="listing">
<h5><span class="title-label">代码清单 1.13</span>:新 <code>README</code> 文件,<code>README.md</code></h5>
<div class="highlight language-text"><pre># Ruby on Rails Tutorial: "hello, world!"
This is the first application for the
[*Ruby on Rails Tutorial*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/).
</pre></div>
</div>
</section>
<section data-type="sect3" id="commit">
<h3>提交</h3>
<p>编辑后,查看一下该分支的状态:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span>git status
</span>On branch modify-README
Changes to be committed:
<span class="o">(</span>use <span class="s2">"git reset HEAD <file>..."</span> to unstage<span class="o">)</span>
renamed: README.rdoc -> README.md
Changes not staged <span class="k">for</span> commit:
<span class="o">(</span>use <span class="s2">"git add <file>..."</span> to update what will be committed<span class="o">)</span>
<span class="o">(</span>use <span class="s2">"git checkout -- <file>..."</span> to discard changes in working directory<span class="o">)</span>
modified: README.md
</pre></div>
</div>
<p>这里,我们本可以使用“<a href="#first-time-repository-setup">第一次使用仓库前要做的设置</a>”一节用过的 <code>git add -A</code>,但是 <code>git commit</code> 提供了 <code>-a</code> 旗标,可以直接提交现有文件中的全部改动(以及使用 <code>git mv</code> 新建的文件,对 Git 来说这不算新文件):</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span>git commit -a -m <span class="s2">"Improve the README file"</span>
</span><span class="m">2</span> files changed, <span class="m">5</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">243</span> deletions<span class="o">(</span>-<span class="o">)</span>
delete mode <span class="m">100644</span> README.rdoc
create mode <span class="m">100644</span> README.md
</pre></div>
</div>
<p>使用 <code>-a</code> 旗标一定要小心,千万别误用了。如果上次提交之后项目中添加了新文件,应该使用 <code>git add -A</code>,先告诉 Git 新增了文件。</p>
<p>注意,我们使用现在时(严格来说是祈使语气)编写提交消息。Git 把提交当做一系列补丁,在这种情况下,说明现在做了什么比说明过去做了什么要更合理。而且这种用法和 Git 命令生成的提交信息相配。详情参阅《<a href="https://github.com/blog/926-shiny-new-commit-styles">Shiny new commit styles</a>》。</p>
</section>
<section data-type="sect3" id="merge">
<h3>合并</h3>
<p>我们已经改完了,现在可以把结果合并到主分支了:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span>git checkout master
</span>Switched to branch <span class="s1">'master'</span>
<span class="hll"><span class="nv">$ </span>git merge modify-README
</span>Updating 34f06b7..2c92bef
Fast forward
README.rdoc <span class="p">|</span> <span class="m">243</span> --------------------------------------------------
README.md <span class="p">|</span> <span class="m">5</span> +
<span class="m">2</span> files changed, <span class="m">5</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">243</span> deletions<span class="o">(</span>-<span class="o">)</span>
delete mode <span class="m">100644</span> README.rdoc
create mode <span class="m">100644</span> README.md
</pre></div>
</div>
<p>注意,Git 命令的输出中经常会出现 <code>34f06b7</code> 这样的字符串,这是 Git 内部对仓库的指代。你得到的输出结果不会和我的一模一样,但大致相同。</p>
<p>合并之后,我们可以清理一下分支——如果不用这个主题分支了,可以使用 <code>git branch -d</code> 命令将其删除:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="hll"><span class="nv">$ </span>git branch -d modify-README
</span>Deleted branch modify-README <span class="o">(</span>was 2c92bef<span class="o">)</span>.
</pre></div>
</div>
<p>这一步可做可不做,其实一般都会留着这个主题分支,这样就可以在两个分支之间来回切换,并在合适的时候把改动合并到主分支中。</p>
<p>前面提过,还可以使用 <code>git branch -D</code> 命令放弃主题分支中的改动:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><span class="c"># 仅作演示之用,如果没搞砸,千万别这么做</span>
<span class="nv">$ </span>git checkout -b topic-branch
<span class="nv">$ </span><really screw up the branch>
<span class="nv">$ </span>git add -A
<span class="nv">$ </span>git commit -a -m <span class="s2">"Major screw up"</span>
<span class="nv">$ </span>git checkout master
<span class="nv">$ </span>git branch -D topic-branch
</pre></div>