《应用Rails进行敏捷Web开发》精彩样章——第4章 立竿见影

4

立竿见影

Instant Gratification

 

 

现在,我们来编写一个极其简单的web应用,以验证Rails已经成功地在我们的机器上落户了。在此过程中,我们还会简单介绍Rails应用的工作方式。

4.1  Creating a New Application

4.1  新建一个应用程序

安装了Rails框架之后,你同时也得到了一个新的命令行工具:rails。这个工具可以用于构造每个新的Rails应用程序。

为什么我们需要这么一个工具——我是说,为什么不抄起最顺手的编辑器,从头开始编写应用程序的每行代码呢?呃……我们确实可以这样做,但Rails可以在幕后变很多戏法,让我们只需要做最少量的配置即可运行一个应用程序。为了让这些戏法能够生效,Rails必须能够找到应用程序中的各种组件。正如我们稍后(在第13.2节,“目录结构”一节,原书第173页)将会看到的,这就意味着我们必须创建某种固定的目录结构,并且将我们的代码放在合适的地方。rails这个命令可以帮我们创建这一目录结构,并且生成一些标准的Rails代码。

现在,我们来创建第一个Rails应用程序:打开shell窗口,进入文件系统的某个地方——你希望将应用程序目录结构保存在那里的某个地方。在我们的例子中,我们将把项目创建在一个名为work的目录之下。因此,我们在这个目录中用rails命令创建一个名为demo的应用程序。在这里要加些小心:如果已经存在一个名叫demo的目录,rails会询问你是否要覆盖已有的文件。


dave> cd work

work> rails demo

create

create app/apis

create app/controllers

create app/helpers

: : :

create log/development.log

create log/test.log

work>

上述命令创建了一个名为demo的目录。进入这个目录,列出它的全部内容(在Unix中使用ls命令,在Windows中使用dir命令),你应该会看到这样的一堆文件和子目录:

work> cd demo

demo> ls -p

CHANGELOG      app/          db/           log/        test/

README         components/   doc/          public/     vendor/

Rakefile       config/       lib/          script/

突然面对那么多目录(还有它们包含的文件)也许会让你感到有点害怕,不过我们完全不用理会它们的存在。现在,我们只需要用到它们中的一个,也就是public目录。

正如它的名字所暗示的,public目录包含了我们希望暴露给最终用户看到的那些文件。这里的关键文件是分发器(dispatcher):dispatch.cgidispatch.fcgi、以及dispatch.rb。分发器负责接收用户从浏览器发出的请求,并将这些请求引导至应用程序中的程序代码。这几个文件很重要,不过我们目前还不需要接触它们。

你还会看到,在demo目录下有一个script子目录,其中存放的是一些工具脚本,我们在开发应用程序的过程中会用到它们。现在,我们就要使用其中名叫server的脚本,它会启动一个独立运行的WEBrick[1]服务器,我们新建的Rails应用程序就将在其中运行。那么,在继续前进之前,我们先把刚才编写(或者说,生成)的应用程序启动起来吧。

demo> ruby script/server

=> Rails application started on http:// 0.0.0 .0:3000

[2005-02-26 09:16:43] INFO WEBrick 1.3.1

[2005-02-26 09:16:43] INFO ruby 1.8.2 ( 2004-08-24 ) [powerpc-darwin7.5.0]

[2005-02-26 09:16:43] INFO WEBrick::HTTPServer-start: pid=2836 port=3000

从启动输出信息的最后一行就可以看出,我们在3000端口[2]上启动了一个web服务器。我们可以打开浏览器,访问http://localhost:3000,就会看到这个应用程序(如图4.1)。

4.1  新建的Rails应用程序

我们可以让WEBrick一直在这个命令行窗口中运行。稍后我们编写应用代码之后,只要在浏览器中访问,就会看到命令行窗口输出请求的相关信息。如果想要停止WEBrick的运行,可以在命令行窗口中按下Ctrl-C键。

现在,我们已经让新应用跑了起来,但其中还没有我们自己编写的代码。下面,我们就要改变这种情况。

4.2  Hello, Rails!

4.2  Hello, Rails!

Dave说:我实在没办法——每次要试用一个新系统时,我都得写一个“Hello, World!”程序。在Rails中,这个程序会把我们诚挚的问候发送到浏览器上。

正如我们在第2章(“Rails应用的架构”,原书第9页)中所介绍的,Rails是一个MVC框架。Rails接收来自浏览器的请求,对请求进行解读以找到合适的控制器,再调用控制器中合适的方法。然后,控制器又调用一个特定的视图,将结果显示给用户。好消息是,Rails已经帮我们搞定了绝大部分的“管道代码”,这几部分已经被有机地结合在一起。现在,为了写出这个简单的“Hello, World!”应用,我们需要编写一个控制器和一个视图。我们不需要编写模型,因为我们不需要处理任何数据。现在,让我们从控制器开始吧。

就像用rails命令新建一个Rails应用程序一样,我们也可以借助一个脚本来新建一个控制器。这个脚本名叫generate,就保存在demo项目的script子目录中。所以,要创建一个名为say的控制器,我们只需要在demo目录中运行这个脚本,将控制器的名称传递进去即可[3]

demo> ruby script/generate controller Say

exists app/controllers/

exists app/helpers/

create app/views/say

exists test/functional/

create app/controllers/say_controller.rb

create test/functional/say_controller_test.rb

create app/helpers/say_helper.rb

脚本显示出了它所检查的文件与目录,以及它所添加的Ruby源代码和目录。现在,我们感兴趣的是它创建的Ruby源程序,以及新增的目录。

我们首先要关注的是控制器的源代码,它位于app/controllers/say_controller.rb文件中,让我们来看看这个文件:

class SayController < ApplicationController

end

几乎不能再小了,不是吗?SayController是一个空的类,它从ApplicationController继承而来,因此它自动地拥有所有缺省的控制器行为。现在该我们动手了,我们需要在这个控制器中增加一些代码,用来处理用户请求。这些代码应该做什么?现在什么都不需要做——我们只需要一个空的action方法。所以,下一个问题就是:这个方法应该叫什么名字?这个问题的答案是:我们需要先来看看Rails处理请求的方式。

Rails and Request URLs

Rails和请求URL

和别的web应用一样,一个Rails应用在用户看来是与一个URL相关联的。当你把浏览器指向这个URL时,你就开始与这个应用程序对话,它则为你生成应答信息。

然而,真实的情况比这要复杂一些。不妨想象我们的应用程序可以通过下列地址访问:http://pragprog.com/online/demo。存放这个应用程序的web服务器对

4.2  URL被映射到控制器和action

于路径相当聪明,它知道:一旦看到路径中出现online/demo这一部分,就需要与我们的应用程序进行交互。URL中在这之后的任何东西都无法改变这一点:我们的应用程序将被调用。后续的路径信息都将被传递给这个应用程序,后者将把这些信息用作自身内部的用途。

Rails会根据路径来判断控制器的名称、以及控制器内部将被调用的action名称[4](如图4.2)。在路径中,紧跟在应用程序名称后面的第一部分是控制器名称,第二部分是action名称(如图4.3)。

Our First Action

我们的第一个action

我们来给say控制器加上一个名为helloaction。从前一节的讨论可以得知,如果在SayController类中创建一个hello方法,就意味着加上了名为helloaction。但这个方法应该做什么?现在它什么都不需要做。请记住,控制器的职责就是为视图的显示提供充分信息。在我们的第一个应用程序中,没有任何信息需要控制器提供,因此一个空的action方法就足够了。请用你最喜欢的编辑器修改app/controllers目录下的say_controller.rb文件,在其中加上hello()方法:

class SayController < ApplicationController

def hello

end

end


4.3  Rails将请求导向控制器和action

现在让我们试着调用它。随便找一个浏览器窗口,访问下列URLhttp://localhost:3000/say/hello。(请注意:在测试环境下,路径中没有包含应用程序名——我们直接就导向到控制器。)你会看到类似这样的效果。

也许有些恼人,但这个错误是绝对合理的(除了错误提示中那个古怪的路径之外)。我们创建了控制器类和action方法,但没有告诉Rails该显示什么东西——我们还需要一个视图。还记得我们运行脚本新建控制器时的情景吗?那个脚本帮我们生成了三个文件和一个目录,这个目录就是用来存放控制器视图的模板文件的。在这里,我们创建了一个名为say的控制器,因此视图就应该位于app/views/say目录中。

为了完成这个“Hello, World!”应用程序,我们来创建一个模板。缺省情况下,Rails会寻找与当前action同名的模板文件。在我们的例子中,这就意味着


我们需要创建一个名为app/views/say/hello.rhtml的文件。(为什么是.rhtml?我们稍后就会解释。)现在,我们只在其中放入基本的HTML代码。

<html>

<head>

<title>Hello, Rails!</title>

</head>

<body>

<h1>Hello from Rails!</h1>

</body>

</html>

保存hello.rhtml文件,刷新浏览器窗口,你就应该看到一句友好的问候。请注意,我们不需要重新启动应用程序就可以看到更新后的效果。在开发的过程中,每当你保存文件时,Rails就会自动将修改结果整合到正在运行的应用程序中去

到目前为止,我们在两个文件中添加了代码:我们在控制器中添加了一个action,又创建了一个模板以便在浏览器上显示页面。这些文件都位于预先定好的标准位置:控制器在app/controllers目录下,视图在app/views中各自的子目录下(如图4.4)。

Making It Dynaic

让它动起来

到目前为止,我们的Rails应用程序还相当老土——它只能显示一个静态页面。为了给它增加一点动感,我们让它在每次显示页面时顺便显示当前的时间。

为了实现这一效果,我们需要对视图的模板文件稍加修改——现在它需要以字符串的形式把“时间”这项信息包含进来。这时就出现了两个问题。首先,我们如何在模板中添加动态内容?其次,要显示的“时间”信息从哪里来?

Dynamic Content

动态内容

Rails中,有两种方式可以创建动态的模板。其一是使用“构建器”(Builder)这种技术,我们将在第17.2节(“Builder模板”,原书第329页)介绍这种方式。第二种方式也就是我们要在这里使用的:将Ruby代码嵌入模板中。这也就


4.4  控制器和视图的标准位置

是我们要把模板文件命名为hello.rhtml的原因:.rhtml后缀告诉Rails,需要借助ERb系统对文件的内容进行扩展——ERb就是用于将Ruby代码嵌入模板文件的。

ERb是一个过滤器:进去的是.rhtml文件,出来的是经过转换的内容——在Rails中,输出的文件通常是HTML格式,但也可以是别的任何东西。普通的内容会直进直出,没有任何变化。但<%=%>符号之间的内容则会被看作Ruby代码执行,执行的结果将被转换为字符串,并替换到文件中<%...%>序列所在的位置。譬如说,我们在hello.rhtml中加入下列内容:

<ul>

<li>Addition: <%= 1+2 %> </li>

<li>Concatenation: <%= "cow" + "boy" %> </li>

<li>Time in one hour: <%= 1.hour.from_now %> </li>

</ul>

刷新浏览器,模板就会生成下列HTML

<ul>

<li>Addition: 3 </li>

<li>Concatenation: cowboy </li>

<li>Time in one hour: Sat Feb 26 18:33:15 CST 2005 </li>

</ul>


 

让开发更简单

你应该已经注意到我们在开发过程中都干了些什么。当我们在应用程序中添加代码之后,我们不需要重新启动正在运行的应用程序,新修改的内容就自动投入运行了。而且每当我们做了修改,在浏览器中立即就能体现出来。这是怎么回事?

这是因为基于WEBrickRails分发器相当聪明。在开发模式下(另外的两种模式是测试模式与生产模式),每当有新的请求进来,它都会自动重新加载相关的源程序。这样一来,当我们对应用程序进行编辑时,分发器就会确保最近的修改结果投入运行。这对于开发来说是件好事。

然而这种灵活性也是有代价的:它会在“用户输入URL”与“应用程序作出响应”之间造成一个短暂的间隔——这段间隔就是分发器重新加载文件的时间。在开发阶段,这是值得付出的代价;但在投入真实运行之后,这就是不能接受的。因此,这项特性在产品模式(详见第22章,“部署与伸缩”,原书第440页)下是被禁用的。

在浏览器窗口中,你会看到下列内容:

l        Addition: 3

l        Concatenation: cowboy

l        Time in one hour: Sat Feb 26 18:33:15 CST 2005

另外,在.rhtml文件中,<%%>符号(前者没有等号)之间的内容会被看作Ruby代码执行,但执行的结果不替换回输出。真正有趣的是,可以将这种程序处理与非Ruby代码混合使用。譬如说,我们可以编写一个“节日版本”的hello.rhtml

<% 3.times do %>

Ho!<br />

<% end %>

Merry Christmas!

再次刷新,你就会听到雪橇铃声:

Ho!

Ho!

Ho!

Merry Christmas!

请留意:每当Ruby循环执行一次,其中的文本都会被发送到输出流。


我们可以将两种形式结合起来。在下面的例子中,一个循环对一个变量设值,变量的值则被插入一段文本中显示:

<% 3.downto(1) do |count| %>

<%= count %>...<br />

<% end %>

Lift off!

这个模板会把下列内容送给浏览器:

3...<br />

2...<br />

1...<br />

Lift off!

关于ERb,还有一件事需要说明:很多时候,用<%=...%>生成的字符串会包含“<”符号与“& 符号,这两个符号对于HTML来说是至关重要的。为了防止这些符号把页面搞乱(以及为了避免潜在的安全性问题,详见第21章,“保护Rails应用的安全”,原书第427页),你会希望对这些字符进行转码。Rails有一个辅助方法h()用于做这件事。大多数时候,在把动态内容替换到HTML页面中时,你会需要使用这个方法。

Email: <%= h("Ann & Bill <frazers@isp.email>") %>

在这个例子中,h()方法保护了邮件地址中的特殊字符不会扰乱浏览器显示——它们会被转码为HTML实体。用户在浏览器中会看到“Email: Ann & Bill <frazers@isp.email>”字样——特殊字符也被正确显示出来。

Adding the Time

把时间加上

我们最初的目标是向用户显示当前的时间。现在我们已经知道如何在应用程序中显示动态数据,第二个需要解决的问题就是:从哪里获取时间?

我们的解决办法是调用RubyTime.now()方法——在say.rhtml模板中直接调用。

<html>

   <head>

      <title>Hello, Rails!</title>

   </head>

   <body>

      <h3>Hello from Rails!</h3>

      <p>

         The time is <%= Time.now %>

      </p>

   </body>

</html>


这个办法行得通。只要访问这个页面,用户就会看到当前的时间。对于我们这个小例子,这种办法也就够好了。然而,通常情况下我们会希望用另一种办法:将“获得时间”的逻辑放在控制器中,视图则只承担显示信息的职责。因此,我们要对控制器中的action方法稍加修改,将时间值放在名为@time的实例变量中:

class SayController < ApplicationController

   def hello

      @time = Time.now

   end

end

.rhtml模板中,我们把这个实例变量输出给用户:

<html>

   <head>

      <title>Hello, Rails!</title>

   </head>

   <body>

      <h1>Hello from Rails!</h1>

      <p>

         It is now <%= @time %>.

      </p>

   </body>

</html>

刷新浏览器,我们会看到时间显示出来(如图4.5)。而且你可以看到,每当点击浏览器的“刷新”按钮时,页面上显示的时间都会变化。看起来,我们确实已经制造出了动态的内容。

为什么我们要在控制器中获取时间,然后在视图中显示?这不是自找麻烦吗?好问题。在这个应用程序中,你当然可以直接在模板中调用Time.now()

4.5  带时间显示的“Hello, World!


 

 


       Joe问……

       视图如何获取时间?

按照前面的介绍,我们在控制器中把时间信息放进一个实例变量,.rhtml文件则从这个实例变量取出当前时间,并显示给用户。但是,控制器对象中的实例变量是private的。那么ERb又是如何取出这些private的数据,并在模板中使用呢?

答案既简单又有些微妙。Rails使用了Ruby的某些魔法,使得控制器对象中的实例变量被注射到了模板对象中。其结果就是:视图模板可以访问控制器中的任何实例变量,就好象是在访问它们自己的实例变量一样。

方法;但是,把这一调用放进控制器会给你带来好处。譬如说,也许我们将来会希望对应用程序进行扩展,使其可以支持多国家使用,这样我们就需要对时间的显示加以本地化:不仅要选择适合用户习惯的显示格式,还要提供与他们所在时区相应的时间。这些逻辑应该属于应用级代码,并不适合嵌在视图中。如果在控制器中提供要显示的时间信息,我们的应用程序就会更加灵活:我们可以在控制器中修改显示格式和时区设置,而不必对视图做任何修改。

The Story So Far

目前的故事

我们来简单回顾一下,这个应用程序是如何工作的。

1.  用户通过浏览器进入我们的应用程序。在这里,我们使用一个本地URL,例如http://localhost:3000/say/hello

2.  RailsURL进行分析,say这一部分被视为控制器的名称,因此RailsSayController这个Ruby类(位于app/controllers/say_controller.rb文件)新建一个实例。

3.  URL路径中的下一部分(hello)被视为action的名称。Rails调用控制器中名称为hello的方法,该方法新建一个Time对象(后者记录了当前的时间),


并将其放进@time实例变量中。

4.  Rails寻找一个用于显示结果的模板,它会到app/views目录中寻找与控制器名称相同的子目录(say),然后在该子目录中寻找与action名称相符的文件(hello.rhtml)。

5.  Rails借助ERb对模板进行处理,执行其中嵌套的Ruby代码,以及将控制器提供的值替换进去。

6.  Rails结束对这一请求的处理,处理的结果被发送给浏览器。

这并不是故事的全部——Rails给了我们很多机会,可以对基本流程进行调整(我们很快就会用到这些机会)。我们这个故事的意义在于:它充分体现了惯例重于配置convention over configuration)的原则,这是Rails的基本思想之一。由于提供了便利有效的缺省配置与命名惯例,Rails应用通常只需要很少的外部配置——如果不是完全不需要的话。应用程序的各个部分以一种很自然的方式组合起来,不需要我们多操心。

4.3  Linking Pages Together

4.3  把页面链起来

很少有哪个web应用只有一个页面的。现在,我们就来试试给“Hello, World!”应用再加上一点web设计的美妙效果。

一般来说,应用程序中的一类页面会对应到一个视图。在这里,我们要新加一个action方法,并为它新建一个页面(这并非是必须的,我们在本书后面部分会看到不同的例子)。我们将把这个action方法也放在SayController控制器中——当然你也可以选择新建一个控制器,不过目前并没有什么特别的理由让我们这样做。

我们已经知道如何新建视图与action:为了添加一个名为goodbyeaction,我们只需在控制器中定义一个同名的新方法即可。现在,我们的控制器看起来就像这样:

class SayController < ApplicationController

   def hello

      @time = Time.now

   end

   def goodbye

   end

end


4.6  基本的“Goodbye”页面

下面,我们要在app/views/say目录中新建一个模板,它的名字应该是goodbye.rhtml,因为在缺省状态下,模板的名字应该与action的名字匹配。

<html>

   <head>

      <title>See You Later!</title>

   </head>

   <body>

      <h1>Goodbye!</h1>

      <p>

         It was nice having you here.

      </p>

   </body>

</html>

打开我们可爱的浏览器,这次输入的URLhttp://localhost:3000/say/goodbye。你应该会看到如图4.6的效果。

现在我们需要将这两个页面连接起来。我们要在“hello”页面上放一个链接,让它把用户带到“goodbye”页面;反过来,后者也应该有一个指向前者的链接。在一个真实的应用程序中,我们可能希望用好看的按钮来提供这样的功能,不过现在只要使用超链接就够了。

我们已经知道,Rails按照命名惯例将URL解析为“控制器名称”和“action名称”两部分。因此,我们的链接也应该遵循这一惯例。所以,我们在hello.rhtml中加入下列内容:


<html> ....

   <p>

      Say <a href="/say/goodbye">GoodBye</a>!

   </p>

</html>

goodbye.rhtml中的链接也与此类似:

<html> ....

   <p>

      Say <a href="/say/hello">Hello</a>!

   </p>

</html>

这种办法当然管用,但多少有点脆弱:如果我们把应用程序搬到web服务器的另一个地方,这些URL就会失效。而且,这实际上是把RailsURL格式的解读直接写进了代码中,而Rails将来的版本完全有可能改变目前的解读方式。

还好,我们有办法解决这个问题。Rails提供了一大堆可以在视图模板中使用的辅助方法。在这里,我们可以使用link_to()辅助方法,这个方法可以创建指向一个action的超链接[5]。用上link_to()方法之后,hello.rhtml就变成了:

<html>

   <head>

      <title>Hello, Rails!</title>

   </head>

   <body>

      <h1>Hello from Rails!</h1>

      <p>

      It is now <%= @time %>.

   </p>

   <p>

      Time to say

      <%= link_to "GoodBye!", :action => "goodbye" %>

      </p>

   </body>

</html>

在最后一个<%=...%>ERb序列中嵌入了对link_to()的调用,其结果是一个指向goodbye()这个actionURL。在调用link_to()时,第一个参数是超链接显示的文字,第二个参数则告诉Rails如何生成超链接。由于我们没有指定控制器,Rails会使用当前的控制器。

我们不妨多花点时间来思考link_to()方法的第二个参数。我们刚才写下了:

link_to "GoodBye!", :action => "goodbye"

4.7  Hello”页面加上了指向“Goodbye”页面的链接

:action是一个Ruby符号symbol),你可以把这里的冒号看作“名叫某某某的东西”,因此:action就代表“名叫action的东西”。后面的=>"goodbye"则将"goodbye"这个字符串与action这个名字关联起来。从效果上来说,这就是允许我们在调用方法时指定一些参数来传递,即关键字参数keyword parameter)。Rails大量使用了这种技术:只要一个方法接收多个参数、并且其中的一些参数是可选的,你就可以通过关键字参数来传递这些参数值。

OK,言归正传。现在,如果我们用浏览器查看“hello”页面,其中就会出现一个指向“goodbye”页面的链接(如图4.7)。

我们可以在goodbye.rhtml中做类似的对象,链回“hello”页面。

<html>

   <head>

      <title>See You Later!</title>

   </head>

   <body>

      <h1>Goodbye!</h1>

      <p>

         It was nice having you here.

      </p>

      <p>

         Say <%= link_to "Hello", :action=>"hello" %> again.

      </p>

   </body>

</html>


4.4  What We Just Did

4.4  我们做了什么

在本章中,我们构造了一个玩具应用。通过这一过程,我们学会了:

l        如何新建一个Rails应用程序,以及如何在其中新建控制器,

l        Rails是如何将用户请求映射到程序代码的,

l        如何在控制器中创建动态内容,以及如何在视图模板中显示动态内容,

l        如何把各个页面链接起来。

这是一个很好的开始。下面,我们来建造一个更真实的应用程序。

 



[1] WEBrick是一个纯Ruby编写的web服务器,随Ruby 1.8.1或更高版本发行。

[2] URL地址中的“0.0.0.0”表示WEBrick会接收来自所有接口的连接。在DaveOS X系统上,这就表示不管来自本地接口(127.0.0.1::1)还是来自LAN连接的请求都会被WEBrick接收到。

[3] “控制器的名称”这个概念可能比你想象的要更复杂一些,我们会在第13.4节(“命名惯例”,原书第180页)详细解释这个问题。现在,我们只需假设控制器的名字就是Say

[4] Rails在解析URL时相当灵活。本章所描述的是默认的URL解析机制。在第16.3节(“请求的路由”,原书第280页),我们将看到如何改变这一机制。

[5] link_to()可以做的还远不止这些。不过别着急,我们慢慢来……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值