Web开发人员的利器:Ruby on Rails
作者:Jonathan Palley
工具发展简史
人类的发展就是工具的发展。从石器到木棒和矛,再到火器,我们学会了如何更有效地捕猎。从观看星像到指南针,再到海洋精密计时仪(marine chronometers)和全球定位系统(GPS),我们发现了如何更好地进行导航。从书信到电报,再到电话和互联网,我们将人类通讯的方式进行了革命。人类能够进行创新。我们能够发现解决问题的更有效的方法。我们能够创造更好的工具——这些工具对于我们所要解决的问题来说,显得更具有针对性和专门化,因而更为高效。
程序员也一样。优秀的程序员总是在寻找解决问题的更好的方法,这些方法更易于理解、需要更少的重复性工作和更少的代码,并且也更易于测试。David Heinemeier Hansson 就是这样一个优秀的程序员。
两年多之前,David写了一个叫做 Basecamp 的Web应用程序。当他考察一些现有的工具时,他发现没有任何一种语言和框架是被设计成能够以一种他认为是完全有可能的最简洁、需要最少的重复性工作以及是最容易测试的方式来开发Web应用。他在Ruby语言中看到了他心目中的理想工具所需的灵活性和能力。就这样,Web 应用开发框架 Ruby on Rails 从Basecamp 和 Ruby 中诞生了。
Rails是被设计用于开发Web 应用的。你不应当使用Rails来开发数据挖掘算法、企业财务系统或桌面应用。你应当使用Rails来开发Web应用。同时,Rails将会促使你以一种更快和更可靠的方式来开发,而在这之前你会认为这种方式是不可能的。
RoR确实具有可伸缩性
如果你对“可伸缩性”的定义是要求达到Google搜索引擎或是Yahoo的首页那样的数量级,那么你不再需要继续阅读本文了。实际上,没有任何一种现有的商业解决方案能够适用于那样规模巨大的网站。然而,如果你正在开发一个Web应用,并且你在还没有10万个用户的情况下就开始考虑如何处理1亿个用户的情况,那么你应当重新考虑开发时的优先级。Web开发人员首先需要关注的是开发出一个用户喜欢和愿意使用并且具有较好的体系结构的Web应用。如果你能很好地做到这一点,你才会有时间和金钱来有效地应对你的第1亿个用户。Rails鼓励你在为一个Web应用设计体系结构时采取一些最佳的实践方法,从而使得当需求出现时,你能够对应用进行优化和提升性能。
需要表明的是:Ruby on Rails是具有可伸缩性的。它被成功地运用在了真实世界中的具有很大访问量的应用中。这不是猜想或假定,这是事实。目前,Ruby on Rails正在很多高端的Web应用中服务着每天数以百万计的请求。这些使用了Ruby on Rails的Web应用包括,Basecamp(37signal.com)、Odeo(odeo.com)、 43things(43things.com)、insiderpages(insiderpages.com)、zvents(zvents.com) 以及A List Apart(alistapart.com)。这些Web应用中的每一个每天都在响应着数以百万计的页面请求,同时,数千万的风险投资基金在支持着这些公司。一个使用了Rails的叫做CashNetUSA的公司刚刚以三千五百万美元的价格被收购。
在美国,同其他编程语言或技术相比,对于Ruby 和 Rails的职位需求的增长最大。除此之外,有关Ruby 以及Ruby on Rails的图书销售超过了像Perl这样流行的语言。这并不表示某种潮流或表明Ruby on Rails是一种“玩具语言”——因为很多真实的公司正在将Rails集成到他们现有的工作流程中。Google、Amazon、IBM 以及美国联邦政府的许多部门都在内部使用Rails。顶尖的NASA科学家也在同时使用Python和Ruby。
对于Rails的最大误解或许就是,它被认为不能用于真正的应用中。正如上文所说:一些流量很大的网站正在成功地使用Rails,它们甚至在Rails 1.0 发布之前就开始使用了。我无法在这篇文章中展示出详细的流量数据。然而,所有的这些网站每天都有数以百万计的页面访问量,它们都需要处理性能提升的问题,并维持一个良好的网站正常运行率。下面我将对这些应用做一个简单介绍:
Basecamp:Ruby on Rails就是从Basecamp的代码中提炼出来的。Basecamp也是一个在线项目管理系统。就像ruby on Rails一样,它非常简单,然而却很强大。它的功能包括:创建项目里程碑和待办事项列表、发送消息、上传文件、管理项目人员等等。Bascamp 是由一个名为 37signals的公司创建的。在创建Basecamp之后,他们还开发了很多其它的在线协同工作的应用,所有这些都使用了Ruby on Rails。我所在的公司,即Idapted,在内部使用basecamp来管理各种各样的工作流程和项目。通过快速的网上搜索,你将能看到很多很多公司都在依赖于Basecamp来完成工作。
Odeo:Odeo是一个语音博客/播客(podcasting)的Web应用。它使得人们可以在网站上创建和录制播客内容和其他语音信息,建立RSS种子,加入注释,等等。它实际上就是一个带有音频信息的博客应用。该应用在Ruby on Rails还没有到达1.0时就使用了它来开发。
InsiderPage :InsiderPages是一个社会黄页应用。它使得人们可以对他们所在区域中的各种企业发表意见和评级。该应用最初是用Java写的,并使用了通常的Struts/Hibernate等框架。然而他们发现Java代码缺乏灵活性和适应性,无法满足他们的应用。他们希望能够快速对用户的反馈和功能需求作出反应,并能够容易地发布小版本和进行更新。他们把全部的应用转换成了基于Ruby on Rails,并发现开发过程得到极大的改进。
尽管InsiderPages不允许我公布有关他们从Java转换到Rails的具体数字,不过已经有一些已发表的我认为很能说明问题的估计数字。完整的内容可以从以下的地址得到:
http://article.gmane.org/gmane.comp.lang.ruby.rails/24863
。 这篇贴子描述的是,参与了某个使用Java/Hibernate/JSP/Struts/Ajax/Unit test技术来开发企业级项目的开发人员发现,当从Java转换到Rails后,代码行数减少到原来的25分之一。这里是从该贴子中摘录的一些统计数据:
Java version:
10361 lines of Java code
1143 lines of JSP
8082 lines of XML
1267 lines of build configuration
-----------------------------------------------------------
20853 TOTAL lines of stuff
Rails version:
494 lines of code (386 "LOC" per rake stats)
254 lines of RHTML
75 lines of configuration (includes comments in routes.rb)
0 lines of build configuration
-----------------------------------------------------------
823 TOTAL lines of stuff
尽管有人肯定会争论说,这个项目采用Java也可以有更加有效的方法,不过采用Rails的真正好处并不在于此。Ruby on Rails的美妙之处并不是它能使你写更少的代码,而是能使你写出能够反应出你的意图的代码。Rails提供了一个开发Web应用的框架,它使你写出的代码能够反应出该应用的情况。使用Rails 之后,开发人员不再需要关心那些配置信息,也不必再去把那些对于该应用的功能没有直接贡献的模块和代码捆绑在一起。Ruby on Rails使得Web开发人员可以写出只做一件事情的代码:这就是,描述他们正在开发的应用情况。
Rails 的优势
Rails 能够节省时间并且还能构架和开发更好的 Web应用, Rails之所以有这些好处,是因为它让 Web 开发者只专注于与应用程序的功能直接相关的代码。这篇文章的其他部分将会探索Rails 是如何做到这点并成为了广受赞誉的工具的。由于这篇文章致力于比较不同的Web 开发框架,Rails 是否是适合你的工具?我相信最好的方式是将决策权交给你,在这里我们只进行展示和讨论。
但是,为了让下面的种种讨论在我们规定的范围之内,我将先讨论一下 Rails和 PHP/ASP.net/Java相比在概念上的优势。
PHP 最初被设计来向在 Web页面里嵌入小脚本,而不是完成 Web 应用。面向对象编程的概念:对象、类、继承等等是在后期引入的。虽然PHP5 在面向对象方面做出了重大的改进,它依然无法真正体现面向对象语言的优势。Ruby 则是纯面向对象语言而Rails 充分利用了这一点。Ruby 中的一切都是类并且不存在简单类型。正如我们之后所要演示的,Rails 通过这些面向对象特性打造了纯MVC(Model-View-Controller )框架以及强大灵活的 OR映射。Rails更进一步地通过继承和混入 (mixin) 使得整个框架极易被扩展。通过有效率的语法,各种插件以及特定领域语言(Domain Specific Language))可以轻松的在框架之上进行种种定制。Ruby 的灵活性使得Rails 成为Web 开发者的最爱。因此你不能简单地界定Ruby 是一种开发语言而PHP 是一个框架。
ASP.net 被局限在一种平台,一种 Web服务器,如果你想让它更具效率, SQL Server 则是持久层的唯一选择。Rails 却几乎可以运行在任何平台并与任意的数据库交互。与拥有简单强大的ActiveRecord 作为ORM 的Rails 相比,ASP.net 的开发者却时常迷失在DataAdapters、Connection Objects、Command Objects、DataSets……的世界里。在 MVC 框架是否简洁,是否强大的方面,ASP.net无法与 Rails相提并论,更不要说Rails 所能给予我们的灵活性以及强大能量。
Ruby 是一种极度灵活的动态语言。你可以通过class_evals、混合(mixins )、动态类型(duck typing) 动态扩展对象。Java与 Ruby 相比,最大的问题在语言本身的灵活性上。这是为什么Java web开发是基于规范和定义的其中一个原因。在基于Structs/Hibernate/ 等框架的Java web开发中,不使用 XML 文件或者其他冗余的定义代码是几乎不可能的。通过对一些应用从Java 迁移Rails的统计,我们可以看出,Ruby 语言的灵活性使得Web应用被开发的更加迅速,而且与Java 相比代码的数量大大减少了。
RoR无法掩盖的特性
现在是讨论那些真正使得Rails成为Web 开发语言大赢家的特性的时候了。这篇文章并非面面俱到,它不是一篇指南性质的文章。我略去了那些在开发Rails应用中我认为不能说明Rails 更加强大的部分。后面的内容更多的是让你对Rails 的世界有所了解。
约定胜于配置
Rails是一个真正的MVC(Model-View-Controller))框架。它清楚地将你的模型(数据库)的代码与你的控制器(应用逻辑))从视图代码中清楚地分离出来。一个Rails开发者很少或者可能从未思考过“这段代码应该放在哪一层?”,在Rails 的世界中,适合你那段代码生长的环境有且仅有一处。
事实上,与配置相比,Rails 更倾向于约定。这意味着如果你遵守Rails的约定,你将很少,也许永远不会与配置文件打交道。更重要的是,你永远不需要把时间浪费在搞明白如何配置你的应用上,你所有的精力都花在如何正确地实现应用。当然,所有的东西也是可以配置的。如果你开始一个新的应用,似乎没有什么原因使我们拒绝遵守 Rails的约定。
例如你的models 应该被放在app/models/ 目录, controllers 被放在 the app/controllers / 目录而views 被放在 app/views/controller_name/ 目录。 一旦你把这些文件放在正确的位置,Rails将自动发现它们并把它们加入到应用中。Url 映射是自动完成的。访问
http://yoursite/admin/list
意味着应用中有命名为 admin的控制器与命名为 list的视图与之对应。
约定优于配置的观念 对Rails的影响不仅仅如此。不但你无需考虑“这个文件放在哪儿?”,你也无须考虑“我该如何命名这个文件”,命名的原则是尽量自然并且使得大多数的代码可读性更好。模型( Model)的名字应该为单数,这样在模型(Model)是以单数还是复数形式被引用取决于它在什么时候更有意义。例如假设在系统中, Person持有多个Message ,你大可以写出如下代码。
Person.find(person_id).messages
或者
Message.find(message_id)
注意,message是如何以单数形式被引用的,而与 Person 关联的多个message 则是复数形式。这样更有意义。由于我们还将讨论框架的其他元素,你将发现约定胜于配置的观念在Rails中是如何的根深蒂固。
Ruby的最佳实践——ActiveRecord
上面的例子引入了Rails 最有趣的部分之一:ActiveRecord,ActiveRecord 是ORM层,它是Ruby 语言灵活性的最佳实践。为了沿用上面的例子,我们对数据库结构进行如下设计:
create table users (
id int auto_increment;
name varchar(100);
email varchar(100);
city varchar(100);
primary key (id)
)
create table messages (
id int;
user_id int;
message varchar(100);
created_on timestamp;
primary key (id)
)
然后我们运行rails脚本:
script/generate model user message.
Rails生成了下列文件并把它们放入了 app/models目录:
user.rb
class User < ActiveRecord::Base
end
message.rb
class Message < ActiveRecord::Base
end
注意表是以复数形式存在(这样SQL语句如 SELECT * FROM USERS就更有意义),而对应的类则是单数。
没有使用任何配置我们已经自动得到了最基本的创建,查询,更新以及删除的方法(Message.find, Message.create, Message.delete, Message.update_attributes ),而且这些方法都是真正可以工作的,我们可以在控制器中立刻使用这些方法。如果我们想查找一个名叫"kevin"的用户。我们可以写出如下代码:
User.find(:condition => "name = 'kevin'").
User.find将自动生成正确的 SQL,但是,这段代码读并不易懂。通过使用ruby语言的动态部分, ActiveRecord同样允许我们写出这样的代码:
User.find_by_name("kevin")
或者
User.find_by_name_and_email("kevin", "
kevin@example.com
").
同样,如果我们想查出从北京来的用户,我们大可以写出这样的代码:
User.find_all_by_city("Beijing").
仅仅简单的查找或者持久化数据,使我们在几分钟之后就兴致索然了。数据库和Web 应用之所以值得关注,是它使得数据之间相互关联。我们对ActiveRecord 模型进行相应的修改:
user.rb
class User < ActiveRecord::Base
has_many :messages
end
message.rb
class User < ActiveRecord::Base
belongs_to :user
end
我们现在得到了User 和Messages 的一对多的 (one-to-many) 关系,按照rails的约定,messages 表中应该存在user_id字段,has_many和belongs_to做了什么?在这两个简单方法背后,它们向我们的 model类中添加大量的新的实例方法。例如,不进行任何额外的工作,在控制器中,我们可以这样做:
@user = User.find_by_name("kevin")
@user.create_message(:text => "hello world").
这段代码向Kevin这个用户添加了一条新的message 记录。而且,Rails 自动将记录创建的时间戳持久化到created_on字段。我们可以通过如下的代码得到Kevin 相关的message。
@kevins_messages = @user.messages
AJAX中的视图
Rails中的视图,其实是一个经由erb(embedded ruby,嵌入式Ruby)处理过的html文件,借此可将Ruby功能轻松嵌入html。Rails框架提供大量用于生成视图文件的工具函数。视图文件在控制器里与动作(或方法)关联。下面,我们就创建一个视图,用以显示Kevin的消息:
<for message in @kevins_messages>
<div class=”message”>
<strong>Sent:</strong><%= message.created_on %>
<strong>Body:</strong><%= message.body %>
</div>
<% end %>
看,Ruby代码被完美嵌入html。我们再给动作增加一个创建新消息的链接。为此,我们需要使用Rails中的一个工具函数:
<div id=”new_message”>
<%= link_to 'Create New Message', :url => {:action => 'create'} %>
</div>
执行时,程序会调用控制器中的一个名为“create”动作(或方法)。因此在我们的视图文件目录中,将会自动产生名为create.rhtml的文件。这个文件的RHTML视图代码可以生成一个html表单,以此可以创建新的消息。
为什么一定要在新页面上创建这个表单呢?当创建新消息时,让表单出现在当前页面,这样我能同时看到旧的消息,不是更好吗?如果使用AJAX,这是可以实现的。Rails中大量的工具函数就能让我们不使用JavaScript也可以编写AJAX应用,太美妙了。这些东西在Ruby里都是现成的。使用AJAX创建我们的链接,我们只需要做如下简单修改:
<%= link_to_remote 'Create New Message', “new_message”, :url=>{action=>'create} %>
Rails利用Prototype或Scriptaculous框架里的JavaScript库,在页面里生成了相应的JavaScript程序。“Create New Message”链接被点击时,对服务器的AJAX请求被生成,请求的响应也在“new_message”区里被创建。
Rails 1.1甚至以RJS的方式,实现了对JavaScriot更高级的支持。RJS让不使用JavaScript就能编写JavaScript功能成为可能——你写的Ruby和Rails代码将在适当时候被自动转化为JavaScript。用这种手段搭建高交互性的站点,我们就可以心无旁骛,专注于MVC的设计模式。
RJS文件有点像RHTML。但它不会被转化为HTML,在一个AJAX请求里,RJS是以JavaScript方式执行的。举个例子,我们希望上面提到的“Create New Message”AJAX链接不仅显示表单,而且要让其呈高亮态,并出现一个导航警告,最后调用JavaScript函数。好,我们就用RJS文件create.rjs替代create.rhtml,其格式与如下相仿:
page.replace_html 'new_message', :partial => 'new_message_form'
page.visual_effect :highlight, 'new_message'
page.alert('Directions go here')
page.<< “js_func_to_call”
代码“:partial => 'new_message_form”说明我们希望生成另一个名为“_new_message_form.rhtml”的rhtml文件。这里的“partial”实际上和先前包含表单的create.rhtml完全相同。
Ruby语言——DSL
聪明的读者可能已经知道,Rails的威力大多来源于Ruby语言本身的灵活性。我不想过多讨论Ruby的重要性,这里要重点介绍的是一个以此为基础且令人惊叹的衍生品。Ruby允许创建各种DSL(Domain Specific Language)。这些袖珍型语言为特定领域而设计,可以使用Ruby直接创建。RJS文件是DSL的一个例子。例中,被创建的DSL将JavaScript功能加入了站点。
在我公司开发的一个应用里,DSLs发挥了巨大作用。比如,我们应用里很大一个部分是处理VoIP(译者注:Voice over IP,网络语音或网络电话)和Web接口的交互。因此,我们的应用需要控制并与VoIP服务器交互。VoIP服务器使用了一个叫做Asterisk的开源平台。Asterisk提供一个叫做AGI的功能集,允许编程使用TCP链接控制服务器。而AGI API凌乱不堪,因此,我们开发了一个DSL,以此封装AGI的功能,允许我们用Rails开发清晰而简洁开发语音接口。用DSL抽象封装了混乱的AGI代码,我们就能以清晰的思路,直接编写实际的应用功能代码。
这里有一个我们开发的基于银行应用的语音功能DSL例子。首先,它创建一个输入提示表单,应用以此通过电话收集各项细节信息。其结构和HTML表单类似。然后将所有收集到的信息送入Rails控制器里的action,action完成信息的处理并将结果写入数据库。
asterisk.form :action=>'create' do |form|
form.numeric_input 'bank-deposit-or-withdrawl', 'type', 1000
form.numeric_input 'bank-enter-line-item-amount', 'line_item[amount]'
form.record_input session.id.to_s, 'line_item[description]'
end
用DSL按照Ruby语法习惯编写代码,asterisk表单的结构和表现方式就完全掌握在我们自己手里了。DSL的厉害就在于它可以把与AGI交互的功能以完全不同的方式而又非常条理化地抽象出来。
高度集成的测试
使用Rails快速完成Web应用开发,但最后就得为测试煞费苦心了吧?事实并非如此,测试被高度集成到Rails框架并成为Rails应用的组成部分了。测试分三类:单元测试,测试数据存取接口;功能测试,测试控制部分;集成测试,测试应用的流程。
我就不深入测试的细节了—有兴趣的读者可以到Google搜索,能得到大堆资料。上面讨论过DSL,我想说说如何将DSL与Rails的集成测试有机结合。为你的应用创建测试DSL,这是可能的,甚至相当简单。这样一来,质量保证部门和其他测试人员就可不费吹灰之力编写测试用例,再不需学习复杂的编程语言。以前面提到的包含消息组件的应用为例,我们为它创建了DSL,并将测试部分抽象进去,那么测试者就能以如下方式简单编写各类测试场景:
kevin = users('kevin')
kevin.log_in
kevin.view_new_messages
kevin.create_message_to('susan')
kevin.send_message
这就是我们的DSL。幕后,DSL将为我们描述的场景中的各种功能执行必要测试。使用Ruby,编写DSL抽象测试就是这么简单。
学习Rails的捷径
判断Rails工具是否适合于你的最好办法是试用。幸运的是,有大量非常好的资源可以帮助你学习Rails。首推的应该是《Agile Web Development with Ruby on Rails》这本图书(The Pragramatic Programmers出版)。它堪称Rails圣经,也是我读过的最好并最让我乐在其中的技术书。开篇是一些介绍性章节,然后就全程陪同读者在一个在线Web商店应用里漫步。学习Rails(以及其他Ruby的扩展框架)的最好办法是自己动手完成一个应用的全部开发,并真正弄懂这些代码的意思。这本书已经出了中文版,但如果购买在线的英文pdf电子书,那么你就能及时获得包含大量Rails新特性的更新版本。
利用《Agile Web Development》学习Rails,通常可以掌握Ruby语言的基础,但要想深度把握Ruby,《Programming Ruby》(也由The Pragramtic Programmers出版)将是无可争议的首选。这是另一本优秀的图书。
最后,Ruby on Rails的主要站点,rubyonrails.org上也有大量文档信息,还包括一个wiki。
启动电脑,试用RoR吧
最后一点,开发人员欲善其事,必先利其器。Java、PHP和ASP久经考验、功能强大,但它们缺乏针对MVC型Web应用的专门设计。这些语言和框架使创建Web应用成为可能,但开发者常常受制于它们的设计初衷和强调特性与配置的传统设计哲学,而不能专注于应用本身的功能和意图。
Ruby on Rails针对Web应用量身定做。和传统工具相比,使用它可以更快更好创建Web应用。虽然它还年轻,但步履坚实,已经和即将使用它的公司正在爆炸性增长。
随着硬件和网络带宽价格的下降,下一代互联网将不再容忍那么笨拙、难以修改和升级的Web应用。这些应用必须能够持续更新与提升——两年的版本周期已经缩短到两周,用户的要求越来越苛刻,他们希望得到真正能够满足他们需求和特殊要求的Web应用;因此,Web开发人员必须能够快速编写有助于提升他们应用的代码。即使有一行代码不能直接服务于应用的功能,那也是不能容许的浪费。Ruby on Rails就是专为这样一个应用环境设计的工具。
好了,启动你的电脑,试用Ruby on Rails吧。像作家的生花妙笔、木匠的惯用铁锤,Rails也将成为你开发Web应用的利器。
作者简介:Jonathan Palley是Idapted公司的CTO和创始人之一。该公司是一个位于北京的硅谷创业公司,他们开发的Web和VoIP平台彻底地改变了人们学习语言的方式。在这之前,Jonathan Palley在斯坦佛大学学习物理。