-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathchapter2.html
More file actions
1186 lines (1071 loc) · 91 KB
/
chapter2.html
File metadata and controls
1186 lines (1071 loc) · 91 KB
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 教程 - 第 2 章 玩具应用</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="最好的 Ruby on Rails 入门教程"/>
<meta name="keywords" content="ruby, rails, tutorial"/>
<meta name="author" content="Michael Hartl"/>
<meta name="translator" content="安道"/>
<meta name="generator" content="persie 0.0.5.1"/>
<link rel="stylesheet" type="text/css" href="//railstutorial-china.org/assets/css/main.css"/>
<link rel="stylesheet" type="text/css" href="book.css"/>
<script type="text/javascript" src="//railstutorial-china.org/assets/js/global.js"></script>
</head>
<body class="book-page">
<nav class="navbar">
<div class="container">
<div class="clearfix">
<a class="navbar-brand hidden-sm-up" href="//railstutorial-china.org/" title="Ruby on Rails 教程">Ruby on Rails 教程</a>
<button class="navbar-toggler hidden-sm-up pull-xs-right" type="button" data-toggle="collapse" data-target="#main-nav">☰</button>
</div>
<a class="navbar-brand hidden-xs-down" href="//railstutorial-china.org/" title="Ruby on Rails 教程">Ruby on Rails 教程</a>
<div class="collapse navbar-toggleable-xs pull-sm-right" id="main-nav">
<ul class="nav navbar-nav">
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/" title="首页">首页</a></li>
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/blog/" title="博客">博客</a></li>
<li class="nav-item active"><a class="nav-link" href="//railstutorial-china.org/book/" title="阅读">阅读</a></li>
<li class="nav-item"><a class="nav-link" href="//railstutorial-china.org/#ebook" title="电子书">电子书</a></li>
</ul>
</div>
</div>
</nav>
<div class="content">
<div class="container">
<div class="row">
<div class="col-lg-offset-2 col-lg-8">
<div class="book-versions">
选择版本:
<a class="btn btn-primary" href="//railstutorial-china.org/book/" title="Ruby on Rails 教程(原书第 4 版,针对 Rails 5)">Rails 5</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails42/" title="Ruby on Rails 教程(原书第 3 版,针对 Rails 4.2)">Rails 4.2</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails4/" title="Ruby on Rails 教程(原书第 3 版,针对 Rails 4.0)">Rails 4.0</a>
<a class="btn btn-secondary" href="//railstutorial-china.org/rails3/" title="Ruby on Rails 教程(原书第 2 版,针对 Rails 3.2)">Rails 3.2</a>
</div>
<div class="alert alert-warning">
<p>在线版的内容可能落后于电子书,如果想及时获得更新,请<a href="//railstutorial-china.org/#ebook" title="购买电子书">购买电子书</a>。</p>
</div>
<article class="article">
<section data-type="chapter" id="a-toy-app">
<h1><span class="title-label">第 2 章</span> 玩具应用</h1>
<p>本章我们要开发一个简单的演示应用,展示 Rails 强大的功能。我们会使用脚手架快速生成应用,这样就能站在一定高度上概览 Ruby on Rails 编程的过程(也能大致了解 Web 开发)。正如<a class="xref-link" href="chapter1.html#aside-scaffolding">旁注 1.2</a> 所说,本书将采用与众不同的方法,循序渐进开发一个完整的演示应用,遇到新的概念都会详细说明。不过为了快速概览(也为了寻找成就感),无需对脚手架避而不谈。我们将通过 URL 与最终开发出来的玩具应用交互,了解 Rails 应用的结构,也第一次演示 Rails 使用的 REST 架构。</p>
<p>与后面的演示应用类似,这个玩具应用中有用户(users)和微博(microposts),因此算是一个简化的 Twitter 类应用。应用的功能还需要后续开发,而且开发过程中的很多步骤看起来很神秘,不过暂时不用担心:从<a class="xref-link" href="chapter3.html#static-pages">3.2 节</a>起将从零开始再开发一个类似的完整应用,我还会提供大量的资料供你后续阅读。你要有些耐心,不要怕多犯错误,本章的主要目的就是让你不要被脚手架的神奇迷惑住,而要更深入地了解 Rails。</p>
<section data-type="sect1" id="planning-the-application">
<h1><span class="title-label">2.1</span> 规划应用</h1>
<p>这一节,我们要规划一下这个玩具应用。与 <a class="xref-link" href="chapter1.html#the-hello-application">1.3 节</a>一样,我们先使用 <code>rails new</code> 命令(指定 Rails 的版本号)生成应用的骨架:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span><span class="nb">cd</span> ~/environment
<span class="nv">$ </span>rails _5.1.6_ new toy_app
<span class="nv">$ </span><span class="nb">cd </span>toy_app/
</code></pre></div>
</div>
<p>如果使用<a class="xref-link" href="chapter1.html#development-environment">1.2.1 节</a>推荐的云端 IDE,这个应用可以在第一个应用所在的工作空间中创建,没必要再新建一个工作空间。如果没看到文件,可以点击文件浏览器中的齿轮图标,然后选择“Refresh File Tree”(刷新文件树)。</p>
<p>然后,在文本编辑器中修改 <code>Gemfile</code> 文件,写入<a class="xref-link" href="#listing-demo-gemfile-sqlite-version-redux">代码清单 2.1</a> 中的内容。</p>
<div id="listing-demo-gemfile-sqlite-version-redux" data-type="listing">
<h5><span class="title-label">代码清单 2.1</span>:这个玩具应用的 <code>Gemfile</code> 文件</h5>
<div class="highlight language-ruby"><pre><code><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">'5.1.6'</span>
<span class="n">gem</span> <span class="s1">'puma'</span><span class="p">,</span> <span class="s1">'3.9.1'</span>
<span class="n">gem</span> <span class="s1">'sass-rails'</span><span class="p">,</span> <span class="s1">'5.0.6'</span>
<span class="n">gem</span> <span class="s1">'uglifier'</span><span class="p">,</span> <span class="s1">'3.2.0'</span>
<span class="n">gem</span> <span class="s1">'coffee-rails'</span><span class="p">,</span> <span class="s1">'4.2.2'</span>
<span class="n">gem</span> <span class="s1">'jquery-rails'</span><span class="p">,</span> <span class="s1">'4.3.1'</span>
<span class="n">gem</span> <span class="s1">'turbolinks'</span><span class="p">,</span> <span class="s1">'5.0.1'</span>
<span class="n">gem</span> <span class="s1">'jbuilder'</span><span class="p">,</span> <span class="s1">'2.7.0'</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.13'</span>
<span class="n">gem</span> <span class="s1">'byebug'</span><span class="p">,</span> <span class="s1">'9.0.6'</span><span class="p">,</span> <span class="ss">platform: :mri</span>
<span class="k">end</span>
<span class="n">group</span> <span class="ss">:development</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s1">'web-console'</span><span class="p">,</span> <span class="s1">'3.5.1'</span>
<span class="n">gem</span> <span class="s1">'listen'</span><span class="p">,</span> <span class="s1">'3.1.5'</span>
<span class="n">gem</span> <span class="s1">'spring'</span><span class="p">,</span> <span class="s1">'2.0.2'</span>
<span class="n">gem</span> <span class="s1">'spring-watcher-listen'</span><span class="p">,</span> <span class="s1">'2.0.1'</span>
<span class="k">end</span>
<span class="n">group</span> <span class="ss">:production</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s1">'pg'</span><span class="p">,</span> <span class="s1">'0.20.0'</span>
<span class="k">end</span>
<span class="c1"># Windows does not include zoneinfo files, so bundle the tzinfo-data gem</span>
<span class="n">gem</span> <span class="s1">'tzinfo-data'</span><span class="p">,</span> <span class="ss">platforms: </span><span class="p">[</span><span class="ss">:mingw</span><span class="p">,</span> <span class="ss">:mswin</span><span class="p">,</span> <span class="ss">:x64_mingw</span><span class="p">,</span> <span class="ss">:jruby</span><span class="p">]</span>
</code></pre></div>
</div>
<p>注意,<a class="xref-link" href="#listing-demo-gemfile-sqlite-version-redux">代码清单 2.1</a> 和<a class="xref-link" href="chapter1.html#listing-gemfile-pg-gem">代码清单 1.13</a> 的内容一样。</p>
<p>与 <a class="xref-link" href="chapter1.html#heroku-setup">1.5.1 节</a>一样,安装 gem 时要指定 <code>--without production</code> 选项,不安装生产环境使用的 gem:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>bundle <span class="nb">install</span> <span class="nt">--without</span> production
</code></pre></div>
</div>
<p>如 <a class="xref-link" href="chapter1.html#bundler">1.3.1 节</a>所述,可能还要运行 <code>bundle update</code>(<a class="xref-link" href="chapter1.html#aside-technical-sophistication">旁注 1.1</a>)。</p>
<p>最后,把这个玩具应用纳入 Git 版本控制系统:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git init
<span class="nv">$ </span>git add <span class="nt">-A</span>
<span class="nv">$ </span>git commit <span class="nt">-m</span> <span class="s2">"Initialize repository"</span>
</code></pre></div>
</div>
<div id="fig-create-demo-repository" class="figure"><img src="images/chapter2/create_demo_repo_bitbucket.png" alt="create demo repo bitbucket" /><div class="figcaption"><span class="title-label">图 2.1</span>:在 Bitbucket 中为这个玩具应用创建一个仓库</div></div>
<p>你还可以在 Bitbucket 中点击“Create”(新建)按钮<a href="https://bitbucket.org/repo/create" class="external-link">创建一个新仓库</a>(<a class="xref-link" href="#fig-create-demo-repository">图 2.1</a>),然后把代码推送到这个远程仓库中:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git remote add origin git@bitbucket.org:<username>/toy_app.git
<span class="nv">$ </span>git push <span class="nt">-u</span> origin <span class="nt">--all</span>
</code></pre></div>
</div>
<p>越早部署应用越好。我建议你按照 <a class="xref-link" href="chapter1.html#hello-world">1.3.4 节</a>所述的步骤做,修改<a class="xref-link" href="#listing-hello-action-redux">代码清单 2.2</a> 和<a class="xref-link" href="#listing-hello-root-route-redux">代码清单 2.3</a>。</p>
<div id="listing-hello-action-redux" data-type="listing">
<h5><span class="title-label">代码清单 2.2</span>:在 <code>Application</code> 控制器中添加 <code>hello</code> 动作</h5>
<div class="source-file">app/controllers/application_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o"><</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">Base</span>
<span class="n">protect_from_forgery</span> <span class="ss">with: :exception</span>
<span class="k">def</span> <span class="nf">hello</span>
<span class="hll"> <span class="n">render</span> <span class="ss">html: </span><span class="s2">"hello, world!"</span></span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
</div>
<div id="listing-hello-root-route-redux" data-type="listing">
<h5><span class="title-label">代码清单 2.3</span>:设置根路由</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">root</span> <span class="s1">'application#hello'</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>然后,提交改动,再推送到 Heroku 中:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>git commit <span class="nt">-am</span> <span class="s2">"Add hello"</span>
<span class="nv">$ </span>heroku create
<span class="nv">$ </span>git push heroku master
</code></pre></div>
</div>
<p>(与 <a class="xref-link" href="chapter1.html#deploying">1.5 节</a>一样,你可能会看到一些提醒消息,现在先不去管它。<a class="xref-link" href="chapter7.html#professional-grade-deployment">7.5 节</a>会解决。)除了 Heroku 为应用提供的地址之外,输出的内容应该与<a class="xref-link" href="chapter1.html#fig-heroku-app">图 1.24</a> 一样。</p>
<p>下面要开发这个应用了。一般来说,开发 Web 应用的第一步是创建数据模型(data model)。模型表示应用所需的结构。这个玩具应用是个Twitter 类微博,只有用户和简短的文章(微博)。那么,我们先为这个应用添加 <code>User</code> 模型(<a class="xref-link" href="#modeling-demo-users">2.1.1 节</a>),然后再添加 <code>Micropost</code> 模型(<a class="xref-link" href="#modeling-demo-microposts">2.1.2 节</a>)。</p>
<section data-type="sect2" id="modeling-demo-users">
<h2><span class="title-label">2.1.1</span> <code>User</code> 模型</h2>
<p>网络中有多少不同的注册表单,就有多少定义用户数据模型的方式。简单起见,我们将使用一种最简可用的方式。这个玩具应用的用户有一个唯一的标识 <code>id</code>(<code>integer</code> 类型)、一个公开的名字 <code>name</code>(<code>string</code> 类型)和一个电子邮件地址 <code>email</code>(也是 <code>string</code> 类型)。电子邮件地址将作为用户名使用。<code>User</code> 模型的结构如<a class="xref-link" href="#fig-demo-user-model">图 2.2</a>。</p>
<div id="fig-demo-user-model" class="figure"><img src="images/chapter2/demo_user_model.png" alt="demo user model" /><div class="figcaption"><span class="title-label">图 2.2</span>:<code>User</code> 数据模型</div></div>
<p>在 <a class="xref-link" href="chapter6.html#database-migrations">6.1.1 节</a>会看到,<a class="xref-link" href="#fig-demo-user-model">图 2.2</a> 中的 <code>users</code> 对应于数据库中的一个表(table);<code>id</code>、<code>name</code> 和 <code>email</code> 是表中的列(column)。</p>
</section>
<section data-type="sect2" id="modeling-demo-microposts">
<h2><span class="title-label">2.1.2</span> <code>Micropost</code> 模型</h2>
<p><code>Micropost</code> 数据模型比 <code>User</code> 模型还要简单:微博只要一个 <code>id</code> 和表示微博内容的 <code>content</code>(<code>text</code> 类型)字段即可。<sup>[<a id="fn-ref-1" href="#fn-1">1</a>]</sup>此外还有一个比较复杂的字段要实现,这个字段把微博和用户关联(associate)起来。我们使用 <code>user_id</code> 存储微博的属主。最终得到的 <code>Micropost</code> 数据模型如<a class="xref-link" href="#fig-demo-micropost-model">图 2.3</a> 所示。</p>
<div id="fig-demo-micropost-model" class="figure"><img src="images/chapter2/demo_micropost_model.png" alt="demo micropost model" /><div class="figcaption"><span class="title-label">图 2.3</span>:<code>Micropost</code> 数据模型</div></div>
<p><a class="xref-link" href="#a-user-has-many-microposts">2.3.3 节</a>会介绍怎样使用 <code>user_id</code> 字段简单实现一个用户拥有多个微博的功能。(<a class="xref-link" href="chapter13.html#user-microposts">第 13 章</a>有更完整的说明。)</p>
</section>
</section>
<section data-type="sect1" id="demo-users-resource">
<h1><span class="title-label">2.2</span> Users 资源</h1>
<p>这一节我们要实现 <a class="xref-link" href="#modeling-demo-users">2.1.1 节</a>设定的 <code>User</code> 数据模型,还会为它创建 Web 界面。二者结合起来就是一个 <code>Users</code> 资源。“资源”的意思是把用户设想为对象,可以通过 <a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" class="external-link">HTTP 协议</a>在网页中创建(create)、读取(read)、更新(update)和删除(delete)。正如前面提到的,我们将使用 Rails 内置的脚手架生成 <code>Users</code> 资源。我建议你先不要细看脚手架生成的代码,这时看只会让你更困惑。</p>
<p>把 <code>scaffold</code> 传给 <code>rails generate</code> 命令就可以使用 Rails 的脚手架了。传给 <code>scaffold</code> 的参数是资源名的单数形式(这里是 <code>User</code>)<sup>[<a id="fn-ref-2" href="#fn-2">2</a>]</sup>,后面可以再跟着一些可选参数,指定数据模型中的字段:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="hll"><span class="nv">$ </span>rails generate scaffold User name:string email:string</span>
invoke active_record
create db/migrate/20160515001017_create_users.rb
create app/models/user.rb
invoke test_unit
create <span class="nb">test</span>/models/user_test.rb
create <span class="nb">test</span>/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
invoke test_unit
create <span class="nb">test</span>/controllers/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/users.coffee
invoke scss
create app/assets/stylesheets/users.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
</code></pre></div>
</div>
<p>我们在执行的命令中加入了 <code>name:string</code> 和 <code>email:string</code>,这样就可以实现<a class="xref-link" href="#fig-demo-user-model">图 2.2</a> 中的 <code>User</code> 模型了。注意,没必要指定 <code>id</code> 字段,Rails 会自动创建并将其设为表的主键(primary key)。</p>
<p>接下来我们要用 <code>rails db:migrate</code> 命令迁移(migrate)数据库,如<a class="xref-link" href="#listing-first-migration">代码清单 2.4</a> 所示。</p>
<div id="listing-first-migration" data-type="listing">
<h5><span class="title-label">代码清单 2.4</span>:迁移数据库</h5>
<div class="highlight language-sh"><pre><code><span class="hll"><span class="nv">$ </span>rails db:migrate</span>
<span class="o">==</span> CreateUsers: migrating <span class="o">====================================================</span>
<span class="nt">--</span> create_table<span class="o">(</span>:users<span class="o">)</span>
-> 0.0017s
<span class="o">==</span> CreateUsers: migrated <span class="o">(</span>0.0018s<span class="o">)</span> <span class="o">===========================================</span>
</code></pre></div>
</div>
<p>上述命令的作用是使用新的 <code>User</code> 数据模型更新数据库。(从 <a class="xref-link" href="chapter6.html#database-migrations">6.1.1 节</a>开始会深入学习数据库迁移。)</p>
<p>顺便说一下,在 Rails 5 之前的版本中,<code>db:migrate</code> 命令使用 <code>rake</code> 执行,而不是 <code>rails</code>。因此,如果你还要维护以前的应用,一定要知道如何使用 Rake(<a class="xref-link" href="#aside-rake">旁注 2.1</a>)。</p>
<div data-type="sidebar" id="aside-rake" class="sidebar">
<h5><span class="title-label">旁注 2.1</span>:Rake</h5>
<p>在 Unix 中,把源码编译成可执行的程序时,<a href="http://en.wikipedia.org/wiki/Make_(software)" class="external-link">Make</a> 扮演了很重要的角色。Rake 是 Ruby 版 Make,是使用 Ruby 语言编写的 Make 类程序。</p>
<p>在 Rails 5 之前,Ruby on Rails 大量使用 Rake,因此为了维护以前的应用,一定要知道如何使用 Rake。或许,Rails 最常使用的两个 Rake 命令是 <code>rake db:migrate</code>(迁移数据库,更新数据模型)和 <code>rake test</code>(运行自动化测试组件)。使用 Rake 时,要确保使用的是 Rails 应用 <code>Gemfile</code> 文件中指定的版本,方法是使用 Bundler 提供的 <code>bundle exec</code> 命令。因此,执行迁移的 <code>rails db:migrate</code> 命令要写成:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>bundle <span class="nb">exec </span>rake db:migrate
</code></pre></div>
</div>
</div>
<p>执行<a class="xref-link" href="#listing-first-migration">代码清单 2.4</a> 中的迁移之后,可以新打开一个终端标签页(<a class="xref-link" href="chapter1.html#fig-rails-server-new-tab">图 1.10</a>),运行本地 Web 服务器:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="nv">$ </span>rails server
</code></pre></div>
</div>
<p>现在,这个玩具应用应该可以在本地服务器中访问了,结果与 <a class="xref-link" href="chapter1.html#rails-server">1.3.2 节</a>一样。(如果使用云端 IDE,要在一个新的浏览器选项卡中打开网页,别在 IDE 中打开。)</p>
<section data-type="sect2" id="a-user-tour">
<h2><span class="title-label">2.2.1</span> 浏览用户相关的页面</h2>
<p>访问根 URL,我们会看到与<a class="xref-link" href="chapter1.html#fig-hello-world-hello-app">图 1.15</a> 一样的“hello, world!”页面。不过使用脚手架生成 <code>Users</code> 资源时生成了很多用来处理用户的页面。例如,列出所有用户的页面 /users,创建新用户的页面 /users/new。本节的目的是走马观花地浏览一下这些用户相关的页面。浏览时你会发现<a class="xref-link" href="#table-user-urls">表 2.1</a> 很有用,表中显示了页面和 URL 之间的对应关系。</p>
<table id="table-user-urls" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 2.1</span>:<code>Users</code> 资源中页面和 URL 的对应关系</caption>
<colgroup>
<col style="width: 33.3333%;" />
<col style="width: 33.3333%;" />
<col style="width: 33.3334%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">URL</th>
<th class="tableblock halign-left valign-top">动作</th>
<th class="tableblock halign-left valign-top">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>index</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">/users/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>show</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">显示 ID 为 1 的用户</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/new</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>new</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">/users/1/edit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>edit</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">编辑 ID 为 1 的用户</p></td>
</tr>
</tbody>
</table>
<p>我们先来看显示应用中所有用户的页面,这个页面叫索引页,路径是 /users。和预期一样,目前还没有用户,如<a class="xref-link" href="#fig-demo-blank-user-index-rails-3">图 2.4</a> 所示。</p>
<p>如果想创建新用户,要访问 /users/new 路径上的页面,如<a class="xref-link" href="#fig-demo-new-user-rails_3">图 2.5</a> 所示。<a class="xref-link" href="chapter7.html#sign-up">第 7 章</a>会把这个页面打造成用户注册页面。</p>
<p>我们可以在表单中填入名字和电子邮件地址,然后点击“Create User”(创建用户)按钮创建一个用户。
此时,浏览器会转向这个用户的页面,即 /users/1,如<a class="xref-link" href="#fig-demo-show-user-rails-3">图 2.6</a> 所示。(页面中显示的绿色文字是闪现消息(flash message),<a class="xref-link" href="chapter7.html#the-flash">7.4.2 节</a>会介绍。)注意,这个页面的 URL 是 /users/1。你可能猜到了,这里的 <code>1</code> 就是<a class="xref-link" href="#fig-demo-user-model">图 2.2</a> 中的用户 <code>id</code>。<a class="xref-link" href="chapter7.html#showing-users">7.1 节</a>会把这个页面打造成用户的资料页。</p>
<p>如果想修改用户的信息,要访问编辑页面,即 /users/1/edit(<a class="xref-link" href="#fig-demo-edit-user-rails-3">图 2.7</a>)。修改用户信息后点击“Update User”(更新用户)按钮就更改了这个玩具应用中该用户的信息(<a class="xref-link" href="#fig-demo-update-user-rails-3">图 2.8</a>)。(<a class="xref-link" href="chapter6.html#modeling-users">第 6 章</a>会详细介绍,用户的信息存储在后端的数据库中。)我们会在 <a class="xref-link" href="chapter10.html#updating-users">10.1 节</a>为演示应用添加编辑和更新用户信息的功能。</p>
<div id="fig-demo-blank-user-index-rails-3" class="figure"><img src="images/chapter2/demo_blank_user_index_3rd_edition.png" alt="demo blank user index 3rd edition" /><div class="figcaption"><span class="title-label">图 2.4</span>:<code>Users</code> 资源的索引页(/users)</div></div>
<div id="fig-demo-new-user-rails_3" class="figure"><img src="images/chapter2/demo_new_user_3rd_edition.png" alt="demo new user 3rd edition" /><div class="figcaption"><span class="title-label">图 2.5</span>:新建用户页面(/users/new)</div></div>
<div id="fig-demo-show-user-rails-3" class="figure"><img src="images/chapter2/demo_show_user_3rd_edition.png" alt="demo show user 3rd edition" /><div class="figcaption"><span class="title-label">图 2.6</span>:显示某个用户的页面(/users/1)</div></div>
<div id="fig-demo-edit-user-rails-3" class="figure"><img src="images/chapter2/demo_edit_user_3rd_edition.png" alt="demo edit user 3rd edition" /><div class="figcaption"><span class="title-label">图 2.7</span>:编辑用户信息的页面(/users/1/edit)</div></div>
<div id="fig-demo-update-user-rails-3" class="figure"><img src="images/chapter2/demo_update_user_3rd_edition.png" alt="demo update user 3rd edition" /><div class="figcaption"><span class="title-label">图 2.8</span>:更新信息后的用户页面</div></div>
<div id="fig-demo-user-index-two-rails-3" class="figure"><img src="images/chapter2/demo_user_index_two_3rd_edition.png" alt="demo user index two 3rd edition" /><div class="figcaption"><span class="title-label">图 2.9</span>:创建第二个用户后的用户索引页(/users)</div></div>
<p>现在回到 /users/new 页面,在表单中填写信息,创建第二个用户。然后访问用户索引页,结果如<a class="xref-link" href="#fig-demo-user-index-two-rails-3">图 2.9</a> 所示。<a class="xref-link" href="chapter7.html#showing-users">7.1 节</a>会美化这个显示所有用户的页面。</p>
<p>我们已经看了创建、显示和编辑用户的页面,最后要看删除用户的页面(<a class="xref-link" href="#fig-demo-destroy-user">图 2.10</a>)。点击<a class="xref-link" href="#fig-demo-destroy-user">图 2.10</a> 中所示的链接后,会删除第二个用户,现在索引页面就只剩一个用户了。(如果这个操作不成功,确认浏览器是否启用了 JavaScript。Rails 通过 JavaScript 发送删除用户的请求。)<a class="xref-link" href="chapter10.html#deleting-users">10.4 节</a>会为演示应用实现用户删除功能,而且仅限于管理员级别的用户才能执行这项操作。</p>
<div id="fig-demo-destroy-user" class="figure"><img src="images/chapter2/demo_destroy_user_3rd_edition.png" alt="demo destroy user 3rd edition" /><div class="figcaption"><span class="title-label">图 2.10</span>:删除一个用户</div></div>
<h5 id="exercises-a-user-tour" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>(如果你了解 CSS)创建一个新用户,然后使用浏览器中的 HTML 审查工具找出“User was successfully created.”文本的 CSS ID。刷新页面后会发生什么?</p>
</li>
<li>
<p>如果创建用户时只填写名字,而没填写电子邮件地址,会发生什么?</p>
</li>
<li>
<p>如果创建用户时填写的电子邮件地址无效,例如填写的是“@example.com”,会发生什么?</p>
</li>
<li>
<p>删除前几题创建的用户。删除用户时,Rails 会显示消息吗?</p>
</li>
</ol>
</section>
<section data-type="sect2" id="mvc-in-action">
<h2><span class="title-label">2.2.2</span> MVC 实战</h2>
<p>我们已经快速概览了 <code>Users</code> 资源,下面我们从 MVC(<a class="xref-link" href="chapter1.html#mvc">1.3.3 节</a>)的视角出发,审视其中某些部分。我们将分析在浏览器中访问用户索引页(/users)的过程,了解一下 MVC(<a class="xref-link" href="#fig-mvc-detailed">图 2.11</a>)。</p>
<div id="fig-mvc-detailed" class="figure"><img src="images/chapter2/mvc_detailed.png" alt="mvc detailed" /><div class="figcaption"><span class="title-label">图 2.11</span>:Rails 中的 MVC 架构详解</div></div>
<p>图中各步的说明如下:</p>
<ol class="arabic">
<li>
<p>浏览器向 /users 发送请求;</p>
</li>
<li>
<p>Rails 的路由把 /users 交给 <code>Users</code> 控制器的 <code>index</code> 动作处理;</p>
</li>
<li>
<p><code>index</code> 动作要求 <code>User</code> 模型检索所有用户(<code>User.all</code>);</p>
</li>
<li>
<p><code>User</code> 模型从数据库中读取所有用户;</p>
</li>
<li>
<p><code>User</code> 模型把所有用户组成的列表返回给控制器;</p>
</li>
<li>
<p>控制器把所有用户赋值给 <code>@users</code> 变量,然后传入 <code>index</code> 视图;</p>
</li>
<li>
<p>视图使用嵌入式 Ruby 把页面渲染成 HTML;</p>
</li>
<li>
<p>控制器把 HTML 送回浏览器。<sup>[<a id="fn-ref-3" href="#fn-3">3</a>]</sup></p>
</li>
</ol>
<p>下面详细分析这个过程。首先,浏览器发送请求(第 1 步)。这一步可以直接在浏览器地址栏中输入地址,也可以点击网页中的链接。请求到达 Rails 路由器(第 2 步),路由器根据 URL(以及请求的类型,参见<a class="xref-link" href="chapter3.html#aside-get-etc">旁注 3.2</a>)把请求分配给合适的控制器动作。把 <code>Users</code> 资源中相关的 URL 映射到控制器动作的代码如<a class="xref-link" href="#listing-rails-routes">代码清单 2.5</a> 所示。那行代码会按照<a class="xref-link" href="#table-user-urls">表 2.1</a> 中的对应关系做映射。(<code>:users</code> 这个写法看着很奇怪,它是一个符号(Symbol),<a class="xref-link" href="chapter4.html#hashes-and-symbols">4.3.3 节</a>会介绍。)</p>
<div id="listing-rails-routes" data-type="listing">
<h5><span class="title-label">代码清单 2.5</span>:Rails 路由,为 <code>Users</code> 资源定义了一条规则</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="hll"> <span class="n">resources</span> <span class="ss">:users</span></span>
<span class="n">root</span> <span class="s1">'application#hello'</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>既然打开路由文件了,那就花点儿时间把根路由改为用户索引页吧。修改之后,访问根地址就会显示 /users 页面。我们在<a class="xref-link" href="#listing-hello-root-route-redux">代码清单 2.3</a> 中添加了根路由:</p>
<div data-type="listing">
<div class="highlight language-ruby"><pre><code><span class="n">root</span> <span class="s1">'application#hello'</span>
</code></pre></div>
</div>
<p>上述规则把根路由指向 <code>Application</code> 控制器中的 <code>hello</code> 动作。现在,我们想使用 <code>Users</code> 控制器的 <code>index</code> 动作,因此要按照<a class="xref-link" href="#listing-rails-routes-root-route">代码清单 2.6</a> 所示的代码修改。</p>
<div id="listing-rails-routes-root-route" data-type="listing">
<h5><span class="title-label">代码清单 2.6</span>:把根路由指向 <code>Users</code> 控制器的动作</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">resources</span> <span class="ss">:users</span>
<span class="hll"> <span class="n">root</span> <span class="s1">'users#index'</span></span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>一个控制器中有多个动作,<a class="xref-link" href="#a-user-tour">2.2.1 节</a>浏览的页面对应于 <code>Users</code> 控制器的不同动作。脚手架生成的控制器代码摘要如<a class="xref-link" href="#listing-demo-users-controller">代码清单 2.7</a> 所示。注意 <code>class UsersController < ApplicationController</code> 这种写法,在 Ruby 中这表示类继承。(<a class="xref-link" href="#inheritance-hierarchies">2.3.4 节</a>会简要介绍继承,<a class="xref-link" href="chapter4.html#ruby-classes">4.4 节</a>再做详细介绍。)</p>
<div id="listing-demo-users-controller" data-type="listing">
<h5><span class="title-label">代码清单 2.7</span>:<code>Users</code> 控制器代码摘要</h5>
<div class="source-file">app/controllers/users_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">UsersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">def</span> <span class="n">index</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">new</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">edit</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">create</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">update</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">destroy</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>你可能注意到了,动作的数量比我们看过的页面数量多,<code>index</code>、<code>show</code>、<code>new</code> 和 <code>edit</code> 对应于 <a class="xref-link" href="#a-user-tour">2.2.1 节</a>介绍的页面。此外还有一些其他动作,<code>create</code>、<code>update</code> 和 <code>destroy</code>。这些动作一般不直接渲染页面(不过有时也会),只会修改数据库中保存的用户数据。<a class="xref-link" href="#table-demo-restful-users">表 2.2</a> 列出了控制器的全部动作,这些动作就是 Rails 对 REST 架构(<a class="xref-link" href="#aside-rest">旁注 2.2</a>)的实现。REST 架构由计算机科学家 <a href="http://en.wikipedia.org/wiki/Roy_Fielding" class="external-link">Roy Fielding</a> 提出,意思是“表现层状态转化”(Representational State Transfer)。<sup>[<a id="fn-ref-4" href="#fn-4">4</a>]</sup>注意<a class="xref-link" href="#table-demo-restful-users">表 2.2</a> 中的内容,有些部分有重叠。例如 <code>show</code> 和 <code>update</code> 两个动作都映射到 /users/1 这个地址上,二者的区别是响应的 <a href="http://en.wikipedia.org/wiki/HTTP_request#Request_methods" class="external-link">HTTP 请求方法</a>不同。<a class="xref-link" href="chapter3.html#getting-started-with-testing">3.3 节</a>会更详细地介绍 HTTP 请求方法。</p>
<table id="table-demo-restful-users" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 2.2</span>:<a class="xref-link" href="#listing-rails-routes">代码清单 2.5</a> 中 <code>Users</code> 资源生成的符合 REST 架构的路由</caption>
<colgroup>
<col style="width: 15%;" />
<col style="width: 20%;" />
<col style="width: 15%;" />
<col style="width: 50%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">HTTP 请求</th>
<th class="tableblock halign-left valign-top">URL</th>
<th class="tableblock halign-left valign-top">动作</th>
<th class="tableblock halign-left valign-top">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>index</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>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>show</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">显示 ID 为 1 的用户</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/new</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>new</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>POST</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>create</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>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/1/edit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>edit</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">显示 ID 为 1 的用户的编辑页面</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>PATCH</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>update</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">更新 ID 为 1 的用户</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DELETE</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/users/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>destroy</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">删除 ID 为 1 的用户</p></td>
</tr>
</tbody>
</table>
<div data-type="sidebar" id="aside-rest" class="sidebar">
<h5><span class="title-label">旁注 2.2</span>:表现层状态转化(REST)</h5>
<p>如果阅读过一些 Ruby on Rails Web 开发相关的资料,你会发现很多地方都提到了“REST”,它是“表现层状态转化”(REpresentational State Transfer)的简称。REST 是一种架构风格,用于开发分布式、基于网络的系统和软件应用,例如万维网和 Web 应用。REST 理论很抽象,在 Rails 应用中,REST 意味着大多数组件(例如用户和微博)都被模型化,变成资源(resource),可以创建(create)、读取(read)、更新(update)和删除(delete)。这些操作与<a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete" class="external-link">关系型数据库中的 CRUD 操作</a>和 <a href="http://en.wikipedia.org/wiki/HTTP_request#Request_methods" class="external-link">HTTP 请求方法</a>(<code>POST</code>、<code>GET</code>、<code>PATCH</code> 和 <code>DELETE</code>)对应。<a class="xref-link" href="chapter3.html#getting-started-with-testing">3.3 节</a>,特别是<a class="xref-link" href="chapter3.html#aside-get-etc">旁注 3.2</a>,将更详细地介绍 HTTP 请求。</p>
<p>作为 Rails 应用开发者,REST 开发方式能帮助你决定编写哪些控制器和动作:你只需简单地把可以创建、读取、更新和删除的资源理清就可以了。对本章的“用户”和“微博”来说,这一过程非常明确,因为它们都是很自然的资源形式。在<a class="xref-link" href="chapter14.html#following-users">第 14 章</a>将看到,使用 REST 架构可以通过一种自然而便捷的方式解决棘手问题(“关注用户”功能)。</p>
</div>
<p>为了探明 <code>Users</code> 控制器与 <code>User</code> 模型之间的关系,我们看一下简化后的 <code>index</code> 动作,如<a class="xref-link" href="#listing-demo-index-action">代码清单 2.8</a> 所示。(要阅读不完全能理解的代码也体现了“技术是复杂的”。)</p>
<div id="listing-demo-index-action" data-type="listing">
<h5><span class="title-label">代码清单 2.8</span>:这个玩具应用中简化的 <code>index</code> 动作</h5>
<div class="source-file">app/controllers/users_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">UsersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">def</span> <span class="n">index</span>
<span class="hll"> <span class="vi">@users</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">all</span></span>
<span class="k">end</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<p><code>index</code> 动作中有一行代码,<code>@users = User.all</code>(<a class="xref-link" href="#fig-mvc-detailed">图 2.11</a> 中的第 3 步),让 <code>User</code> 模型从数据库中检索所有用户(第 4 步),然后把结果赋值给 <code>@users</code> 变量(读作“at-users”,第 5 步)。<code>User</code> 模型的代码参见<a class="xref-link" href="#listing-demo-user-model">代码清单 2.9</a>。这段代码看似简单,但是通过继承具备了很多功能(参见 <a class="xref-link" href="#inheritance-hierarchies">2.3.4 节</a> 和 <a class="xref-link" href="chapter4.html#ruby-classes">4.4 节</a>)。具体而言,使用 Rails 中名为 Active Record 的库后,<code>User.all</code> 就能返回数据库中的所有用户。</p>
<div id="listing-demo-user-model" data-type="listing">
<h5><span class="title-label">代码清单 2.9</span>:玩具应用中的 <code>User</code> 模型</h5>
<div class="source-file">app/models/user.rb</div>
<div class="highlight language-ruby"><pre><code><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="k">end</span>
</code></pre></div>
</div>
<p>定义 <code>@users</code> 变量后,控制器再调用视图(第 6 步)。视图的代码如<a class="xref-link" href="#listing-demo-index-view">代码清单 2.10</a> 所示。以 <code>@</code> 开头的变量是实例变量(instance variable),在视图中自动可用。这里,<code>index.html.erb</code> 视图中的代码(<a class="xref-link" href="#listing-demo-index-view">代码清单 2.10</a>)遍历 <code>@users</code>,为每个用户生成一行 HTML。(你现在可能读不懂这些代码,这里只是让你看一下视图是什么样子。)</p>
<div id="listing-demo-index-view" data-type="listing">
<h5><span class="title-label">代码清单 2.10</span>:用户索引页的视图</h5>
<div class="source-file">app/views/users/index.html.erb</div>
<div class="highlight language-erb"><pre><code><span class="nt"><h1></span>Listing users<span class="nt"></h1></span>
<span class="nt"><table></span>
<span class="nt"><thead></span>
<span class="nt"><tr></span>
<span class="nt"><th></span>Name<span class="nt"></th></span>
<span class="nt"><th></span>Email<span class="nt"></th></span>
<span class="nt"><th</span> <span class="na">colspan=</span><span class="s">"3"</span><span class="nt">></th></span>
<span class="nt"></tr></span>
<span class="nt"></thead></span>
<span class="hll"><span class="cp"><%</span> <span class="vi">@users</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">user</span><span class="o">|</span> <span class="cp">%></span></span>
<span class="nt"><tr></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">user</span><span class="p">.</span><span class="nf">name</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">user</span><span class="p">.</span><span class="nf">email</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Show'</span><span class="p">,</span> <span class="n">user</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Edit'</span><span class="p">,</span> <span class="n">edit_user_path</span><span class="p">(</span><span class="n">user</span><span class="p">)</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'Destroy'</span><span class="p">,</span> <span class="n">user</span><span class="p">,</span> <span class="ss">method: :delete</span><span class="p">,</span>
<span class="ss">data: </span><span class="p">{</span> <span class="ss">confirm: </span><span class="s1">'Are you sure?'</span> <span class="p">}</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
<span class="nt"></table></span>
<span class="nt"><br></span>
<span class="cp"><%=</span> <span class="n">link_to</span> <span class="s1">'New User'</span><span class="p">,</span> <span class="n">new_user_path</span> <span class="cp">%></span>
</code></pre></div>
</div>
<p>视图把代码转换成 HTML(第 7 步),然后控制器将其返回给浏览器,再显示出来(第 8 步)。</p>
<h5 id="exercises-mvc-in-action" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>参照<a class="xref-link" href="#fig-mvc-detailed">图 2.11</a>,写出访问 /users/1/edit 页面的步骤。</p>
</li>
<li>
<p>在脚手架生成的代码中找出前一题从数据库中检索用户的代码。</p>
</li>
<li>
<p>编辑用户页面的视图文件,其名称是什么?</p>
</li>
</ol>
</section>
<section data-type="sect2" id="weaknesses-of-this-users-resource">
<h2><span class="title-label">2.2.3</span> 这个 <code>Users</code> 资源的不足</h2>
<p>脚手架生成的 <code>Users</code> 资源虽然能够让你大致了解 Rails,但也有一些不足:</p>
<ul>
<li>
<p><strong>没有验证数据</strong>。<code>User</code> 模型会接受空名字和无效的电子邮件地址,而不报错。</p>
</li>
<li>
<p><strong>没有验证身份</strong>。没实现登录和退出功能,随意一个用户都可以进行任何操作。</p>
</li>
<li>
<p><strong>没有测试</strong>。也不是完全没有,脚手架会生成一些基本的测试,不过很粗糙也不灵便,没有针对数据验证和身份验证的测试,更别说针对其他功能的测试了。</p>
</li>
<li>
<p><strong>没样式,没布局</strong>。没有共用的样式和网站导航。</p>
</li>
<li>
<p><strong>没真正理解</strong>。如果你能读懂脚手架生成的代码,就不需要阅读这本书了。</p>
</li>
</ul>
</section>
</section>
<section data-type="sect1" id="demo-microposts-resource">
<h1><span class="title-label">2.3</span> <code>Microposts</code> 资源</h1>
<p>我们已经生成并浏览了 <code>Users</code> 资源,现在要生成 <code>Microposts</code> 资源。阅读本节时,我推荐你和 <a class="xref-link" href="#demo-users-resource">2.2 节</a>对比一下。你会发现这两个资源在很多方面都是一致的。通过这样重复生成资源,我们可以更好地理解 Rails 中的 REST 架构。在这样的早期阶段看一下 <code>Users</code> 资源和 <code>Microposts</code> 资源的相同之处,也是本章的主要目的之一。</p>
<section data-type="sect2" id="a-micropost-microtour">
<h2><span class="title-label">2.3.1</span> 概览 <code>Microposts</code> 资源</h2>
<p>与 <code>Users</code> 资源一样,我们将使用 <code>rails generate scaffold</code> 命令生成 <code>Microposts</code> 资源的代码,不过这一次要实现<a class="xref-link" href="#fig-demo-micropost-model">图 2.3</a> 中的数据模型:<sup>[<a id="fn-ref-5" href="#fn-5">5</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="hll"><span class="nv">$ </span>rails generate scaffold Micropost content:text user_id:integer</span>
invoke active_record
create db/migrate/20160515211229_create_microposts.rb
create app/models/micropost.rb
invoke test_unit
create <span class="nb">test</span>/models/micropost_test.rb
create <span class="nb">test</span>/fixtures/microposts.yml
invoke resource_route
route resources :microposts
invoke scaffold_controller
create app/controllers/microposts_controller.rb
invoke erb
create app/views/microposts
create app/views/microposts/index.html.erb
create app/views/microposts/edit.html.erb
create app/views/microposts/show.html.erb
create app/views/microposts/new.html.erb
create app/views/microposts/_form.html.erb
invoke test_unit
create <span class="nb">test</span>/controllers/microposts_controller_test.rb
invoke helper
create app/helpers/microposts_helper.rb
invoke test_unit
invoke jbuilder
create app/views/microposts/index.json.jbuilder
create app/views/microposts/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/microposts.coffee
invoke scss
create app/assets/stylesheets/microposts.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
</code></pre></div>
</div>
<p>(如果看到 Spring 相关的错误,再次执行这个命令即可。)然后,跟 <a class="xref-link" href="#demo-users-resource">2.2 节</a>一样,我们要执行迁移,更新数据库,使用新建的数据模型:</p>
<div data-type="listing">
<div class="highlight language-sh"><pre><code><span class="hll"><span class="nv">$ </span>rails db:migrate</span>
<span class="o">==</span> CreateMicroposts: migrating <span class="o">===============================================</span>
<span class="nt">--</span> create_table<span class="o">(</span>:microposts<span class="o">)</span>
-> 0.0023s
<span class="o">==</span> CreateMicroposts: migrated <span class="o">(</span>0.0026s<span class="o">)</span> <span class="o">======================================</span>
</code></pre></div>
</div>
<p>现在我们就可以使用类似 <a class="xref-link" href="#a-user-tour">2.2.1 节</a>中介绍的方法来创建微博了。你可能猜到了,脚手架还会更新 Rails 的路由文件,为 <code>Microposts</code> 资源加入一条规则,如<a class="xref-link" href="#listing-demo-microposts-resource">代码清单 2.11</a> 所示。<sup>[<a id="fn-ref-6" href="#fn-6">6</a>]</sup>与 <code>Users</code> 资源类似,<code>resources :micropsts</code> 把微博相关的 URL 映射到 <code>Microposts</code> 控制器上,如<a class="xref-link" href="#table-demo-restful-microposts">表 2.3</a> 所示。</p>
<div id="listing-demo-microposts-resource" data-type="listing">
<h5><span class="title-label">代码清单 2.11</span>:Rails 的路由,有一条针对 <code>Microposts</code> 资源的新规则</h5>
<div class="source-file">config/routes.rb</div>
<div class="highlight language-ruby"><pre><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="hll"> <span class="n">resources</span> <span class="ss">:microposts</span></span>
<span class="n">resources</span> <span class="ss">:users</span>
<span class="n">root</span> <span class="s1">'users#index'</span>
<span class="k">end</span>
</code></pre></div>
</div>
<table id="table-demo-restful-microposts" class="tableblock frame-all grid-all" style="width: 100%;">
<caption><span class="title-label">表 2.3</span>:<a class="xref-link" href="#listing-demo-microposts-resource">代码清单 2.11</a> 中 <code>Microposts</code> 资源生成的符合 REST 架构的路由</caption>
<colgroup>
<col style="width: 15%;" />
<col style="width: 20%;" />
<col style="width: 15%;" />
<col style="width: 50%;" />
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">HTTP 请求</th>
<th class="tableblock halign-left valign-top">URL</th>
<th class="tableblock halign-left valign-top">动作</th>
<th class="tableblock halign-left valign-top">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>index</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>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>show</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">显示 ID 为 1 的微博</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts/new</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>new</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>POST</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>create</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>GET</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts/1/edit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>edit</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">显示 ID 为 1 的微博的编辑页面</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>PATCH</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>update</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">更新 ID 为 1 的微博</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DELETE</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">/microposts/1</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>destroy</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">删除 ID 为 1 的微博</p></td>
</tr>
</tbody>
</table>
<p><code>Microposts</code> 控制器的代码简化后如<a class="xref-link" href="#listing-demo-microposts-controller">代码清单 2.12</a> 所示。注意,除了把 <code>UsersController</code> 换成 <code>MicropostsController</code> 之外,这段代码和<a class="xref-link" href="#listing-demo-users-controller">代码清单 2.7</a> 没什么区别。这说明了两个资源在 REST 架构中的共同之处。</p>
<div id="listing-demo-microposts-controller" data-type="listing">
<h5><span class="title-label">代码清单 2.12</span>:简化后的 <code>Microposts</code> 控制器</h5>
<div class="source-file">app/controllers/microposts_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">MicropostsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">def</span> <span class="n">index</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">new</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">edit</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">create</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">update</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">def</span> <span class="nf">destroy</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>我们在发布微博的页面(/microposts/new)输入一些内容,发布一篇微博,如<a class="xref-link" href="#fig-demo-new-micropost">图 2.12</a> 所示。</p>
<p>既然已经打开这个页面了,那就多发布几篇微博,并且确保至少把一篇微博的 <code>user_id</code> 设为 <code>1</code>,把微博赋予 <a class="xref-link" href="#a-user-tour">2.2.1 节</a>中创建的第一个用户。结果应该和<a class="xref-link" href="#fig-demo-micropost-index">图 2.13</a> 类似。</p>
<div id="fig-demo-new-micropost" class="figure"><img src="images/chapter2/demo_new_micropost_3rd_edition.png" alt="demo new micropost 3rd edition" /><div class="figcaption"><span class="title-label">图 2.12</span>:发布微博的页面(/microposts/new)</div></div>
<div id="fig-demo-micropost-index" class="figure"><img src="images/chapter2/demo_micropost_index_3rd_edition.png" alt="demo micropost index 3rd edition" /><div class="figcaption"><span class="title-label">图 2.13</span>:微博索引页(/microposts)</div></div>
<h5 id="exercises-a-micropost-microtour" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>(如果你了解 CSS)发布一篇新微博,然后使用浏览器中的 HTML 审查工具找出“Micropost was successfully created.”文本的 CSS ID。刷新页面后会发生什么?</p>
</li>
<li>
<p>发布微博时不输入内容也不指定用户 ID 试试。</p>
</li>
<li>
<p>发布一篇内容超过 140 字(例如<a href="https://en.wikipedia.org/wiki/Ruby_(programming_language)" class="external-link">维基百科中介绍 Ruby</a> 的第一段)的微博试试。</p>
</li>
<li>
<p>删除前几题创建的微博。</p>
</li>
</ol>
</section>
<section data-type="sect2" id="putting-the-micro-in-microposts">
<h2><span class="title-label">2.3.2</span> 限制微博的长度</h2>
<p>为了称得上“微博”这个名字,内容的长度要做限制。在 Rails 中实现这种限制很简单,使用验证(validation)功能即可。要限制微博的长度最多为 140 个字符(就像 Twitter 一样),我们可以使用长度验证。在文本编辑器或 IDE 中打开 <code>app/models/micropost.rb</code> 文件,写入<a class="xref-link" href="#listing-demo-length-validation">代码清单 2.13</a> 中的代码。</p>
<div id="listing-demo-length-validation" data-type="listing">
<h5><span class="title-label">代码清单 2.13</span>:限制微博的长度最多为 140 个字符</h5>
<div class="source-file">app/models/micropost.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="hll"> <span class="n">validates</span> <span class="ss">:content</span><span class="p">,</span> <span class="ss">length: </span><span class="p">{</span> <span class="ss">maximum: </span><span class="mi">140</span> <span class="p">}</span></span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>这段代码看起来可能很神秘,我们会在 <a class="xref-link" href="chapter6.html#user-validations">6.2 节</a>详细介绍验证。如果我们在发布微博的页面输入超过 140 个字符的内容,就能看到这个验证的作用了。如<a class="xref-link" href="#fig-micropost-length-error">图 2.14</a> 所示,Rails 会渲染错误消息,提示微博的内容太长了。(<a class="xref-link" href="chapter7.html#signup-error-messages">7.3.3 节</a>会详细介绍错误消息。)</p>
<div id="fig-micropost-length-error" class="figure"><img src="images/chapter2/micropost_length_error_3rd_edition.png" alt="micropost length error 3rd edition" /><div class="figcaption"><span class="title-label">图 2.14</span>:发布微博失败时显示的错误消息</div></div>
<h5 id="exercises-putting-the-micro-in-microposts" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>使用<a class="xref-link" href="#exercises-a-micropost-microtour">前一节练习</a>中的那段文字发布微博,这一次有什么变化呢?</p>
</li>
<li>
<p>使用浏览器中的 HTML 审查工具找到前一题那个错误消息的 CSS ID。</p>
</li>
</ol>
</section>
<section data-type="sect2" id="a-user-has-many-microposts">
<h2><span class="title-label">2.3.3</span> 一个用户拥有多篇微博</h2>
<p>Rails 最强大的功能之一,是可以在不同的数据模型之间建立关联(association)。对这里的 <code>User</code> 模型而言,每个用户可以拥有多篇微博。我们可以更新 <code>User</code> 模型(参见<a class="xref-link" href="#listing-demo-user-has-many-microposts">代码清单 2.14</a>)和 <code>Micropost</code> 模型(参见<a class="xref-link" href="#listing-demo-micropost-belongs-to-user">代码清单 2.15</a>)的代码实现这种关联。</p>
<div id="listing-demo-user-has-many-microposts" data-type="listing">
<h5><span class="title-label">代码清单 2.14</span>:一个用户拥有多篇微博</h5>
<div class="source-file">app/models/user.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="hll"> <span class="n">has_many</span> <span class="ss">:microposts</span></span>
<span class="k">end</span>
</code></pre></div>
</div>
<div id="listing-demo-micropost-belongs-to-user" data-type="listing">
<h5><span class="title-label">代码清单 2.15</span>:一篇微博属于一个用户</h5>
<div class="source-file">app/models/micropost.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="hll"> <span class="n">belongs_to</span> <span class="ss">:user</span></span>
<span class="n">validates</span> <span class="ss">:content</span><span class="p">,</span> <span class="ss">length: </span><span class="p">{</span> <span class="ss">maximum: </span><span class="mi">140</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div>
</div>
<p>我们可以把这种关联用<a class="xref-link" href="#fig-micropost-user-association">图 2.15</a> 表示出来。因为 <code>microposts</code> 表中有 <code>user_id</code> 这一列,所以 Rails(通过 Active Record)能把微博和各个用户关联起来。</p>
<div id="fig-micropost-user-association" class="figure"><img src="images/chapter2/micropost_user_association.png" alt="micropost user association" /><div class="figcaption"><span class="title-label">图 2.15</span>:微博和用户之间的关联</div></div>
<p>在<a class="xref-link" href="chapter13.html#user-microposts">第 13 章</a>和<a class="xref-link" href="chapter14.html#following-users">第 14 章</a>,我们会使用微博和用户之间的关联显示用户的所有微博,还会生成一个和 Twitter 类似的微博列表。现在,我们可以在控制台(console)中检查用户与微博之间的关联。控制台是与 Rails 应用交互常用的工具。在命令行中执行 <code>rails console</code> 命令,启动控制台。然后输入 <code>User.first</code>,从数据库中检索第一个用户,并把得到的数据赋值给 <code>first_user</code> 变量:<sup>[<a id="fn-ref-7" href="#fn-7">7</a>]</sup></p>
<div data-type="listing">
<div class="highlight language-irb"><pre><code><span class="hll"><span class="go">$ rails console</span></span>
<span class="go">>> first_user = User.first</span>
<span class="p">=></span> <span class="kt">#<</span><span class="no">User</span> <span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">name: </span><span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email: </span><span class="s2">"michael@example.org"</span><span class="p">,</span>
<span class="ss">created_at: </span><span class="s2">"2016-05-15 02:01:31"</span><span class="p">,</span> <span class="ss">updated_at: </span><span class="s2">"2016-05-15 02:01:31"</span><span class="kt">></span>
<span class="o">>></span> <span class="n">first_user</span><span class="p">.</span><span class="nf">microposts</span>
<span class="o">=></span> <span class="p">[</span><span class="kt">#<</span><span class="no">Micropost</span> <span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">content: </span><span class="s2">"First micropost!"</span><span class="p">,</span> <span class="ss">user_id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">created_at:</span>
<span class="s2">"2016-05-15 02:37:37"</span><span class="p">,</span> <span class="ss">updated_at: </span><span class="s2">"2016-05-15 02:37:37"</span><span class="kt">></span><span class="p">,</span> <span class="kt">#<</span><span class="no">Micropost</span> <span class="ss">id: </span><span class="mi">2</span><span class="p">,</span>
<span class="ss">content: </span><span class="s2">"Second micropost"</span><span class="p">,</span> <span class="ss">user_id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">created_at: </span><span class="s2">"2016-05-15 02:38:54"</span><span class="p">,</span>
<span class="ss">updated_at: </span><span class="s2">"2016-05-15 02:38:54"</span><span class="kt">></span><span class="p">]</span>
<span class="o">>></span> <span class="n">micropost</span> <span class="o">=</span> <span class="n">first_user</span><span class="p">.</span><span class="nf">microposts</span><span class="p">.</span><span class="nf">first</span> <span class="c1"># 使用 Micropost.first 也可以</span>
<span class="o">=></span> <span class="kt">#<</span><span class="no">Micropost</span> <span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">content: </span><span class="s2">"First micropost!"</span><span class="p">,</span> <span class="ss">user_id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">created_at:</span>
<span class="s2">"2016-05-15 02:37:37"</span><span class="p">,</span> <span class="ss">updated_at: </span><span class="s2">"2016-05-15 02:37:37"</span><span class="kt">></span>
<span class="o">>></span> <span class="n">micropost</span><span class="p">.</span><span class="nf">user</span>
<span class="o">=></span> <span class="kt">#<</span><span class="no">User</span> <span class="ss">id: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">name: </span><span class="s2">"Michael Hartl"</span><span class="p">,</span> <span class="ss">email: </span><span class="s2">"michael@example.org"</span><span class="p">,</span>
<span class="ss">created_at: </span><span class="s2">"2016-05-15 02:01:31"</span><span class="p">,</span> <span class="ss">updated_at: </span><span class="s2">"2016-05-15 02:01:31"</span><span class="kt">></span>
<span class="o">>></span> <span class="nb">exit</span>
</code></pre></div>
</div>
<p>(我在这段代码的最后一行加上了 <code>exit</code>,告诉你如何退出控制台。在大多数系统中也可以按 Ctrl-D 键退出控制台。)<sup>[<a id="fn-ref-8" href="#fn-8">8</a>]</sup>我们使用 <code>first_user.microposts</code> 获取这个用户发布的微博。Active Record 会自动返回 <code>user_id</code> 的值与 <code>first_user</code> 的 ID(<code>1</code>)相同的所有微博。在<a class="xref-link" href="chapter13.html#user-microposts">第 13 章</a>和<a class="xref-link" href="chapter14.html#following-users">第 14 章</a>中,我们会更深入地学习关联。</p>
<h5 id="exercises-a-user-has-many-microposts" class="discrete">练习</h5>
<ol class="arabic">
<li>
<p>编辑显示用户的页面,显示用户发布的第一篇微博。(根据文件中的其他内容猜测所需的句法。)访问 /users/1,确认改动是正确的。</p>
</li>
<li>
<p><a class="xref-link" href="#listing-validates-content-presence">代码清单 2.16</a> 添加了一个存在性验证,确保微博的内容不能为空。确认这个验证的行为与<a class="xref-link" href="#fig-micropost-content-cant-be-blank">图 2.16</a> 中一样。</p>
</li>
<li>
<p>把<a class="xref-link" href="#listing-toy-user-presence-valications">代码清单 2.17</a> 中的 <code>FILL_IN</code> 换成相应的代码,为 <code>User</code> 模型的 <code>name</code> 和 <code>email</code> 属性添加存在性验证。效果如<a class="xref-link" href="#fig-user-presence-validations">图 2.17</a> 所示。</p>
</li>
</ol>
<div id="listing-validates-content-presence" data-type="listing">
<h5><span class="title-label">代码清单 2.16</span>:验证微博内容存在性的代码</h5>
<div class="source-file">app/models/micropost.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:user</span>
<span class="n">validates</span> <span class="ss">:content</span><span class="p">,</span> <span class="ss">length: </span><span class="p">{</span> <span class="ss">maximum: </span><span class="mi">140</span> <span class="p">},</span>
<span class="hll"> <span class="ss">presence: </span><span class="kp">true</span></span>
<span class="k">end</span>
</code></pre></div>
</div>
<div id="listing-toy-user-presence-valications" data-type="listing">
<h5><span class="title-label">代码清单 2.17</span>:为 <code>User</code> 模型添加存在性验证</h5>
<div class="source-file">app/models/user.rb</div>
<div class="highlight language-ruby"><pre><code><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_many</span> <span class="ss">:microposts</span>
<span class="hll"> <span class="n">validates</span> <span class="no">FILL_IN</span><span class="p">,</span> <span class="ss">presence: </span><span class="kp">true</span> <span class="c1"># 把 FILL_IN 换成正确的代码</span></span>
<span class="hll"> <span class="n">validates</span> <span class="no">FILL_IN</span><span class="p">,</span> <span class="ss">presence: </span><span class="kp">true</span> <span class="c1"># 把 FILL_IN 换成正确的代码</span></span>
<span class="k">end</span>
</code></pre></div>
</div>
<div id="fig-micropost-content-cant-be-blank" class="figure"><img src="images/chapter2/micropost_content_cant_be_blank.png" alt="micropost content cant be blank" /><div class="figcaption"><span class="title-label">图 2.16</span>:微博内容存在性验证的效果</div></div>
<div id="fig-user-presence-validations" class="figure"><img src="images/chapter2/user_presence_validations.png" alt="user presence validations" /><div class="figcaption"><span class="title-label">图 2.17</span>:<code>User</code> 模型存在性验证的效果</div></div>
</section>
<section data-type="sect2" id="inheritance-hierarchies">
<h2><span class="title-label">2.3.4</span> 继承体系</h2>
<p>接下来简要介绍 Rails 中控制器和模型的类继承。
如果你有面向对象编程(Object-oriented Programming,简称 OOP)的经验,尤其是类,能更好地理解这些内容。如果暂时不理解,也没关系,<a class="xref-link" href="chapter4.html#ruby-classes">4.4 节</a>会详细说明这些概念。</p>
<p>我们先介绍模型的继承体系。对比一下<a class="xref-link" href="#listing-demo-user-class">代码清单 2.18</a> 和<a class="xref-link" href="#listing-demo-micropost-class">代码清单 2.19</a>,可以看出,<code>User</code> 和 <code>Micropost</code> 都(通过 <code><</code> 符号)继承自 <code>ApplicationRecord</code> 类,而这个类继承自 <code>ActiveRecord::Base</code> 类,这是 Active Record 为模型提供的基类。<a class="xref-link" href="#fig-demo-model-inheritance">图 2.18</a> 列出了这种继承关系。继承 <code>ActiveRecord::Base</code> 类,模型对象才能与数据库通讯,才能把数据库中的列看做 Ruby 中的属性,等等。</p>
<div id="listing-demo-user-class" data-type="listing">
<h5><span class="title-label">代码清单 2.18</span>:<code>User</code> 类的继承关系</h5>
<div class="source-file">app/models/user.rb</div>
<div class="highlight language-ruby"><pre><code><span class="hll"><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ApplicationRecord</span></span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<div id="listing-demo-micropost-class" data-type="listing">
<h5><span class="title-label">代码清单 2.19</span>:<code>Mcropost</code> 类的继承关系</h5>
<div class="source-file">app/models/micropost.rb</div>
<div class="highlight language-ruby"><pre><code><span class="hll"><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ApplicationRecord</span></span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<div id="fig-demo-model-inheritance" class="figure"><img src="images/chapter2/demo_model_inheritance_4th_ed.png" alt="demo model inheritance 4th ed" /><div class="figcaption"><span class="title-label">图 2.18</span>:<code>User</code> 模型和 <code>Micropost</code> 模型的继承体系</div></div>
<p>控制器的继承结构与模型基本相同。对比<a class="xref-link" href="#listing-demo-users-controller-class">代码清单 2.20</a> 和<a class="xref-link" href="#listing-demo-microposts-controller-class">代码清单 2.21</a>,可以看出,<code>UsersController</code> 和 <code>MicropostsController</code> 都继承自 <code>ApplicationController</code>。如<a class="xref-link" href="#listing-toy-application-controller-class">代码清单 2.22</a> 所示,<code>ApplicationController</code> 继承自 <code>ActionController::Base</code>。<code>ActionController::Base</code> 是 Rails 中 Action Pack 库为控制器提供的基类。这些类之间的关系如<a class="xref-link" href="#fig-demo-controller-inheritance">图 2.19</a> 所示。</p>
<div id="listing-demo-users-controller-class" data-type="listing">
<h5><span class="title-label">代码清单 2.20</span>:<code>UsersController</code> 类中的继承</h5>
<div class="source-file">app/controllers/users_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="hll"><span class="k">class</span> <span class="nc">UsersController</span> <span class="o"><</span> <span class="no">ApplicationController</span></span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<div id="listing-demo-microposts-controller-class" data-type="listing">
<h5><span class="title-label">代码清单 2.21</span>:<code>MicropostsController</code> 类中的继承</h5>
<div class="source-file">app/controllers/microposts_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="hll"><span class="k">class</span> <span class="nc">MicropostsController</span> <span class="o"><</span> <span class="no">ApplicationController</span></span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<div id="listing-toy-application-controller-class" data-type="listing">
<h5><span class="title-label">代码清单 2.22</span>:<code>ApplicationController</code> 类中的继承</h5>
<div class="source-file">app/controllers/application_controller.rb</div>
<div class="highlight language-ruby"><pre><code><span class="hll"><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>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</code></pre></div>
</div>
<div id="fig-demo-controller-inheritance" class="figure"><img src="images/chapter2/demo_controller_inheritance.png" alt="demo controller inheritance" /><div class="figcaption"><span class="title-label">图 2.19</span>:<code>UsersController</code> 和 <code>MicropostsController</code> 的继承体系</div></div>
<p>与模型的继承类似,通过继承 <code>ActionController::Base</code>,<code>Users</code> 控制器和 <code>Microposts</code> 控制器获得了很多功能。例如,处理模型对象的能力、过滤入站 HTTP 请求,以及把视图渲染成 HTML 的能力。Rails 应用中的所有控制器都继承自 <code>ApplicationController</code>,所以其中定义的规则会自动运用于应用中的每个动作。例如,<a class="xref-link" href="chapter9.html#remember-me">9.1 节</a>会介绍如何在 <code>Application</code> 控制器中引入辅助方法,为整个应用的所有控制器都加上登录和退出功能。</p>