-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathchapter2.html
940 lines (798 loc) · 62.9 KB
/
chapter2.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
<!doctype html>
<html lang="zh_CN">
<head>
<meta charset="utf-8" />
<title>第 2 章 演示程序</title>
<meta name="author" content="Andor Chen" />
<link rel="stylesheet" href="assets/styles/style.css" />
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="assets/js/global.js"></script>
</head>
<body>
<div class="wrapper">
<div class="header">
<h1 class="logo"><a class="ir" href="http://railstutorial-china.org/rails4">Ruby on Rails 教程</a></h1>
<p class="subtitle">Ruby on Rails Tutorial 原书第 2 版(涵盖 Rails 4)</p>
</div>
<div class="content">
<div class="item chapter">
<h1 id="chapter-2"><span>第 2 章</span> 演示程序</h1>
<ol class="toc"> <li class="level-2">
<a href="#section-2-1">2.1 规划程序</a>
</li>
<li class="level-3">
<a href="#section-2-1-1">2.1.1 用户模型</a>
</li>
<li class="level-3">
<a href="#section-2-1-2">2.1.2 微博模型</a>
</li>
<li class="level-2">
<a href="#section-2-2">2.2 Users 资源(users resource)</a>
</li>
<li class="level-3">
<a href="#section-2-2-1">2.2.1 浏览用户相关的页面</a>
</li>
<li class="level-3">
<a href="#section-2-2-2">2.2.2 MVC 实践</a>
</li>
<li class="level-3">
<a href="#section-2-2-3">2.2.3 上述 Users 资源的缺陷</a>
</li>
<li class="level-2">
<a href="#section-2-3">2.3 Microposts 资源</a>
</li>
<li class="level-3">
<a href="#section-2-3-1">2.3.1 概览 Microposts 资源</a>
</li>
<li class="level-3">
<a href="#section-2-3-2">2.3.2 限制微博内容的长度</a>
</li>
<li class="level-3">
<a href="#section-2-3-3">2.3.3 一个用户有多篇微博</a>
</li>
<li class="level-3">
<a href="#section-2-3-4">2.3.4 继承关系</a>
</li>
<li class="level-3">
<a href="#section-2-3-5">2.3.5 部署演示程序</a>
</li>
<li class="level-2">
<a href="#section-2-4">2.4 小结</a>
</li>
</ol>
<div class="main">
<p>本章我们要开发一个简单的演示应用程序来展示一下 Rails 强大的功能。我们会使用脚手架(scaffold)功能快速的生成程序,这样就能以一定的高度概览一下 Ruby on Rails 编程的过程(也能大致的了解一下 Web 开发)。正如在第一章的<a href="chapter1.html#aside-1-1">旁注 1.1</a> 中所说,本书将采用另一种方法,我们会循序渐进的开发程序,遇到新的概念都会详细说明,不过为了概览功能(也为了寻找成就感)也无需对脚手架避而不谈。我们可以通过 URL 和最终的演示程序进行交互,了解一下 Rails 应用程序的结构,也第一次演示 Rails 使用的 REST 架构。</p>
<p>和后面的大型示例程序类似,这个演示程序将包含用户(users)和微博(microposts)两个模型(因此实现了一个小型的 Twitter 类程序)。程序的功能还需要后续的开发,而且开发过程中的很多步骤看起来也很神秘,不过暂时不用担心:从第三章起将从零开始再开发一个类似的程序,我还会提供大量的资料供后续参考。你要有些耐心,不要怕多犯错误,本章的主要目的就是让你不要被脚手架的神奇迷惑住了,而要更深入的了解 Rails。</p>
<h2 id='section-2-1'><span>2.1</span> 规划程序</h2>
<p>在这一节我们要规划一下这个演示程序。和 <a href="chapter1.html#section-1-2-3">1.2.3 节</a>类似,我们先使用 <code>rails</code> 命令生成程序的骨架。</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span><span class="nb">cd</span> ~/rails_projects
<span class="gp">$ </span>rails new demo_app
<span class="gp">$ </span><span class="nb">cd </span>demo_app
</pre></div>
</div>
<p>然后我们用一个文本编辑器修改 <code>Gemfile</code>,写入代码 2.1 所示的代码。</p>
<div class="codeblock has-caption" id="codeblock-2-1"><p class="caption"><span>代码 2.1:</span>演示程序的 <code>Gemfile</code></p><div class="highlight type-ruby"><pre><span class="n">source</span> <span class="s1">'https://rubygems.org'</span>
<span class="n">ruby</span> <span class="s1">'2.0.0'</span>
<span class="n">gem</span> <span class="s1">'rails'</span><span class="p">,</span> <span class="s1">'4.0.4'</span>
<span class="n">group</span> <span class="p">:</span><span class="n">development</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.8'</span>
<span class="k">end</span>
<span class="n">gem</span> <span class="s1">'sass-rails'</span><span class="p">,</span> <span class="s1">'4.0.1'</span>
<span class="n">gem</span> <span class="s1">'uglifier'</span><span class="p">,</span> <span class="s1">'2.1.1'</span>
<span class="n">gem</span> <span class="s1">'coffee-rails'</span><span class="p">,</span> <span class="s1">'4.0.1'</span>
<span class="n">gem</span> <span class="s1">'jquery-rails'</span><span class="p">,</span> <span class="s1">'2.2.1'</span>
<span class="n">gem</span> <span class="s1">'turbolinks'</span><span class="p">,</span> <span class="s1">'1.1.1'</span>
<span class="n">gem</span> <span class="s1">'jbuilder'</span><span class="p">,</span> <span class="s1">'1.0.2'</span>
<span class="n">group</span> <span class="p">:</span><span class="n">doc</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s1">'sdoc'</span><span class="p">,</span> <span class="s1">'0.3.20'</span><span class="p">,</span> <span class="ss">require: </span><span class="kp">false</span>
<span class="k">end</span>
<span class="n">group</span> <span class="p">:</span><span class="n">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.15.1'</span>
<span class="k">end</span>
</pre></div>
</div>
<p>注意,代码 2.1 和代码 1.9 是一样的。</p>
<p>和 <a href="chapter1.html#section-1-4-1">1.4.1 节</a>一样,在本地安装 gem 时指定 <code>--without production</code> 选项不安装生产环境所需的 gem:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle install --without production
<span class="gp">$ </span>bundle update
<span class="gp">$ </span>bundle install
</pre></div>
</div>
<p>(再次提醒,如果 Bundler 提示一个和 <code>readline</code> 有关的错误,请在 <code>Gemfile</code> 中加入 <code>gem rb-readline</code>。)</p>
<div class="figure" id="figure-2-1">
<img src="figures/create_demo_repo_4_0.png" alt="create demo repo 40" />
<p class="caption"><span>图 2.1:</span>为演示程序在 GitHub 新建一个仓库</p>
</div>
<p>最后我们还要把演示程序纳入版本控制。提醒一下,<code>rails</code> 命令会生成一个默认的 <code>.gitignore</code> 文件,不过对于你所使用的系统而言代码 1.7 中的代码似乎更有用。然后初始化一个 Git 仓库,做第一次提交:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>git init
<span class="gp">$ </span>git add .
<span class="gp">$ </span>git commit -m <span class="s2">"Initial commit"</span>
</pre></div>
</div>
<p>你可以重新创建一个仓库然后将代码推送到 GitHub:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>git remote add origin https://github.com/<username>/demo_app.git
<span class="gp">$ </span>git push -u origin master
</pre></div>
</div>
<p>(和第一章中的程序一样,注意不要使用 GitHub 自动生成的 <code>README</code> 文件初始化仓库。)</p>
<p>下面要开发这个程序了。开发 Web 应用程序一般来说第一步是创建数据模型(data model),模型代表应用程序所需的结构。对我们这个程序而言,它是个轻博客,有用户和微博。那么我们先为程序创建一个用户(users)模型(<a href="chapter2.html#section-2-1-1">2.1.1 节</a>),然后再添加微博(microposts)模型(<a href="chapter2.html#section-2-1-2">2.1.2 节</a>)。</p>
<h3 id='section-2-1-1'><span>2.1.1</span> 用户模型</h3>
<p>不同的注册表单代表了不同的用户数据模型,我们将选择一种简化的模型。这个演示程序的用户要有一个唯一的标识符 <code>id</code>(整数 <code>integer</code>),一个对外显示的名字 <code>name</code>(字符串 <code>string</code>),还有一个 Email 地址 <code>email</code>(字符串 <code>string</code>),它将同时兼任用户名。用户模型的结构如图 2.2。</p>
<div class="figure" id="figure-2-2">
<img src="figures/demo_user_model.png" alt="demo user model" />
<p class="caption"><span>图 2.2:</span>用户数据模型</p>
</div>
<p>我们会在 <a href="chapter6.html#section-6-1-1">6.1.1 节</a>中介绍,图 2.2 中的标签 <code>users</code> 代表数据库中的一个表,<code>id</code>、<code>name</code> 和 <code>email</code> 是表中的列。</p>
<h3 id='section-2-1-2'><span>2.1.2</span> 微博模型</h3>
<p>微博数据模型的核心比用户的模型还要简单:微博要有一个 <code>id</code> 和一个内容 <code>content</code>(字符串 <code>string</code>)。<sup class="footnote" id="fnref-2-1"><a href="#fn-2-1" rel="footnote">1</a></sup> 不过还有一个比较复杂的数据要实现:将微博和用户关联起来,我们使用 <code>user_id</code> 来存储微博的拥有者。最终的数据模型如图 2.3。</p>
<div class="figure" id="figure-2-3">
<img src="figures/demo_micropost_model.png" alt="demo micropost model" />
<p class="caption"><span>图 2.3:</span>微博的数据模型</p>
</div>
<p>在 <a href="chapter2.html#section-2-3-3">2.3.3 节</a>中我们会看到怎样使用 <code>user_id</code> 字段简单的实现一个用户拥有多个微博的功能(<a href="chapter10.html">第 10 章</a>会做更详尽的介绍)。</p>
<h2 id='section-2-2'><span>2.2</span> Users 资源(users resource)</h2>
<p>本节我们将要实现 <a href="chapter2.html#section-2-1-1">2.1.1 节</a>中设定的用户数据模型,还会为这个模型创建基于网页的界面。这二者结合起来就是一个“Users 资源”,“资源”的意思是将用户设想为对象,可以通过 HTTP 协议在网页中创建(create)、读取(read)、更新(update)和删除(delete)。正如前面提到的,我们的 Users 资源会使用脚手架功能生成,Rails 内置了这样的功能。我强烈建议你先不要细看生成的代码,在这个时候看只会让你更困惑。</p>
<p>将 <code>scaffold</code> 传递给 <code>rails generate</code> 就可以使用 Rails 的脚手架功能了。传给 <code>scaffold</code> 的参数是资源名的单数形式(本例中就是 <code>User</code>),后面可以再跟着指定数据模型的字段:<sup class="footnote" id="fnref-2-2"><a href="#fn-2-2" rel="footnote">2</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails generate scaffold User name:string email:string
invoke active_record
create db/migrate/20130305221714_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 jbuilder_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
create <span class="nb">test</span>/helpers/users_helper_test.rb
invoke jbuilder
exist app/views/users
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/users.js.coffee
invoke scss
create app/assets/stylesheets/users.css.scss
invoke scss
create app/assets/stylesheets/scaffolds.css.scss
</pre></div>
</div>
<p>上面代码中的命令加入了 <code>name:string</code> 和 <code>email:string</code>,这样我们就可以实现如图 2.2 所示的用户模型了。(注意没必要指定 <code>id</code>,Rails 会自动创建并将其设为表的主键(primary key)。)</p>
<p>接下来我们要用 Rake(参见<a href="chapter2.html#aside-2-1">旁注 2.1</a>)来迁移(migrate)数据库:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rake db:migrate
<span class="o">==</span> CreateUsers: migrating <span class="o">====================================================</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>
</pre></div>
</div>
<p>上面的命令会使用新定义的 User 数据模型更新数据库。(在 <a href="chapter6.html#section-6-1-1">6.1.1 节</a>中将详细介绍数据库迁移)注意,为了使用 <code>Gemfile</code> 中指定的 Rake 版本,我们通过 <code>bundle exec</code> 来执行 <code>rake</code>。(如果你使用 RVM,如前一章所说,可以不加 <code>bundle exec</code>,但为了命令完整,我会一直都加上。不加 <code>bundle exec</code> 还有其他办法,参见 <a href="chapter3.html#section-3-6-1">3.6.1 节</a>。)</p>
<p>然后我们可以使用 <code>rails s</code>(<code>rails server</code> 的缩略形式)来启动本地服务器:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails s
</pre></div>
</div>
<p>现在演示程序应该已经可以通过 <a href="http://localhost:3000/">http://localhost:3000/</a> 查看了。</p>
<div class="aside box" id="aside-2-1">
<h4><span>旁注 2.1:</span>Rake</h4>
<p>在 Unix 中,在将源码编译成可执行程序的过程中,<a href="http://en.wikipedia.org/wiki/Make_(software)">make</a> 组件起了很重要的作用。很多程序员的身体甚至已经对下面的代码产生了条件反射</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>./configure <span class="o">&&</span> make <span class="o">&&</span> sudo make install
</pre></div>
</div>
<p>这行代码在 Unix 中(包括 Linux 和 Mac OS X)会对代码进行编译。</p>
<p>Rake 就是 Ruby 版的 make,用 Ruby 编写的类 make 程序。Rails 灵活的运用了 Rake 的功能,特别是提供了一些用来开发基于数据库的 Web 程序所需的任务。<code>rake db:migrate</code> 是最常用的了,还有很多其他的命令,你可以运行 <code>rake -T db</code> 来查看所有和数据库有关的任务:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rake -T db
</pre></div>
</div>
<p>如果要查看所有的 Rake 任务,运行</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rake -T
</pre></div>
</div>
<p>任务列表看起来有点让人摸不着头脑,不过现在无需担心,你不需要知道所有的(或大多数)命令。学完本教程后你会知道所有重要的命令。</p>
</div>
<div class="figure" id="figure-2-4">
<img src="figures/demo_blank_user_index_rails_3.png" alt="demo blank user index rails3" />
<p class="caption"><span>图 2.4:</span>Users 资源的初始索引页面(<a href="http://localhost:3000/users">/users</a>)</p>
</div>
<h3 id='section-2-2-1'><span>2.2.1</span> 浏览用户相关的页面</h3>
<p>访问根地址 <a href="http://localhost:3000/">http://localhost:3000/</a> 得到的还是如图 1.3 所示的 Rails 程序默认页面,不过使用脚手架生成 Users 资源的时候也生成了很多用来处理用户的页面。例如,列出所有用户的页面地址是 <a href="http://localhost:3000/users">/users</a>,创建新用户的地址是 <a href="http://localhost:3000/users/new">/users/new</a>。本节的目的就是走马观花的浏览一下这些用户相关的页面。浏览的时候你会发现表格 2.1 很有用,表中显示了页面和 URL 地址之间的对应关系。</p>
<p>我们先来看一下显示所有用户的页面,叫做“index”,如你所想,目前还没有用户存在。(如图 2.4)</p>
<p>如果想创建新用户就要访问“new”页面,如图 2.5 所示。(在本地开发时,地址的前面部分都是 http://localhost:3000,因此在后面的内容中我会省略这一部分)在第七章中我们会将其改造成用户注册页面。</p>
<div class="table has-caption" id="table-2-1"><p class="caption"><span>表格 2.1:</span>Users 资源中页面和 URL 的对应关系</p><table>
<thead>
<tr>
<th>URL</th>
<th>动作(Action)</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://localhost:3000/users">/users</a></td>
<td><code>index</code></td>
<td>显示所有用户的页面</td>
</tr>
<tr>
<td><a href="http://localhost:3000/users/1">/users/1</a></td>
<td><code>show</code></td>
<td>显示 ID 为 1 的用户的页面</td>
</tr>
<tr>
<td><a href="http://localhost:3000/users/new">/users/new</a></td>
<td><code>new</code></td>
<td>创建新用户的页面</td>
</tr>
<tr>
<td><a href="http://localhost:3000/users/1/edit">/users/1/edit</a></td>
<td><code>edit</code></td>
<td>编辑 ID 为 1 的用户的页面</td>
</tr>
</tbody>
</table>
</div>
<div class="figure" id="figure-2-5">
<img src="figures/demo_new_user_rails_3.png" alt="demo new user rails3" />
<p class="caption"><span>图 2.5:</span>创建新用户的页面(<a href="http://localhost:3000/users/new">/users/new</a>)</p>
</div>
<p>你可以在表格中填入名字和 Email 地址,然后点击创建用户(Create User)按钮来创建一个用户。然后就会显示这个用户的页面(<code>show</code>),如图 2.6 所示。(页面中的绿色文字是通过 Flash 消息实现的,会在 <a href="chapter7.html#section-7-4-2">7.4.2 节</a>中介绍)注意页面的地址是 <a href="http://localhost:3000/users/1">/users/1</a>,正如你猜想的,这里的 <code>1</code> 就是图 2.2 中的用户 <code>id</code>。在 <a href="chapter7.html#section-7-1">7.1 节</a> 中会将其打造成用户的资料页面。</p>
<p>如果要修改用户的信息就要访问编辑(edit)页面了(如图 2.7)。修改用户的信息后点击“更新用户(Update User)”按钮就更改了演示程序中该用户的信息(如图 2.8)。(在<a href="chapter6.html">第 6 章</a>我们会看到,用户的数据存储在后端的数据库中。)我们会在 <a href="chapter9.html#section-9-1">9.1 节</a>中添加编辑和更新用户的功能。</p>
<div class="figure" id="figure-2-6">
<img src="figures/demo_show_user_rails_3.png" alt="demo show user rails3" />
<p class="caption"><span>图 2.6:</span>显示某个用户的页面(<a href="http://localhost:3000/users/1">/users/1</a>)</p>
</div>
<p>现在我们重新回到创建新用户页面,然后提交表格创建第二个用户。然后访问用户索引(index)页面(如图 2.9)。<a href="chapter7.html#section-7-1">7.1 节</a>将美化一下这个显示所有用户的页面。</p>
<p>我们已经演示了创建、展示、编辑用户的页面,下面要演示销毁用户页面了(如图 2.10)。点击图 2.10 中的链接会出现一个验证对话框,确认后就会删除第二个用户,索引页面就只会显示一个用户。(如果这个操作没有顺利完成,请确保浏览器启用了 JavaScript 支持。销毁用户时 Rails 是通过 JavaScript 发送请求的。)<a href="chapter9.html#section-9-4">9.4 节</a>会增强用户的删除功能,只有管理员级别的用户才能删除用户。</p>
<h3 id='section-2-2-2'><span>2.2.2</span> MVC 实践</h3>
<p>我们已经大概的浏览了 Users 资源,下面我们要用 <a href="chapter1.html#section-1-2-6">1.2.6 节</a>中介绍的 MVC 的视角来仔细的看一下其中某些特定的部分。我们会分析在浏览器中做一次点击的内在过程,这里通过访问用户索引页面做演示,来了解一下 MVC。(如图 2.11)</p>
<ol>
<li>浏览器向 /users 发起一个请求;</li>
<li>Rails 的路由将 /user 分配到 Users 控制器的 <code>index</code> 动作;</li>
<li><code>index</code> 动作向 User 模型获取所有的用户(<code>User.all</code>);</li>
<li>User 模型从数据库中将所有的用户读取出来;</li>
<li>User 模型将所有的用户返回给控制器;</li>
<li>控制器将获得的所有用户数据赋予 <code>@users</code> 变量,然后传递给 <code>index</code> 的视图;</li>
<li>视图使用内嵌 Ruby 代码的模板渲染成 HTML;</li>
<li>控制器将生成的 HTML 发送回浏览器。<sup class="footnote" id="fnref-2-3"><a href="#fn-2-3" rel="footnote">3</a></sup></li>
</ol>
<p>首先我们要从浏览器中发起一个请求,你可以直接在浏览器地址栏中敲入地址,也可以点击页面中的链接。(图 2.11 中的第 1 步)接着请求到达 Rails 路由(第 2 步),根据 URL 将其分发到适当的控制器动作(而且还会考量请求的类型,<a href="chapter3.html#aside-3-2">旁注 3.2</a> 中会介绍)。将 Users 资源中相关的 URL 映射到控制器动作的代码如代码 2.2 所示。这些代码会按照表格 2.1 中的对应关系做映射。(<code>:users</code> 是一个 Symbol,<a href="chapter4.html#section-4-3-3">4.3.3 节</a>会介绍)</p>
<div class="codeblock has-caption" id="codeblock-2-2"><p class="caption"><span>代码 2.2:</span>Rails 的路由设置,包含一条 Users 资源的规则</p><p class="file"><code>config/routes.rb</code></p><div class="highlight type-ruby"><pre><span class="no">DemoApp</span><span class="o">::</span><span class="no">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="p">:</span><span class="n">users</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<div class="figure" id="figure-2-7">
<img src="figures/demo_edit_user_rails_3.png" alt="demo edit user rails3" />
<p class="caption"><span>图 2.7:</span>编辑用户的页面(<a href="http://localhost:3000/users/1/edit">/users/1/edit</a>)</p>
</div>
<p><a href="chapter2.html#section-2-2-1">2.2.1 节</a>中浏览的页面就对应了 Users 控制器中不同的动作。脚手架生成的控制器代码大致如代码 2.3 所示。注意一下 <code>class UsersController < ApplicationController</code> 的用法,这是 Ruby 中类继承的写法。(<a href="chapter2.html#section-2-3-4">2.3.4 节</a>中将简要的介绍一下继承,<a href="chapter4.html#section-4-4">4.4 节</a>将详细介绍类和继承。)</p>
<div class="codeblock has-caption" id="codeblock-2-3"><p class="caption"><span>代码 2.3:</span>用户控制器的代码概要</p><p class="file"><code>app/controllers/users_controller.rb</code></p><div class="highlight type-ruby"><pre><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">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">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">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>
</pre></div>
</div>
<div class="figure" id="figure-2-8">
<img src="figures/demo_update_user_rails_3.png" alt="demo update user rails3" />
<p class="caption"><span>图 2.8:</span>显示“信息已更新”提示的用户页面</p>
</div>
<p>或许你发现了动作的数量比我们看过的页面数量要多,<code>index</code>、<code>show</code>、<code>new</code> 和 <code>edit</code> 对应了 <a href="chapter2.html#section-2-2-1">2.2.1 节</a>中介绍的页面。不过还有一些其他的动作,<code>create</code>、<code>update</code> 和 <code>destroy</code> 等,这些动作一般不会直接渲染页面(不过有时也会),它们只会修改数据库中保存的用户数据。表格 2.2 列出的是控制器的全部动作,这些动作就是 Rails 对 REST 架构(参见<a href="chapter2.html#aside-2-2">旁注 2.2</a>)的实现。REST 是由计算机科学家 <a href="http://en.wikipedia.org/wiki/Roy_Fielding">Roy Fielding</a> 提出的概念,意思是表现层状态转化(Representational State Transfer)。<sup class="footnote" id="fnref-2-4"><a href="#fn-2-4" rel="footnote">4</a></sup> 注意表格 2.2 中的内容,有些部分是有重叠的。例如 <code>show</code> 和 <code>update</code> 两个动作都映射到 /users/1 这个地址上。二者的区别是它们所用的 <a href="http://en.wikipedia.org/wiki/HTTP_request#Request_methods">HTTP 请求方法</a>不同。<a href="chapter3.html#section-3-2-1">3.2.1 节</a>将更详细的介绍 HTTP 请求方法。</p>
<div class="aside box" id="aside-2-2">
<h4><span>旁注 2.2:</span>表现层状态转化(REST)</h4>
<p>如果你阅读过一些 Ruby on Rails Web 开发相关的资料,你会看到很多地方都提到了“REST”,它是“表现层状态转化(REpresentational State Transfer)”的简称。REST 是一种架构方式,用来开发分布式、基于网络的系统和程序,例如 WWW 和 Web 应用程序。REST 理论是很抽象的,在 Rails 程序中,REST 意味着大多数的组件(例如用户和微博)会被模型化,变成资源(resource),可以被创建(create)、读取(read)、更新(update)和删除(delete),这些操作会与<a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">关系型数据库中的 CRUD 操作</a>和 <a href="http://en.wikipedia.org/wiki/HTTP_request#Request_methods">HTTP 请求方法</a>(<code>POST</code>,<code>GET</code>,<code>PATCH</code> 和 <code>DELETE</code>)对应起来。(<a href="chapter3.html#section-3-2-1">3.2.1 节</a>,特别是<a href="chapter3.html#aside-3-3">旁注 3.3</a>,将更详细的介绍 HTTP 请求)</p>
<p>作为 Rails 程序开发者,REST 开发方式会帮助你决定编写哪些控制器和动作:你只需简单的将可以创建、读取、更新和删除的资源理清就可以了。对本章的用户和微博来说,这一过程非常明确,因为它们都是很自然的资源形式。在<a href="chapter11.html">第 11 章</a>中将看到 REST 架构允许我们将一个很棘手的问题(“关注用户”功能)通过一种自然而便捷的方式处理。</p>
</div>
<div class="figure" id="figure-2-9">
<img src="figures/demo_user_index_two_rails_3.png" alt="demo user index two rails3" />
<p class="caption"><span>图 2.9:</span>显示了第二个用户的用户索引页面(<a href="http://localhost:3000/users">/users</a>)</p>
</div>
<div class="figure" id="figure-2-10">
<img src="figures/demo_destroy_user_rails_3.png" alt="demo destroy user rails3" />
<p class="caption"><span>图 2.10:</span>销毁用户</p>
</div>
<div class="table has-caption" id="table-2-2"><p class="caption"><span>表格 2.2:</span>代码 2.2 中 Users 资源生成的符合 REST 架构的路由</p><table>
<thead>
<tr>
<th>HTTP 请求</th>
<th>URL</th>
<th>动作</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GET</code></td>
<td>/users</td>
<td><code>index</code></td>
<td>显示所用用户的页面</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/users/1</td>
<td><code>show</code></td>
<td>显示 ID 为 1 的用户页面</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/users/new</td>
<td><code>new</code></td>
<td>创建新用户的页面</td>
</tr>
<tr>
<td><code>POST</code></td>
<td>/users</td>
<td><code>create</code></td>
<td>创建新用户</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/users/1/edit</td>
<td><code>edit</code></td>
<td>编辑 ID 为 1 的用户页面</td>
</tr>
<tr>
<td><code>PATCH</code></td>
<td>/users/1</td>
<td><code>update</code></td>
<td>更新 ID 为 1 的用户</td>
</tr>
<tr>
<td><code>DELETE</code></td>
<td>/users/1</td>
<td><code>destroy</code></td>
<td>删除 ID 为 1 的用户</td>
</tr>
</tbody>
</table>
</div>
<div class="figure" id="figure-2-11">
<img src="figures/mvc_detailed.png" alt="mvc detailed" />
<p class="caption"><span>图 2.11:</span>Rails 中 MVC 的详细说明图解</p>
</div>
<p>为了解释 Users 控制器和 User 模型之间的关系,我们要看一下简化了的 <code>index</code> 动作的代码,如代码 2.4 所示。(脚手架生成的代码很粗糙,所以我们做了简化)</p>
<div class="codeblock has-caption" id="codeblock-2-4"><p class="caption"><span>代码 2.4:</span>演示程序中被简化了的用户 <code>index</code> 动作</p><p class="file"><code>app/controllers/users_controller.rb</code></p><div class="highlight type-ruby"><pre><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="vi">@users</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">all</span>
<span class="k">end</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<p><code>index</code> 动作有一行代码是 <code>@users = User.all</code>(图 2.11 中的第 3 步),它要求 User 模型从数据库中取出所有的用户(第 4 步),然后将结果赋值给 <code>@users</code> 变量(第 5 步)。User 模型的代码参见代码 2.5。代码看似简单,不过它通过继承具备了很多功能(参见 <a href="chapter2.html#section-2-3-4">2.3.4 节</a> 和 <a href="chapter4.html#section-4-4">4.4 节</a>)。简单来说就是通过调用 Rails 中叫做 Active Record 的库,代码 2.5 中的 <code>User.all</code> 就会返回所有的用户。</p>
<div class="codeblock has-caption" id="codeblock-2-5"><p class="caption"><span>代码 2.5:</span>演示程序中的 User 模型</p><p class="file"><code>app/models/user.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="k">end</span>
</pre></div>
</div>
<p>一旦定义了 <code>@users</code> 变量,控制器就会调用视图代码(第 6 步),其代码如代码 2.6 所示。以 <code>@</code> 开头的变量是“实例变量(instance variable)”,在视图中自动可用。在本例中,<code>index.html.erb</code> 视图的代码会遍历 <code>@users</code>,为每个用户生成一行 HTML。(记住,你现在可能读不懂这些代码,这里只是让你看一下这些代码是什么样子。)</p>
<div class="codeblock has-caption" id="codeblock-2-6"><p class="caption"><span>代码 2.6:</span>用户索引页面的视图代码</p><p class="file"><code>app/views/users/index.html.erb</code></p><div class="highlight type-erb"><pre><span class="nt"><h1></span>Listing users<span class="nt"></h1></span>
<span class="nt"><table></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></th></span>
<span class="nt"><th></th></span>
<span class="nt"><th></th></span>
<span class="nt"></tr></span>
<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 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="nt">/></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>
</pre></div>
</div>
<p>视图会将代码转换成 HTML(第 7 步),然后控制器将其返回浏览器显示出来(第 8 步)。</p>
<h3 id='section-2-2-3'><span>2.2.3</span> 上述 Users 资源的缺陷</h3>
<p>脚手架生成的 Users 资源相关代码虽然能够让你大致的了解一下 Rails,不过它也有一些缺陷:</p>
<ul>
<li><strong>没有对数据进行验证(validation)。</strong>User 模型会接受空的名字和不合法的 Email 地址而不会报错。</li>
<li><strong>没有用户身份验证机制(authentication)。</strong>没有实现登录和退出功能,随意一个用户都可以进行任何的操作。</li>
<li><strong>没有测试。</strong>也不是完全没有,脚手架会生成一些基本的测试,不过很粗糙也不灵便,没有对数据进行验证,不包含验证机制的测试,以及其他的需求。</li>
<li><strong>没有布局。</strong>没有共用的样式和网站导航。</li>
<li><strong>没有真正的被理解。</strong>如果你能读懂脚手架生成的代码就不需要阅读本书了。</li>
</ul>
<h2 id='section-2-3'><span>2.3</span> Microposts 资源</h2>
<p>我们已经生成也浏览了 User 资源,现在要生成 Microposts 资源了。阅读本节时我推荐你和 <a href="chapter2.html#section-2-2">2.2 节</a>对比一下,你会看到两个资源在很多方面都是一致的。通过这样重复的生成资源我们可以更好的理解 Rails 中的 REST 架构。在这样的早期阶段看一下 Users 资源和 Microposts 资源的相同之处也是本章的主要目的之一。(后面我们会看到,开发一个比本章的演示程序复杂的程序要付出很多汗水,Microposts 资源在第 10 章才会用到,而我不想这么晚才介绍。)</p>
<h3 id='section-2-3-1'><span>2.3.1</span> 概览 Microposts 资源</h3>
<p>和 Users 资源一样,我们使用 <code>rails generate scaffold</code> 命令生成 Microposts 资源的代码,实现图 2.3 中所示的数据模型:<sup class="footnote" id="fnref-2-5"><a href="#fn-2-5" rel="footnote">5</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails generate scaffold Micropost content:string user_id:integer
invoke active_record
create db/migrate/20130307005528_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 jbuilder_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
create <span class="nb">test</span>/helpers/microposts_helper_test.rb
invoke jbuilder
exist app/views/microposts
create app/views/microposts/index.json.jbuilder
create app/views/microposts/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/microposts.js.coffee
invoke scss
create app/assets/stylesheets/microposts.css.scss
invoke scss
identical app/assets/stylesheets/scaffolds.css.scss
</pre></div>
</div>
<p>然后要更新数据库,使用最新的数据模型,我们要执行类似 <a href="chapter2.html#section-2-2">2.2 节</a>中用到的迁移命令:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>bundle <span class="nb">exec </span>rake db:migrate
<span class="o">==</span> CreateMicroposts: migrating <span class="o">===============================================</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>
</pre></div>
</div>
<p>现在我们就可以使用类似 <a href="chapter2.html#section-2-2-1">2.2.1 节</a>中介绍的方法来创建微博了。就像你猜测的,脚手架也会更新 Rails 的路由文件,为 Microposts 资源加入一条规则,如代码 2.7 所示。<sup class="footnote" id="fnref-2-6"><a href="#fn-2-6" rel="footnote">6</a></sup> 和 Users 资源一样,<code>resources :micropsts</code> 会将微博相关的 URL 地址映射到 Microposts 控制器,如<a href="chapter2.html#table-2-3">表格 2.3</a> 所示。</p>
<div class="table has-caption" id="table-2-3"><p class="caption"><span>表格 2.3:</span>代码 2.7 中 Microposts 资源生成的符合 REST 架构的路由</p><table>
<thead>
<tr>
<th>HTTP 请求</th>
<th>URL</th>
<th>动作</th>
<th>目的</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GET</code></td>
<td>/microposts</td>
<td><code>index</code></td>
<td>显示所有微博的页面</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/microposts/1</td>
<td><code>show</code></td>
<td>显示 ID 为 1 的微博页面</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/microposts/new</td>
<td><code>new</code></td>
<td>显示创建新微博的页面</td>
</tr>
<tr>
<td><code>POST</code></td>
<td>/microposts</td>
<td><code>create</code></td>
<td>创建新微博</td>
</tr>
<tr>
<td><code>GET</code></td>
<td>/microposts/1/edit</td>
<td><code>edit</code></td>
<td>编辑 ID 为 1 的微博页面</td>
</tr>
<tr>
<td><code>PATCH</code></td>
<td>/microposts/1</td>
<td><code>update</code></td>
<td>更新 ID 为 1 的微博</td>
</tr>
<tr>
<td><code>DELETE</code></td>
<td>/microposts/1</td>
<td><code>destroy</code></td>
<td>删除 ID 为 1 的微博</td>
</tr>
</tbody>
</table>
</div>
<div class="codeblock has-caption" id="codeblock-2-7"><p class="caption"><span>代码 2.7:</span>Rails 的路由配置,有一条针对 Microposts 资源的新规则</p><p class="file"><code>config/routes.rb</code></p><div class="highlight type-ruby"><pre><span class="no">DemoApp</span><span class="o">::</span><span class="no">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="p">:</span><span class="n">microposts</span>
<span class="n">resources</span> <span class="p">:</span><span class="n">users</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<p>Microposts 控制器的代码简化后如代码 2.8 所示。注意,除了将 <code>UsersController</code> 换成 <code>MicropostsController</code> 之外,这段代码和代码 2.3 没什么区别。这说明了这两个资源在 REST 架构中的共同之处。</p>
<div class="codeblock has-caption" id="codeblock-2-8"><p class="caption"><span>代码 2.8:</span>Microposts 控制器的代码简化形式</p><p class="file"><code>app/controllers/microposts_controller.rb</code></p><div class="highlight type-ruby"><pre><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">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">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">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>
</pre></div>
</div>
<p>我们在创建微博页面(<a href="http://localhost:3000/microposts/new">/microposts/new</a>)输入一些内容来添加一个微博,如图 2.12 所示。</p>
<div class="figure" id="figure-2-12">
<img src="figures/demo_new_micropost_rails_3.png" alt="demo new micropost rails3" />
<p class="caption"><span>图 2.12:</span>创建微博的页面(<a href="http://localhost:3000/microposts/new">/microposts/new</a>)</p>
</div>
<p>既然已经在这个页面了,那就多创建几个微博,确保至少有一个微博的 <code>user_id</code> 设为了 <code>1</code>,这样就对应到 <a href="chapter2.html#section-2-2-1">2.2.1 节</a>中创建的第一个用户了。结果应该和图 2.13 类似。</p>
<h3 id='section-2-3-2'><span>2.3.2</span> 限制微博内容的长度</h3>
<p>如果要称得上微博这样的名字就要限制其内容的长度。在 Rails 中实现这种限制很简单,使用数据验证(validation)功能。要限制微博的长度最大为 140 个字符(就像 Twitter 一样),我们可以使用长度限制数据验证。现在你可以用你的文本编辑器或 IDE 打开 <code>app/models/micropost.rb</code> 写入代码 2.9 所示的代码。(代码 2.9 中使用的 <code>validates</code> 方法只针对 Rails 3;如果你之前用过 Rails 2.3,就可以对比一下它和 <code>validates_length_of</code> 的区别。)</p>
<div class="figure" id="figure-2-13">
<img src="figures/demo_micropost_index_rails_3.png" alt="demo micropost index rails3" />
<p class="caption"><span>图 2.13:</span>微博索引页面(<a href="http://localhost:3000/microposts">/microposts</a>)</p>
</div>
<div class="codeblock has-caption" id="codeblock-2-9"><p class="caption"><span>代码 2.9:</span>现在微博的长度最长为 140 个字符</p><p class="file"><code>app/models/micropost.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">validates</span> <span class="p">:</span><span class="n">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>
</pre></div>
</div>
<p>上面的代码看起来可能很神秘,我们会在 <a href="chapter6.html#section-6-2">6.2 节</a>中详细介绍数据验证。如果我们在创建微博页面输入超过 140 个字符的内容就会看到这个验证的样子了。如图 2.14 所示,Rails 会显示一个错误提示信息(error message)提示微博的内容太长了。(<a href="chapter7.html#section-7-3-3">7.3.3 节</a>将更详细的介绍错误信息)</p>
<h3 id='section-2-3-3'><span>2.3.3</span> 一个用户有多篇微博</h3>
<p>Rails 强大的功能之一是可以为不同的数据模型之间创建关联(association)。针对本例中的 User 模型,每个用户可以有多篇微博。我们可以通过更新 User 模型(参见代码 2.10)和 Micropost 模型(参见代码 2.11)的代码来实现这种关联。</p>
<div class="figure" id="figure-2-14">
<img src="figures/micropost_length_error_rails_3.png" alt="micropost length error rails3" />
<p class="caption"><span>图 2.14:</span>创建微博失败后显示的错误信息</p>
</div>
<div class="codeblock has-caption" id="codeblock-2-10"><p class="caption"><span>代码 2.10:</span>一个用户有多篇微博</p><p class="file"><code>app/models/user.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">has_many</span> <span class="p">:</span><span class="n">microposts</span>
<span class="k">end</span>
</pre></div>
</div>
<div class="codeblock has-caption" id="codeblock-2-11"><p class="caption"><span>代码 2.11:</span>一篇微博只属于一个用户</p><p class="file"><code>app/models/micropost.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">belongs_to</span> <span class="p">:</span><span class="n">user</span>
<span class="n">validates</span> <span class="p">:</span><span class="n">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>
</pre></div>
</div>
<p>我们可以将这种关联用图 2.15 所示的图形表示出来。因为 <code>microposts</code> 表中有 <code>user_id</code> 这一列,所以 Rails(通过 Active Record)就可以将微博和每个用户关联起来。</p>
<div class="figure" id="figure-2-15">
<img src="figures/micropost_user_association.png" alt="micropost user association" />
<p class="caption"><span>图 2.15:</span>微博和用户之间的关联</p>
</div>
<p>在<a href="chapter10.html">第 10 章</a>和<a href="chapter11.html">第 11 章</a>中,我们将使用用户和微博之间的关联来显示某一个用户的所有微博,并且生成一个和 Twitter 类似的动态列表。我们可以使用控制台(console)来检查一下用户与微博之间关联的实现,控制台是和 Rails 应用程序交互很有用的工具。在命令行中执行 <code>rails console</code> 来启动控制台,然后使用 <code>User.first</code> 从数据库中读取第一个用户(并将读取的数据赋值给 <code>first_user</code> 变量):<sup class="footnote" id="fnref-2-7"><a href="#fn-2-7" rel="footnote">7</a></sup></p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>rails console
<span class="gp">>> </span>first_user <span class="o">=</span> User.first
<span class="gp">=> </span><span class="c">#<User id: 1, name: "Michael Hartl", email: "[email protected]",</span>
created_at: <span class="s2">"2013-03-06 02:01:31"</span>, updated_at: <span class="s2">"2013-03-06 02:01:31"</span>>
<span class="gp">>> </span>first_user.microposts
<span class="gp">=> </span><span class="o">[</span><span class="c">#<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:</span>
<span class="s2">"2013-03-06 02:37:37"</span>, updated_at: <span class="s2">"2013-03-06 02:37:37"</span>>, <span class="c">#<Micropost id: 2,</span>
content: <span class="s2">"Second micropost"</span>, user_id: 1, created_at: <span class="s2">"2013-03-06 02:38:54"</span>,
updated_at: <span class="s2">"2013-03-06 02:38:54"</span>>]
<span class="gp">>> </span><span class="nb">exit</span>
</pre></div>
</div>
<p>(上面代码中我包含了最后一行用来演示如何退出控制台,在大多数系统中也可以使用 Ctrl-d 组合键。)然后使用 <code>first_user.microposts</code> 获取用户的微博:Active Record 会自动返回 <code>user_id</code> 和 <code>first_user</code> 的 id 相同的(<code>1</code>)所有微博。我们将在<a href="chapter10.html">第 10 章</a>和<a href="chapter11.html">第 11 章</a>更详细的学习 Active Record 中这种关联的实现。</p>
<h3 id='section-2-3-4'><span>2.3.4</span> 继承关系</h3>
<p>接下来我们暂时结束演示程序的讨论,来简单的介绍一下 Rails 中控制器和模型的类继承。如果你有一些面向对象编程(Object-oriented Programming,OOP)的经验将更好的理解这些内容,如果你未接触过 OOP的话可以选择跳过本小节。一般来说,如果你不熟悉类的概念(<a href="chapter4.html#section-4-4">4.4 节</a>中会介绍),我建议你稍晚些时候再回过头来看本小节。</p>
<p>我们先介绍模型的继承关系。对比一下代码 2.12 和代码 2.13 中的代码,User 模型和 Micropost 模型都继承自(通过 <code><</code>)<code>ActiveRecord::Base</code>,它是 ActiveRecord 为模型提供的基类。图 2.16 列出了这种继承关系。通过继承 <code>ActiveRecord::Base</code> 我们的模型对象才能够和数据库通讯、将数据库中的列看做 Ruby 中的属性等。</p>
<div class="codeblock has-caption" id="codeblock-2-12"><p class="caption"><span>代码 2.12:</span><code>User</code> 类,包括继承关系</p><p class="file"><code>app/models/user.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<div class="codeblock has-caption" id="codeblock-2-13"><p class="caption"><span>代码 2.13:</span><code>Micropost</code> 类,包括继承关系</p><p class="file"><code>app/models/micropost.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">Micropost</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<div class="figure" id="figure-2-16">
<img src="figures/demo_model_inheritance.png" alt="demo model inheritance" />
<p class="caption"><span>图 2.16:</span>User 模型和 Micropost 模型的继承关系</p>
</div>
<p>控制器的继承关系更复杂一些。对比一下代码 2.14 和代码 2.15,我们可以看到 Users 控制器和 Microposts 控制器都继承自应用程序的控制器(<code>ApplicationController</code>)。如代码 2.16 所示,<code>ApplicationController</code> 继承自 <code>ActionController::Base</code>,它是 Rails 中的 Action Pack 库为控制器提供的基类。这些类之间的关系如图 2.17 所示。</p>
<div class="figure" id="figure-2-17">
<img src="figures/demo_controller_inheritance.png" alt="demo controller inheritance" />
<p class="caption"><span>图 2.17:</span>Users 控制器和 Microposts 控制器的继承关系</p>
</div>
<div class="codeblock has-caption" id="codeblock-2-14"><p class="caption"><span>代码 2.14:</span><code>UsersController</code> 类,包含继承关系</p><p class="file"><code>app/controllers/users_controller.rb</code></p><div class="highlight type-ruby"><pre><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">end</span>
</pre></div>
</div>
<div class="codeblock has-caption" id="codeblock-2-15"><p class="caption"><span>代码 2.15:</span><code>MicropostsController</code> 类,包含继承关系</p><p class="file"><code>app/controllers/microposts_controller.rb</code></p><div class="highlight type-ruby"><pre><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">end</span>
</pre></div>
</div>
<div class="codeblock has-caption" id="codeblock-2-16"><p class="caption"><span>代码 2.16:</span><code>ApplicationController</code> 类,包含继承关系</p><p class="file"><code>app/controllers/application_controller.rb</code></p><div class="highlight type-ruby"><pre><span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o"><</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">Base</span>
<span class="p">.</span>
<span class="nf">.</span>
<span class="p">.</span>
<span class="nf">end</span>
</pre></div>
</div>
<p>和模型的继承类似,通过继承 <code>ActionController::Base</code>,Users 控制器和 Microposts 控制器获得了很多的功能,例如处理模型对象的功能,过滤输入的 HTTP 请求,以及将视图渲染成 HTML 的功能。因为 Rails 中的控制器都继承自 <code>ApplicationController</code>,所以在应用程序控制器中定义的内容就会应用到程序中的所有动作。例如,在 <a href="chapter8.html#section-8-2-1">8.2.1 节</a>中将看到如何在应用程序控制器中添加一个登录、退出的帮助方法。</p>
<h3 id='section-2-3-5'><span>2.3.5</span> 部署演示程序</h3>
<p>完成 Microposts 资源之后,是时候将代码推送到 GitHub 的仓库中了:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>git add .
<span class="gp">$ </span>git commit -m <span class="s2">"Finish demo app"</span>
<span class="gp">$ </span>git push
</pre></div>
</div>
<p>通常情况下,你应该经常做一些很小的提交,不过对于本章来说最后做一次大的提交也可以。</p>
<p>然后,你也可以按照 <a href="chapter1.html#section-1-4">1.4 节</a>中介绍的方法将演示程序部署到 Heroku:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>heroku create
<span class="gp">$ </span>rake assets:precompile
<span class="gp">$ </span>git add .
<span class="gp">$ </span>git commit -m <span class="s2">"Add precompiled assets for Heroku"</span>
<span class="gp">$ </span>git push heroku master
</pre></div>
</div>
<p>最后,迁移生产环境中的数据库:</p>
<div class="codeblock"><div class="highlight type-shell"><pre><span class="gp">$ </span>heroku run rake db:migrate
</pre></div>
</div>
<p>上面的代码会用 User 和 Micropost 数据模型更新 Heroku 上的数据库。</p>
<h2 id='section-2-4'><span>2.4</span> 小结</h2>
<p>现在我们已经结束了对一个 Rails 应用程序的分析,本章中开发的演示程序有一些好的地方也有一些有缺陷的地方。</p>
<h4 id='section-2-4-0-1'><span></span>好的地方</h4>
<ul>
<li>概览了 Rails</li>
<li>介绍了 MVC</li>
<li>第一次体验了 REST 架构</li>
<li>开始使用数据模型了</li>
<li>在生产环境中运行了一个基于数据库的 Web 程序</li>
</ul>
<h4 id='section-2-4-0-2'><span></span>有缺陷的地方</h4>
<ul>
<li>没有自定义布局和样式</li>
<li>没有静态页面(例如“首页”和“关于”)</li>
<li>没有用户密码</li>
<li>没有用户头像</li>
<li>没登录功能</li>
<li>不安全</li>
<li>没实现用户和微博的自动关联</li>
<li>没实现“关注”和“被关注”功能</li>
<li>没实现动态列表</li>
<li>没使用 TDD</li>
<li>没有真的理解所做的事情</li>
</ul>
<p>本书后续的内容会建立在这些好的部分之上,然后改善有缺陷的部分。</p>
<div class="footnotes">
<ol>
<li id="fn-2-1">
<p>如果要实现内容更长的文章,例如一篇常规博客中的文章,应该将字符串类型(<code>string</code>)换成文本类型(<code>text</code>);<a href="#fnref-2-1" rel="reference">↩</a></p>
</li>
<li id="fn-2-2">
<p>脚手架后面跟着的名字和模型一样,是单数形式,而资源和控制器是复数形式。因此是 <code>User</code> 而不是 <code>Users</code>;<a href="#fnref-2-2" rel="reference">↩</a></p>
</li>
<li id="fn-2-3">
<p>有些文章会说是视图直接将 HTML 返回给浏览器的(通过 Web 服务器,例如 Apache 和 Nginx)。不管实现的细节是怎样的,我更相信控制器是一个中枢,应用程序中所有的信息都会通过它;<a href="#fnref-2-3" rel="reference">↩</a></p>
</li>
<li id="fn-2-4">
<p>加利福尼亚大学欧文分校 2000 年 Roy Thomas Fielding 的博士论文《<a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm">架构风格与基于网络的软件架构设计</a>》(译者注:<a href="http://www.redsaga.com/opendoc/REST_cn.pdf">中文翻译</a>)<a href="#fnref-2-4" rel="reference">↩</a></p>
</li>
<li id="fn-2-5">
<p>和生成 Users 资源的脚手架命令一样,生成 Microposts 资源的脚手架也使用了单数形式,因此我们使用 <code>generate Micropost</code>;<a href="#fnref-2-5" rel="reference">↩</a></p>
</li>
<li id="fn-2-6">
<p>和代码 2.7 相比,脚手架生成的代码可能会有额外的空行。你无须担心,因为 Ruby 会忽略额外的空行;<a href="#fnref-2-6" rel="reference">↩</a></p>
</li>
<li id="fn-2-7">
<p>你的控制台可能会显示类似 <code>ruby-2.0.0-head ></code> 的开头,示例中使用 <code>>></code> 替代,因为不同的 Ruby 版本会有所不同。<a href="#fnref-2-7" rel="reference">↩</a></p>
</li>
</ol>
</div>
</div>
</div>
<div class="navigation">
<a class="prev_page" href="chapter1.html">« 第 1 章从零到部署</a>
<a class="next_page" href="chapter3.html">第 3 章基本静态的页面 »</a>
</div>
</div>
<div class="footer">
<p>©2013 <a href="http://about.ac" title="Andor Chen 的个人网站">Andor Chen</a> 保留部分权力。在线阅读版本基于<a href="http://creativecommons.org/licenses/by-sa/3.0/" title="Creative Commons Attribution-ShareAlike 3.0 Unported License" target="_blank">“CC 3.0 BY-SA 协议”</a>发布</p>
</div>
</div>
</body>
</html>