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在斯坦佛大學學習物理。