-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgetting_started.html
1493 lines (1322 loc) · 92.6 KB
/
getting_started.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 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Rails 入门 — Ruby on Rails 指南</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
更多内容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">综览</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下载</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">源码</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">视频</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首页">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首页</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目录</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="L">
<dt>入门</dt>
<dd><a href="getting_started.html">Rails 入门</a></dd>
<dt>模型</dt>
<dd><a href="active_record_basics.html">Active Record 基础</a></dd>
<dd><a href="active_record_migrations.html">Active Record 数据库迁移</a></dd>
<dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
<dd><a href="association_basics.html">Active Record 关联</a></dd>
<dd><a href="active_record_querying.html">Active Record 查询</a></dd>
<dt>视图</dt>
<dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
<dd><a href="form_helpers.html">Action View 表单帮助方法</a></dd>
<dt>控制器</dt>
<dd><a href="action_controller_overview.html">Action Controller 简介</a></dd>
<dd><a href="routing.html">Rails 路由全解</a></dd>
</dl>
<dl class="R">
<dt>深入</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
<dd><a href="i18n.html">Rails 国际化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
<dd><a href="active_job_basics.html">Active Job 基础</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">调试 Rails 程序</a></dd>
<dd><a href="configuring.html">设置 Rails 程序</a></dd>
<dd><a href="command_line.html">Rails 命令行</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>扩展 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客制与新建 Rails 产生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 应用程式模版</a></dd>
<dt>贡献 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件准则</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a></dd>
<dt>维护方针</dt>
<dd><a href="maintenance_policy.html">维护方针</a></dd>
<dt>发布记</dt>
<dd><a href="upgrading_ruby_on_rails.html">升级 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
</dl>
</div>
</li>
<!-- <li><a class="nav-item" href="//github.com/docrails-tw/wiki">参与翻译</a></li> -->
<li><a class="nav-item" href="https://github.com/ruby-china/guides/blob/master/CONTRIBUTING.md">贡献</a></li>
<li><a class="nav-item" href="credits.html">致谢</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目录</option>
<optgroup label="入门">
<option value="getting_started.html">Rails 入门</option>
</optgroup>
<optgroup label="模型">
<option value="active_record_basics.html">Active Record 基础</option>
<option value="active_record_migrations.html">Active Record 数据库迁移</option>
<option value="active_record_validations.html">Active Record 数据验证</option>
<option value="active_record_callbacks.html">Active Record 回调</option>
<option value="association_basics.html">Active Record 关联</option>
<option value="active_record_querying.html">Active Record 查询</option>
</optgroup>
<optgroup label="视图">
<option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
<option value="form_helpers.html">Action View 表单帮助方法</option>
</optgroup>
<optgroup label="控制器">
<option value="action_controller_overview.html">Action Controller 简介</option>
<option value="routing.html">Rails 路由全解</option>
</optgroup>
<optgroup label="深入">
<option value="active_support_core_extensions.html">Active Support 核心扩展</option>
<option value="i18n.html">Rails 国际化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基础</option>
<option value="active_job_basics.html">Active Job 基础</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">调试 Rails 程序</option>
<option value="configuring.html">设置 Rails 程序</option>
<option value="command_line.html">Rails 命令行</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="扩展 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客制与新建 Rails 产生器</option>
<option value="rails_application_templates.html">Rails 应用程式模版</option>
</optgroup>
<optgroup label="贡献 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件准则</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</option>
</optgroup>
<optgroup label="维护方针">
<option value="maintenance_policy.html">维护方针</option>
</optgroup>
<optgroup label="发布记">
<option value="upgrading_ruby_on_rails.html">升级 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide" />
<div id="feature">
<div class="wrapper">
<h2>Rails 入门</h2><p>本文介绍如何开始使用 Ruby on Rails。</p><p>读完本文,你将学到:</p>
<ul>
<li>如何安装 Rails,新建 Rails 程序,如何连接数据库;</li>
<li>Rails 程序的基本文件结构;</li>
<li>MVC(模型,视图,控制器)和 REST 架构的基本原理;</li>
<li>如何快速生成 Rails 程序骨架;</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6">前提条件</a></li>
<li><a href="#rails-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F">Rails 是什么?</a></li>
<li>
<a href="#%E6%96%B0%E5%BB%BA-rails-%E7%A8%8B%E5%BA%8F">新建 Rails 程序</a>
<ul>
<li><a href="#%E5%AE%89%E8%A3%85-rails">安装 Rails</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA-blog-%E7%A8%8B%E5%BA%8F">创建 Blog 程序</a></li>
</ul>
</li>
<li>
<a href="#hello,-rails-bang">Hello, Rails!</a>
<ul>
<li><a href="#%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1%E5%99%A8">启动服务器</a></li>
<li><a href="#%E6%98%BE%E7%A4%BA%E2%80%9Chello,-rails-bang%E2%80%9D">显示“Hello, Rails!”</a></li>
<li><a href="#%E8%AE%BE%E7%BD%AE%E7%A8%8B%E5%BA%8F%E7%9A%84%E9%A6%96%E9%A1%B5">设置程序的首页</a></li>
</ul>
</li>
<li>
<a href="#%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8">开始使用</a>
<ul>
<li><a href="#%E6%8C%96%E5%9C%B0%E5%9F%BA">挖地基</a></li>
<li><a href="#%E9%A6%96%E4%B8%AA%E8%A1%A8%E5%8D%95">首个表单</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA%E6%96%87%E7%AB%A0">创建文章</a></li>
<li><a href="#%E5%88%9B%E5%BB%BA-article-%E6%A8%A1%E5%9E%8B">创建 Article 模型</a></li>
<li><a href="#%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB">运行迁移</a></li>
<li><a href="#%E5%9C%A8%E6%8E%A7%E5%88%B6%E5%99%A8%E4%B8%AD%E4%BF%9D%E5%AD%98%E6%95%B0%E6%8D%AE">在控制器中保存数据</a></li>
<li><a href="#%E6%98%BE%E7%A4%BA%E6%96%87%E7%AB%A0">显示文章</a></li>
<li><a href="#%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E6%96%87%E7%AB%A0">列出所有文章</a></li>
<li><a href="#%E6%B7%BB%E5%8A%A0%E9%93%BE%E6%8E%A5">添加链接</a></li>
<li><a href="#%E6%B7%BB%E5%8A%A0%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81">添加数据验证</a></li>
<li><a href="#%E6%9B%B4%E6%96%B0%E6%96%87%E7%AB%A0">更新文章</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E5%8E%BB%E6%8E%89%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E4%BB%A3%E7%A0%81">使用局部视图去掉视图中的重复代码</a></li>
<li><a href="#%E5%88%A0%E9%99%A4%E6%96%87%E7%AB%A0">删除文章</a></li>
</ul>
</li>
<li>
<a href="#%E6%B7%BB%E5%8A%A0%E7%AC%AC%E4%BA%8C%E4%B8%AA%E6%A8%A1%E5%9E%8B">添加第二个模型</a>
<ul>
<li><a href="#%E7%94%9F%E6%88%90%E6%A8%A1%E5%9E%8B">生成模型</a></li>
<li><a href="#%E6%A8%A1%E5%9E%8B%E5%85%B3%E8%81%94">模型关联</a></li>
<li><a href="#%E6%B7%BB%E5%8A%A0%E8%AF%84%E8%AE%BA%E7%9A%84%E8%B7%AF%E7%94%B1">添加评论的路由</a></li>
<li><a href="#%E7%94%9F%E6%88%90%E6%8E%A7%E5%88%B6%E5%99%A8">生成控制器</a></li>
</ul>
</li>
<li>
<a href="#%E9%87%8D%E6%9E%84">重构</a>
<ul>
<li><a href="#%E6%B8%B2%E6%9F%93%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E9%9B%86%E5%90%88">渲染局部视图中的集合</a></li>
<li><a href="#%E6%B8%B2%E6%9F%93%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E8%A1%A8%E5%8D%95">渲染局部视图中的表单</a></li>
</ul>
</li>
<li>
<a href="#%E5%88%A0%E9%99%A4%E8%AF%84%E8%AE%BA">删除评论</a>
<ul>
<li><a href="#%E5%88%A0%E9%99%A4%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1">删除关联对象</a></li>
</ul>
</li>
<li>
<a href="#%E5%AE%89%E5%85%A8">安全</a>
<ul>
<li><a href="#%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8%AF%81">基本认证</a></li>
<li><a href="#%E5%85%B6%E4%BB%96%E5%AE%89%E5%85%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">其他安全注意事项</a></li>
</ul>
</li>
<li><a href="#%E6%8E%A5%E4%B8%8B%E6%9D%A5%E5%81%9A%E4%BB%80%E4%B9%88">接下来做什么</a></li>
<li><a href="#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98">常见问题</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="前提条件">1 前提条件</h3><p>本文针对想从零开始开发 Rails 程序的初学者,不需要预先具备任何的 Rails 使用经验。不过,为了能顺利阅读,还是需要事先安装好一些软件:</p>
<ul>
<li>
<a href="https://www.ruby-lang.org/en/downloads">Ruby</a> 1.9.3 及以上版本</li>
<li>包管理工具 <a href="https://rubygems.org">RubyGems</a>,随 Ruby 1.9+ 安装。想深入了解 RubyGems,请阅读 <a href="http://guides.rubygems.org">RubyGems 指南</a>
</li>
<li>
<a href="https://www.sqlite.org">SQLite3</a> 数据库</li>
</ul>
<p>Rails 是使用 Ruby 语言开发的网页程序框架。如果之前没接触过 Ruby,学习 Rails 可要深下一番功夫。网上有很多资源可以学习 Ruby:</p>
<ul>
<li><a href="https://www.ruby-lang.org/zh_cn/documentation/">Ruby 语言官方网站</a></li>
<li><a href="http://resrc.io/list/10/list-of-free-programming-books/#ruby">reSRC 列出的免费编程书籍</a></li>
</ul>
<p>记住,某些资源虽然很好,但是针对 Ruby 1.8,甚至 1.6 编写的,所以没有介绍一些 Rails 日常开发会用到的句法。</p><h3 id="rails-是什么?">2 Rails 是什么?</h3><p>Rails 是使用 Ruby 语言编写的网页程序开发框架,目的是为开发者提供常用组件,简化网页程序的开发。只需编写较少的代码,就能实现其他编程语言或框架难以企及的功能。经验丰富的 Rails 程序员会发现,Rails 让程序开发变得更有乐趣。</p><p>Rails 有自己的一套规则,认为问题总有最好的解决方法,而且建议使用最好的方法,有些情况下甚至不推荐使用其他替代方案。学会如何按照 Rails 的思维开发,能极大提高开发效率。如果坚持在 Rails 开发中使用其他语言中的旧思想,尝试使用别处学来的编程模式,开发过程就不那么有趣了。</p><p>Rails 哲学包含两大指导思想:</p>
<ul>
<li>
<strong>不要自我重复(DRY):</strong> DRY 是软件开发中的一个原则,“系统中的每个功能都要具有单一、准确、可信的实现。”。不重复表述同一件事,写出的代码才能更易维护,更具扩展性,也更不容易出问题。</li>
<li>
<strong>多约定,少配置:</strong> Rails 为网页程序的大多数需求都提供了最好的解决方法,而且默认使用这些约定,不用在长长的配置文件中设置每个细节。</li>
</ul>
<h3 id="新建-rails-程序">3 新建 Rails 程序</h3><p>阅读本文时,最佳方式是跟着一步一步操作,如果错过某段代码或某个步骤,程序就可能出错,所以请一步一步跟着做。</p><p>本文会新建一个名为 <code>blog</code> 的 Rails 程序,这是一个非常简单的博客。在开始开发程序之前,要确保已经安装了 Rails。</p><div class="info"><p>文中的示例代码使用 <code>$</code> 表示命令行提示符,你的提示符可能修改过,所以会不一样。在 Windows 中,提示符可能是 <code>c:\source_code></code>。</p></div><h4 id="安装-rails">3.1 安装 Rails</h4><p>打开命令行:在 Mac OS X 中打开 Terminal.app,在 Windows 中选择“运行”,然后输入“cmd.exe”。下文中所有以 <code>$</code> 开头的代码,都要在命令行中运行。先确认是否安装了 Ruby 最新版:</p><div class="info"><p>有很多工具可以帮助你快速在系统中安装 Ruby 和 Ruby on Rails。Windows 用户可以使用 <a href="http://railsinstaller.org">Rails Installer</a>,Mac OS X 用户可以使用 <a href="https://github.com/tokaido/tokaidoapp">Tokaido</a>。</p></div><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ ruby -v
ruby 2.1.2p95
</pre>
</div>
<p>如果你还没安装 Ruby,请访问 <a href="https://www.ruby-lang.org/en/downloads/">ruby-lang.org</a>,找到针对所用系统的安装方法。</p><p>很多类 Unix 系统都自带了版本尚新的 SQLite3。Windows 等其他操作系统的用户可以在 <a href="https://www.sqlite.org">SQLite3 的网站</a>上找到安装说明。然后,确认是否在 PATH 中:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ sqlite3 --version
</pre>
</div>
<p>命令行应该回显版本才对。</p><p>安装 Rails,请使用 RubyGems 提供的 <code>gem install</code> 命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ gem install rails
</pre>
</div>
<p>要检查所有软件是否都正确安装了,可以执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails --version
</pre>
</div>
<p>如果显示的结果类似“Rails 4.2.0”,那么就可以继续往下读了。</p><h4 id="创建-blog-程序">3.2 创建 Blog 程序</h4><p>Rails 提供了多个被称为“生成器”的脚本,可以简化开发,生成某项操作需要的所有文件。其中一个是新程序生成器,生成一个 Rails 程序骨架,不用自己一个一个新建文件。</p><p>打开终端,进入有写权限的文件夹,执行以下命令生成一个新程序:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails new blog
</pre>
</div>
<p>这个命令会在文件夹 <code>blog</code> 中新建一个 Rails 程序,然后执行 <code>bundle install</code> 命令安装 <code>Gemfile</code> 中列出的 gem。</p><div class="info"><p>执行 <code>rails new -h</code> 可以查看新程序生成器的所有命令行选项。</p></div><p>生成 <code>blog</code> 程序后,进入该文件夹:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ cd blog
</pre>
</div>
<p><code>blog</code> 文件夹中有很多自动生成的文件和文件夹,组成一个 Rails 程序。本文大部分时间都花在 <code>app</code> 文件夹上。下面简单介绍默认生成的文件和文件夹的作用:</p>
<table>
<thead>
<tr>
<th>文件/文件夹</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>app/</td>
<td>存放程序的控制器、模型、视图、帮助方法、邮件和静态资源文件。本文主要关注的是这个文件夹。</td>
</tr>
<tr>
<td>bin/</td>
<td>存放运行程序的 <code>rails</code> 脚本,以及其他用来部署或运行程序的脚本。</td>
</tr>
<tr>
<td>config/</td>
<td>设置程序的路由,数据库等。详情参阅“<a href="/configuring.html">设置 Rails 程序</a>”一文。</td>
</tr>
<tr>
<td>config.ru</td>
<td>基于 Rack 服务器的程序设置,用来启动程序。</td>
</tr>
<tr>
<td>db/</td>
<td>存放当前数据库的模式,以及数据库迁移文件。</td>
</tr>
<tr>
<td>Gemfile, Gemfile.lock</td>
<td>这两个文件用来指定程序所需的 gem 依赖件,用于 Bundler gem。关于 Bundler 的详细介绍,请访问 <a href="http://bundler.io">Bundler 官网</a>。</td>
</tr>
<tr>
<td>lib/</td>
<td>程序的扩展模块。</td>
</tr>
<tr>
<td>log/</td>
<td>程序的日志文件。</td>
</tr>
<tr>
<td>public/</td>
<td>唯一对外开放的文件夹,存放静态文件和编译后的资源文件。</td>
</tr>
<tr>
<td>Rakefile</td>
<td>保存并加载可在命令行中执行的任务。任务在 Rails 的各组件中定义。如果想添加自己的任务,不要修改这个文件,把任务保存在 <code>lib/tasks</code> 文件夹中。</td>
</tr>
<tr>
<td>README.rdoc</td>
<td>程序的简单说明。你应该修改这个文件,告诉其他人这个程序的作用,如何安装等。</td>
</tr>
<tr>
<td>test/</td>
<td>单元测试,固件等测试用文件。详情参阅“<a href="/testing.html">测试 Rails 程序</a>”一文。</td>
</tr>
<tr>
<td>tmp/</td>
<td>临时文件,例如缓存,PID,会话文件。</td>
</tr>
<tr>
<td>vendor/</td>
<td>存放第三方代码。经常用来放第三方 gem。</td>
</tr>
</tbody>
</table>
<h3 id="hello,-rails-bang">4 Hello, Rails!</h3><p>首先,我们来添加一些文字,在页面中显示。为了能访问网页,要启动程序服务器。</p><h4 id="启动服务器">4.1 启动服务器</h4><p>现在,新建的 Rails 程序已经可以正常运行。要访问网站,需要在开发电脑上启动服务器。请在 <code>blog</code> 文件夹中执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails server
</pre>
</div>
<div class="info"><p>把 CoffeeScript 编译成 JavaScript 需要 JavaScript 运行时,如果没有运行时,会报错,提示没有 <code>execjs</code>。Mac OS X 和 Windows 一般都提供了 JavaScript 运行时。Rails 生成的 <code>Gemfile</code> 中,安装 <code>therubyracer</code> gem 的代码被注释掉了,如果需要使用这个 gem,请把前面的注释去掉。在 JRuby 中推荐使用 <code>therubyracer</code>。在 JRuby 中生成的 <code>Gemfile</code> 已经包含了这个 gem。所有支持的运行时参见 <a href="https://github.com/sstephenson/execjs#readme">ExecJS</a>。</p></div><p>上述命令会启动 WEBrick,这是 Ruby 内置的服务器。要查看程序,请打开一个浏览器窗口,访问 <a href="http://localhost:3000">http://localhost:3000</a>。应该会看到默认的 Rails 信息页面:</p><p><img src="images/getting_started/rails_welcome.png" alt="欢迎使用页面"></p><div class="info"><p>要想停止服务器,请在命令行中按 Ctrl+C 键。服务器成功停止后回重新看到命令行提示符。在大多数类 Unix 系统中,包括 Mac OS X,命令行提示符是 <code>$</code> 符号。在开发模式中,一般情况下无需重启服务器,修改文件后,服务器会自动重新加载。</p></div><p>“欢迎使用”页面是新建 Rails 程序后的“冒烟测试”:确保程序设置正确,能顺利运行。你可以点击“About your application's environment”链接查看程序所处环境的信息。</p><h4 id="显示“hello,-rails-bang”">4.2 显示“Hello, Rails!”</h4><p>要在 Rails 中显示“Hello, Rails!”,需要新建一个控制器和视图。</p><p>控制器用来接受向程序发起的请求。路由决定哪个控制器会接受到这个请求。一般情况下,每个控制器都有多个路由,对应不同的动作。动作用来提供视图中需要的数据。</p><p>视图的作用是,以人类能看懂的格式显示数据。有一点要特别注意,数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来。默认情况下,视图使用 eRuby(嵌入式 Ruby)语言编写,经由 Rails 解析后,再发送给用户。</p><p>控制器可用控制器生成器创建,你要告诉生成器,我想要个名为“welcome”的控制器和一个名为“index”的动作,如下所示:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate controller welcome index
</pre>
</div>
<p>运行上述命令后,Rails 会生成很多文件,以及一个路由。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
create app/controllers/welcome_controller.rb
route get 'welcome/index'
invoke erb
create app/views/welcome
create app/views/welcome/index.html.erb
invoke test_unit
create test/controllers/welcome_controller_test.rb
invoke helper
create app/helpers/welcome_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/welcome.js.coffee
invoke scss
create app/assets/stylesheets/welcome.css.scss
</pre>
</div>
<p>在这些文件中,最重要的当然是控制器,位于 <code>app/controllers/welcome_controller.rb</code>,以及视图,位于 <code>app/views/welcome/index.html.erb</code>。</p><p>使用文本编辑器打开 <code>app/views/welcome/index.html.erb</code> 文件,删除全部内容,写入下面这行代码:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<h1>Hello, Rails!</h1>
</pre>
</div>
<h4 id="设置程序的首页">4.3 设置程序的首页</h4><p>我们已经创建了控制器和视图,现在要告诉 Rails 在哪个地址上显示“Hello, Rails!”。这里,我们希望访问根地址 <a href="http://localhost:3000">http://localhost:3000</a> 时显示。但是现在显示的还是欢迎页面。</p><p>我们要告诉 Rails 真正的首页是什么。</p><p>在编辑器中打开 <code>config/routes.rb</code> 文件。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.application.routes.draw do
get 'welcome/index'
# The priority is based upon order of creation:
# first created -> highest priority.
#
# You can have the root of your site routed with "root"
# root 'welcome#index'
#
# ...
</pre>
</div>
<p>这是程序的路由文件,使用特殊的 DSL(domain-specific language,领域专属语言)编写,告知 Rails 请求应该发往哪个控制器和动作。文件中有很多注释,举例说明如何定义路由。其中有一行说明了如何指定控制器和动作设置网站的根路由。找到以 <code>root</code> 开头的代码行,去掉注释,变成这样:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
root 'welcome#index'
</pre>
</div>
<p><code>root 'welcome#index'</code> 告知 Rails,访问程序的根路径时,交给 <code>welcome</code> 控制器中的 <code>index</code> 动作处理。<code>get 'welcome/index'</code> 告知 Rails,访问 <a href="http://localhost:3000/welcome/index">http://localhost:3000/welcome/index</a> 时,交给 <code>welcome</code> 控制器中的 <code>index</code> 动作处理。<code>get 'welcome/index'</code> 是运行 <code>rails generate controller welcome index</code> 时生成的。</p><p>如果生成控制器时停止了服务器,请再次启动(<code>rails server</code>),然后在浏览器中访问 <a href="http://localhost:3000">http://localhost:3000</a>。你会看到之前写入 <code>app/views/welcome/index.html.erb</code> 文件的“Hello, Rails!”,说明新定义的路由把根目录交给 <code>WelcomeController</code> 的 <code>index</code> 动作处理了,而且也正确的渲染了视图。</p><div class="info"><p>关于路由的详细介绍,请阅读“<a href="/routing.html">Rails 路由全解</a>”一文。</p></div><h3 id="开始使用">5 开始使用</h3><p>前文已经介绍如何创建控制器、动作和视图,下面我们来创建一些更实质的功能。</p><p>在博客程序中,我们要创建一个新“资源”。资源是指一系列类似的对象,比如文章,人和动物。</p><p>资源可以被创建、读取、更新和删除,这些操作简称 CRUD。</p><p>Rails 提供了一个 <code>resources</code> 方法,可以声明一个符合 REST 架构的资源。创建文章资源后,<code>config/routes.rb</code> 文件的内容如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.application.routes.draw do
resources :articles
root 'welcome#index'
end
</pre>
</div>
<p>执行 <code>rake routes</code> 任务,会看到定义了所有标准的 REST 动作。输出结果中各列的意义稍后会说明,现在只要留意 <code>article</code> 的单复数形式,这在 Rails 中有特殊的含义。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rake routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
</pre>
</div>
<p>下一节,我们会加入新建文章和查看文章的功能。这两个操作分别对应于 CRUD 的 C 和 R,即创建和读取。新建文章的表单如下所示:</p><p><img src="images/getting_started/new_article.png" alt="新建文章表单"></p><p>表单看起来很简陋,不过没关系,后文会加入更多的样式。</p><h4 id="挖地基">5.1 挖地基</h4><p>首先,程序中要有个页面用来新建文章。一个比较好的选择是 <code>/articles/new</code>。这个路由前面已经定义了,可以访问。打开 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a> ,会看到如下的路由错误:</p><p><img src="images/getting_started/routing_error_no_controller.png" alt="路由错误,常量 ArticlesController 未初始化"></p><p>产生这个错误的原因是,没有定义用来处理该请求的控制器。解决这个问题的方法很简单,执行下面的命令创建名为 <code>ArticlesController</code> 的控制器即可:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails g controller articles
</pre>
</div>
<p>打开刚生成的 <code>app/controllers/articles_controller.rb</code> 文件,会看到一个几乎没什么内容的控制器:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController < ApplicationController
end
</pre>
</div>
<p>控制器就是一个类,继承自 <code>ApplicationController</code>。在这个类中定义的方法就是控制器的动作。动作的作用是处理文章的 CRUD 操作。</p><div class="note"><p>在 Ruby 中,方法分为 <code>public</code>、<code>private</code> 和 <code>protected</code> 三种,只有 <code>public</code> 方法才能作为控制器的动作。详情参阅 <a href="http://www.ruby-doc.org/docs/ProgrammingRuby/">Programming Ruby</a> 一书。</p></div><p>现在刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,会看到一个新错误:</p><p><img src="images/getting_started/unknown_action_new_for_articles.png" alt="ArticlesController 控制器不知如何处理 new 动作"></p><p>这个错误的意思是,在刚生成的 <code>ArticlesController</code> 控制器中找不到 <code>new</code> 动作。因为在生成控制器时,除非指定要哪些动作,否则不会生成,控制器是空的。</p><p>手动创建动作只需在控制器中定义一个新方法。打开 <code>app/controllers/articles_controller.rb</code> 文件,在 <code>ArticlesController</code> 类中,定义 <code>new</code> 方法,如下所示:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController < ApplicationController
def new
end
end
</pre>
</div>
<p>在 <code>ArticlesController</code> 中定义 <code>new</code> 方法后,再刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,看到的还是个错误:</p><p><img src="images/getting_started/template_is_missing_articles_new.png" alt="找不到 articles/new 所用模板"></p><p>产生这个错误的原因是,Rails 希望这样的常规动作有对应的视图,用来显示内容。没有视图可用,Rails 就报错了。</p><p>在上图中,最后一行被截断了,我们来看一下完整的信息:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
</pre>
</div>
<p>这行信息还挺长,我们来看一下到底是什么意思。</p><p>第一部分说明找不到哪个模板,这里,丢失的是 <code>articles/new</code> 模板。Rails 首先会寻找这个模板,如果找不到,再找名为 <code>application/new</code> 的模板。之所以这么找,是因为 <code>ArticlesController</code> 继承自 <code>ApplicationController</code>。</p><p>后面一部分是个 Hash。<code>:locale</code> 表示要找哪国语言模板,默认是英语(<code>"en"</code>)。<code>:format</code> 表示响应使用的模板格式,默认为 <code>:html</code>,所以 Rails 要寻找一个 HTML 模板。<code>:handlers</code> 表示用来处理模板的程序,HTML 模板一般使用 <code>:erb</code>,XML 模板使用 <code>:builder</code>,<code>:coffee</code> 用来把 CoffeeScript 转换成 JavaScript。</p><p>最后一部分说明 Rails 在哪里寻找模板。在这个简单的程序里,模板都存放在一个地方,复杂的程序可能存放在多个位置。</p><p>让这个程序正常运行,最简单的一种模板是 <code>app/views/articles/new.html.erb</code>。模板文件的扩展名是关键所在:第一个扩展名是模板的类型,第二个扩展名是模板的处理程序。Rails 会尝试在 <code>app/views</code> 文件夹中寻找名为 <code>articles/new</code> 的模板。这个模板的类型只能是 <code>html</code>,处理程序可以是 <code>erb</code>、<code>builder</code> 或 <code>coffee</code>。因为我们要编写一个 HTML 表单,所以使用 <code>erb</code>。所以这个模板文件应该命名为 <code>articles/new.html.erb</code>,还要放在 <code>app/views</code> 文件夹中。</p><p>新建文件 <code>app/views/articles/new.html.erb</code>,写入如下代码:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<h1>New Article</h1>
</pre>
</div>
<p>再次刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,可以看到页面中显示了一个标头。现在路由、控制器、动作和视图都能正常运行了。接下来要编写新建文章的表单了。</p><h4 id="首个表单">5.2 首个表单</h4><p>要在模板中编写表单,可以使用“表单构造器”。Rails 中常用的表单构造器是 <code>form_for</code>。在 <code>app/views/articles/new.html.erb</code> 文件中加入以下代码:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= form_for :article do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
</pre>
</div>
<p>现在刷新页面,会看到上述代码生成的表单。在 Rails 中编写表单就是这么简单!</p><p>调用 <code>form_for</code> 方法时,要指定一个对象。在上面的表单中,指定的是 <code>:article</code>。这个对象告诉 <code>form_for</code>,这个表单是用来处理哪个资源的。在 <code>form_for</code> 方法的块中,<code>FormBuilder</code> 对象(用 <code>f</code> 表示)创建了两个标签和两个文本字段,一个用于文章标题,一个用于文章内容。最后,在 <code>f</code> 对象上调用 <code>submit</code> 方法,创建一个提交按钮。</p><p>不过这个表单还有个问题。如果查看这个页面的源码,会发现表单 <code>action</code> 属性的值是 <code>/articles/new</code>。这就是问题所在,因为其指向的地址就是现在这个页面,而这个页面是用来显示新建文章表单的。</p><p>要想转到其他地址,就要使用其他的地址。这个问题可使用 <code>form_for</code> 方法的 <code>:url</code> 选项解决。在 Rails 中,用来处理新建资源表单提交数据的动作是 <code>create</code>,所以表单应该转向这个动作。</p><p>修改 <code>app/views/articles/new.html.erb</code> 文件中的 <code>form_for</code>,改成这样:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= form_for :article, url: articles_path do |f| %>
</pre>
</div>
<p>这里,我们把 <code>:url</code> 选项的值设为 <code>articles_path</code> 帮助方法。要想知道这个方法有什么作用,我们要回过头再看一下 <code>rake routes</code> 的输出:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rake routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
</pre>
</div>
<p><code>articles_path</code> 帮助方法告诉 Rails,对应的地址是 <code>/articles</code>,默认情况下,这个表单会向这个路由发起 <code>POST</code> 请求。这个路由对应于 <code>ArticlesController</code> 控制器的 <code>create</code> 动作。</p><p>表单写好了,路由也定义了,现在可以填写表单,然后点击提交按钮新建文章了。请实际操作一下。提交表单后,会看到一个熟悉的错误:</p><p><img src="images/getting_started/unknown_action_create_for_articles.png" alt="ArticlesController 控制器不知如何处理 create 动作"></p><p>解决这个错误,要在 <code>ArticlesController</code> 控制器中定义 <code>create</code> 动作。</p><h4 id="创建文章">5.3 创建文章</h4><p>要解决前一节出现的错误,可以在 <code>ArticlesController</code> 类中定义 <code>create</code> 方法。在 <code>app/controllers/articles_controller.rb</code> 文件中 <code>new</code> 方法后面添加以下代码:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController < ApplicationController
def new
end
def create
end
end
</pre>
</div>
<p>然后再次提交表单,会看到另一个熟悉的错误:找不到模板。现在暂且不管这个错误。<code>create</code> 动作的作用是把新文章保存到数据库中。</p><p>提交表单后,其中的字段以参数的形式传递给 Rails。这些参数可以在控制器的动作中使用,完成指定的操作。要想查看这些参数的内容,可以把 <code>create</code> 动作改成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
render plain: params[:article].inspect
end
</pre>
</div>
<p><code>render</code> 方法接受一个简单的 Hash 为参数,这个 Hash 的键是 <code>plain</code>,对应的值为 <code>params[:article].inspect</code>。<code>params</code> 方法表示通过表单提交的参数,返回 <code>ActiveSupport::HashWithIndifferentAccess</code> 对象,可以使用字符串或者 Symbol 获取键对应的值。现在,我们只关注通过表单提交的参数。</p><p>如果现在再次提交表单,不会再看到找不到模板错误,而是会看到类似下面的文字:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
{"title"=>"First article!", "text"=>"This is my first article."}
</pre>
</div>
<p><code>create</code> 动作把表单提交的参数显示出来了。不过这么做没什么用,看到了参数又怎样,什么都没发生。</p><h4 id="创建-article-模型">5.4 创建 Article 模型</h4><p>在 Rails 中,模型的名字使用单数,对应的数据表名使用复数。Rails 提供了一个生成器用来创建模型,大多数 Rails 开发者创建模型时都会使用。创建模型,请在终端里执行下面的命令:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate model Article title:string text:text
</pre>
</div>
<p>这个命令告知 Rails,我们要创建 <code>Article</code> 模型,以及一个字符串属性 <code>title</code> 和文本属性 <code>text</code>。这两个属性会自动添加到 <code>articles</code> 数据表中,映射到 <code>Article</code> 模型。</p><p>执行这个命令后,Rails 会生成一堆文件。现在我们只关注 <code>app/models/article.rb</code> 和 <code>db/migrate/20140120191729_create_articles.rb</code>(你得到的文件名可能有点不一样)这两个文件。后者用来创建数据库结构,下一节会详细说明。</p><div class="info"><p>Active Record 很智能,能自动把数据表中的字段映射到模型的属性上。所以无需在 Rails 的模型中声明属性,因为 Active Record 会自动映射。</p></div><h4 id="运行迁移">5.5 运行迁移</h4><p>如前文所述,<code>rails generate model</code> 命令会在 <code>db/migrate</code> 文件夹中生成一个数据库迁移文件。迁移是一个 Ruby 类,能简化创建和修改数据库结构的操作。Rails 使用 rake 任务运行迁移,修改数据库结构后还能撤销操作。迁移的文件名中有个时间戳,这样能保证迁移按照创建的时间顺序运行。</p><p><code>db/migrate/20140120191729_create_articles.rb</code>(还记得吗,你的迁移文件名可能有点不一样)文件的内容如下所示:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.string :title
t.text :text
t.timestamps
end
end
end
</pre>
</div>
<p>在这个迁移中定义了一个名为 <code>change</code> 的方法,在运行迁移时执行。<code>change</code> 方法中定义的操作都是可逆的,Rails 知道如何撤销这次迁移操作。运行迁移后,会创建 <code>articles</code> 表,以及一个字符串字段和文本字段。同时还会创建两个时间戳字段,用来跟踪记录的创建时间和更新时间。</p><div class="info"><p>关于迁移的详细说明,请参阅“<a href="active_record_migrations.html">Active Record 数据库迁移</a>”一文。</p></div><p>然后,使用 rake 命令运行迁移:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rake db:migrate
</pre>
</div>
<p>Rails 会执行迁移操作,告诉你创建了 <code>articles</code> 表。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
== CreateArticles: migrating ==================================================
-- create_table(:articles)
-> 0.0019s
== CreateArticles: migrated (0.0020s) =========================================
</pre>
</div>
<div class="note"><p>因为默认情况下,程序运行在开发环境中,所以相关的操作应用于 <code>config/database.yml</code> 文件中 <code>development</code> 区域设置的数据库上。如果想在其他环境中运行迁移,必须在命令中指明:<code>rake db:migrate RAILS_ENV=production</code>。</p></div><h4 id="在控制器中保存数据">5.6 在控制器中保存数据</h4><p>再回到 <code>ArticlesController</code> 控制器,我们要修改 <code>create</code> 动作,使用 <code>Article</code> 模型把数据保存到数据库中。打开 <code>app/controllers/articles_controller.rb</code> 文件,把 <code>create</code> 动作修改成这样:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
@article = Article.new(params[:article])
@article.save
redirect_to @article
end
</pre>
</div>
<p>在 Rails 中,每个模型可以使用各自的属性初始化,自动映射到数据库字段上。<code>create</code> 动作中的第一行就是这个目的(还记得吗,<code>params[:article]</code> 就是我们要获取的属性)。<code>@article.save</code> 的作用是把模型保存到数据库中。保存完后转向 <code>show</code> 动作。稍后再编写 <code>show</code> 动作。</p><div class="info"><p>后文会看到,<code>@article.save</code> 返回一个布尔值,表示保存是否成功。</p></div><p>再次访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,填写表单,还差一步就能创建文章了,会看到一个错误页面:</p><p><img src="images/getting_started/forbidden_attributes_for_new_article.png" alt="新建文章时禁止使用属性"></p><p>Rails 提供了很多安全防范措施保证程序的安全,你所看到的错误就是因为违反了其中一个措施。这个防范措施叫做“健壮参数”,我们要明确地告知 Rails 哪些参数可在控制器中使用。这里,我们想使用 <code>title</code> 和 <code>text</code> 参数。请把 <code>create</code> 动作修改成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
@article = Article.new(article_params)
@article.save
redirect_to @article
end
private
def article_params
params.require(:article).permit(:title, :text)
end
</pre>
</div>
<p>看到 <code>permit</code> 方法了吗?这个方法允许在动作中使用 <code>title</code> 和 <code>text</code> 属性。</p><div class="info"><p>注意,<code>article_params</code> 是私有方法。这种用法可以防止攻击者把修改后的属性传递给模型。关于健壮参数的更多介绍,请阅读<a href="http://weblog.rubyonrails.org/2012/3/21/strong-parameters/">这篇文章</a>。</p></div><h4 id="显示文章">5.7 显示文章</h4><p>现在再次提交表单,Rails 会提示找不到 <code>show</code> 动作。这个提示没多大用,我们还是先添加 <code>show</code> 动作吧。</p><p>我们在 <code>rake routes</code> 的输出中看到,<code>show</code> 动作的路由是:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
article GET /articles/:id(.:format) articles#show
</pre>
</div>
<p><code>:id</code> 的意思是,路由期望接收一个名为 <code>id</code> 的参数,在这个例子中,就是文章的 ID。</p><p>和前面一样,我们要在 <code>app/controllers/articles_controller.rb</code> 文件中添加 <code>show</code> 动作,以及相应的视图文件。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def show
@article = Article.find(params[:id])
end
</pre>
</div>
<p>有几点要注意。我们调用 <code>Article.find</code> 方法查找想查看的文章,传入的参数 <code>params[:id]</code> 会从请求中获取 <code>:id</code> 参数。我们还把文章对象存储在一个实例变量中(以 <code>@</code> 开头的变量),只有这样,变量才能在视图中使用。</p><p>然后,新建 <code>app/views/articles/show.html.erb</code> 文件,写入下面的代码:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
</pre>
</div>
<p>做了以上修改后,就能真正的新建文章了。访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,自己试试。</p><p><img src="images/getting_started/show_action_for_articles.png" alt="显示文章"></p><h4 id="列出所有文章">5.8 列出所有文章</h4><p>我们还要列出所有文章,对应的路由是:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
articles GET /articles(.:format) articles#index
</pre>
</div>
<p>在 <code>app/controllers/articles_controller.rb</code> 文件中,为 <code>ArticlesController</code> 控制器添加 <code>index</code> 动作:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def index
@articles = Article.all
end
</pre>
</div>
<p>然后编写这个动作的视图,保存为 <code>app/views/articles/index.html.erb</code>:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>Listing articles</h1>
<table>
<tr>
<th>Title</th>
<th>Text</th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
</tr>
<% end %>
</table>
</pre>
</div>
<p>现在访问 <a href="http://localhost:3000/articles">http://localhost:3000/articles</a>,会看到已经发布的文章列表。</p><h4 id="添加链接">5.9 添加链接</h4><p>至此,我们可以新建、显示、列出文章了。下面我们添加一些链接,指向这些页面。</p><p>打开 <code>app/views/welcome/index.html.erb</code> 文件,改成这样:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>
</pre>
</div>
<p><code>link_to</code> 是 Rails 内置的视图帮助方法之一,根据提供的文本和地址创建超链接。这上面这段代码中,地址是文章列表页面。</p><p>接下来添加到其他页面的链接。先在 <code>app/views/articles/index.html.erb</code> 中添加“New Article”链接,放在 <code><table></code> 标签之前:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= link_to 'New article', new_article_path %>
</pre>
</div>
<p>点击这个链接后,会转向新建文章的表单页面。</p><p>然后在 <code>app/views/articles/new.html.erb</code> 中添加一个链接,位于表单下面,返回到 <code>index</code> 动作:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= form_for :article do |f| %>
...
<% end %>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<p>最后,在 <code>app/views/articles/show.html.erb</code> 模板中添加一个链接,返回 <code>index</code> 动作,这样用户查看某篇文章后就可以返回文章列表页面了:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<div class="info"><p>如果要链接到同一个控制器中的动作,不用指定 <code>:controller</code> 选项,因为默认情况下使用的就是当前控制器。</p></div><div class="info"><p>在开发模式下(默认),每次请求 Rails 都会重新加载程序,因此修改之后无需重启服务器。</p></div><h4 id="添加数据验证">5.10 添加数据验证</h4><p>模型文件,比如 <code>app/models/article.rb</code>,可以简单到只有这两行代码:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article < ActiveRecord::Base
end
</pre>
</div>
<p>文件中没有多少代码,不过请注意,<code>Article</code> 类继承自 <code>ActiveRecord::Base</code>。Active Record 提供了很多功能,包括:基本的数据库 CRUD 操作,数据验证,复杂的搜索功能,以及多个模型之间的关联。</p><p>Rails 为模型提供了很多方法,用来验证传入的数据。打开 <code>app/models/article.rb</code> 文件,修改成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
end
</pre>
</div>
<p>添加的这段代码可以确保每篇文章都有一个标题,而且至少有五个字符。在模型中可以验证数据是否满足多种条件,包括:字段是否存在、是否唯一,数据类型,以及关联对象是否存在。“<a href="/active_record_validations.html">Active Record 数据验证</a>”一文会详细介绍数据验证。</p><p>添加数据验证后,如果把不满足验证条件的文章传递给 <code>@article.save</code>,会返回 <code>false</code>。打开 <code>app/controllers/articles_controller.rb</code> 文件,会发现,我们还没在 <code>create</code> 动作中检查 <code>@article.save</code> 的返回结果。如果保存失败,应该再次显示表单。为了实现这种功能,请打开 <code>app/controllers/articles_controller.rb</code> 文件,把 <code>new</code> 和 <code>create</code> 动作改成:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
</pre>
</div>
<p>在 <code>new</code> 动作中添加了一个实例变量 <code>@article</code>。稍后你会知道为什么要这么做。</p><p>注意,在 <code>create</code> 动作中,如果保存失败,调用的是 <code>render</code> 方法而不是 <code>redirect_to</code> 方法。用 <code>render</code> 方法才能在保存失败后把 <code>@article</code> 对象传给 <code>new</code> 动作的视图。渲染操作和表单提交在同一次请求中完成;而 <code>redirect_to</code> 会让浏览器发起一次新请求。</p><p>刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,提交一个没有标题的文章,Rails 会退回这个页面,但这种处理方法没多少用,你要告诉用户哪儿出错了。为了实现这种功能,请在 <code>app/views/articles/new.html.erb</code> 文件中检测错误消息:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= form_for :article, url: articles_path do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<p>我们添加了很多代码,使用 <code>@article.errors.any?</code> 检查是否有错误,如果有错误,使用 <code>@article.errors.full_messages</code> 显示错误。</p><p><code>pluralize</code> 是 Rails 提供的帮助方法,接受一个数字和字符串作为参数。如果数字比 1 大,字符串会被转换成复数形式。</p><p>在 <code>new</code> 动作中加入 <code>@article = Article.new</code> 的原因是,如果不这么做,在视图中 <code>@article</code> 的值就是 <code>nil</code>,调用 <code>@article.errors.any?</code> 时会发生错误。</p><div class="info"><p>Rails 会自动把出错的表单字段包含在一个 <code>div</code> 中,并为其添加了一个 class:<code>field_with_errors</code>。我们可以定义一些样式,凸显出错的字段。</p></div><p>再次访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>,尝试发布一篇没有标题的文章,会看到一个很有用的错误提示。</p><p><img src="images/getting_started/form_with_errors.png" alt="出错的表单"></p><h4 id="更新文章">5.11 更新文章</h4><p>我们已经说明了 CRUD 中的 CR 两种操作。下面进入 U 部分,更新文章。</p><p>首先,要在 <code>ArticlesController</code> 中添加 <code>edit</code> 动作:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def edit
@article = Article.find(params[:id])
end
</pre>
</div>
<p>视图中要添加一个类似新建文章的表单。新建 <code>app/views/articles/edit.html.erb</code> 文件,写入下面的代码:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>Editing article</h1>
<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<p>这里的表单指向 <code>update</code> 动作,现在还没定义,稍后会添加。</p><p><code>method: :patch</code> 选项告诉 Rails,提交这个表单时使用 <code>PATCH</code> 方法发送请求。根据 REST 架构,更新资源时要使用 HTTP <code>PATCH</code> 方法。</p><p><code>form_for</code> 的第一个参数可以是对象,例如 <code>@article</code>,把对象中的字段填入表单。如果传入一个和实例变量(<code>@article</code>)同名的 Symbol(<code>:article</code>),效果也是一样。上面的代码使用的就是 Symbol。详情参见 <a href="http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for">form_for 的文档</a>。</p><p>然后,要在 <code>app/controllers/articles_controller.rb</code> 中添加 <code>update</code> 动作:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
</pre>
</div>
<p>新定义的 <code>update</code> 方法用来处理对现有文章的更新操作,接收一个 Hash,包含想要修改的属性。和之前一样,如果更新文章出错了,要再次显示表单。</p><p>上面的代码再次使用了前面为 <code>create</code> 动作定义的 <code>article_params</code> 方法。</p><div class="info"><p>不用把所有的属性都提供给 <code>update</code> 动作。例如,如果使用 <code>@article.update(title: 'A new title')</code>,Rails 只会更新 <code>title</code> 属性,不修改其他属性。</p></div><p>最后,我们想在文章列表页面,在每篇文章后面都加上一个链接,指向 <code>edit</code> 动作。打开 <code>app/views/articles/index.html.erb</code> 文件,在“Show”链接后面添加“Edit”链接:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="2"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
</tr>
<% end %>
</table>
</pre>
</div>
<p>我们还要在 <code>app/views/articles/show.html.erb</code> 模板的底部加上“Edit”链接:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
...
<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>
</pre>
</div>
<p>下图是文章列表页面现在的样子:</p><p><img src="images/getting_started/index_action_with_edit_link.png" alt="在文章列表页面显示了编辑链接"></p><h4 id="使用局部视图去掉视图中的重复代码">5.12 使用局部视图去掉视图中的重复代码</h4><p>编辑文章页面和新建文章页面很相似,显示表单的代码是相同的。下面使用局部视图去掉两个视图中的重复代码。按照约定,局部视图的文件名以下划线开头。</p><div class="info"><p>关于局部视图的详细介绍参阅“<a href="/layouts_and_rendering.html">Layouts and Rendering in Rails</a>”一文。</p></div><p>新建 <code>app/views/articles/_form.html.erb</code> 文件,写入以下代码:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<%= form_for @article do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
</pre>
</div>
<p>除了第一行 <code>form_for</code> 的用法变了之外,其他代码都和之前一样。之所以能在两个动作中共用一个 <code>form_for</code>,是因为 <code>@article</code> 是一个资源,对应于符合 REST 架构的路由,Rails 能自动分辨使用哪个地址和请求方法。</p><p>关于这种 <code>form_for</code> 用法的详细说明,请查阅 <a href="http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style">API 文档</a>。</p><p>下面来修改 <code>app/views/articles/new.html.erb</code> 视图,使用新建的局部视图,把其中的代码全删掉,替换成:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<p>然后按照同样地方法修改 <code>app/views/articles/edit.html.erb</code> 视图:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>Edit article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
</pre>
</div>
<h4 id="删除文章">5.13 删除文章</h4><p>现在介绍 CRUD 中的 D,从数据库中删除文章。按照 REST 架构的约定,删除文章的路由是:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
DELETE /articles/:id(.:format) articles#destroy
</pre>
</div>
<p>删除资源时使用 DELETE 请求。如果还使用 GET 请求,可以构建如下所示的恶意地址:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
</pre>
</div>
<p>删除资源使用 DELETE 方法,路由会把请求发往 <code>app/controllers/articles_controller.rb</code> 中的 <code>destroy</code> 动作。<code>destroy</code> 动作现在还不存在,下面来添加:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
</pre>
</div>
<p>想把记录从数据库删除,可以在 Active Record 对象上调用 <code>destroy</code> 方法。注意,我们无需为这个动作编写视图,因为它会转向 <code>index</code> 动作。</p><p>最后,在 <code>index</code> 动作的模板(<code>app/views/articles/index.html.erb</code>)中加上“Destroy”链接:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete, data: { confirm: 'Are you sure?' } %></td>