| 2006 年 11 月 16 日 本系列文章包括兩部分,第 1 部分 介紹了 Streamlined,這是個(gè)基于 Rails 的開(kāi)放源碼框架,它組合了 Ajax、元編程以及代碼生成的強大功能,把 Rails 的效率提升到了一個(gè)新的層次。第 2 部分將探索 Streamlined 背后的元模型是如何支持定制的。 本系列文章包括兩部分,在 第 1 部分 中,我展示了一種稱(chēng)為搭建的 Ruby on Rails 特性,介紹了它的限制,然后演示了稱(chēng)為 Streamlined 的框架如何可以把搭建提升到構建功能性用戶(hù)界面這一層次。但是即使使用最高級的生成器,用戶(hù)界面也總是需要些定制。如果不能有效地擴展所構建的內容,那么構建得再快,也不會(huì )擁有有效的應用程序開(kāi)發(fā)框架。這篇文章將介紹 Streamlined 背后的定制模型的基礎:Streamlined 元模型。 Streamlined 的快速回顧 | 關(guān)于本系列 在 跨越邊界 系列中,,作者 Bruce Tate 宣揚了這樣一種觀(guān)點(diǎn):當今的 Java 程序員通過(guò)學(xué)習其他方法和語(yǔ)言會(huì )得到其他思路。自從 Java 明顯成為所有開(kāi)發(fā)項目的最佳選擇以來(lái),編程前景已經(jīng)改變。其他的框架正在影響搭建 Java 框架的方式,從其他語(yǔ)言學(xué)到的概念可以影響您的 Java 編程。您編寫(xiě)的 Python(Ruby、Smalltalk……)代碼可以改變您處理 Java 編碼的方式。 本系列介紹了與 Java 開(kāi)發(fā)有根本不同但是卻直接適用的編程概念和技術(shù)。在某些情況下,需要集成這些技術(shù)以利用它。在其他情況下,則可以直接應用這些概念。比起其他語(yǔ)言和框架能夠影響 Java 社區的開(kāi)發(fā)人員、框架甚至基本方法這一概念,單獨的工具不是那么重要。 | | 在 第 1 部分 中,我構建了一個(gè)簡(jiǎn)單的 Rails 應用程序,存儲了一些進(jìn)行山地單車(chē)運動(dòng)的 trail 并將它們按其臨近的城市加以組織。我開(kāi)始時(shí)創(chuàng )建了一個(gè)模型,包含兩個(gè)模型對象和它們之間的關(guān)系。然后運行 Streamlined 應用程序生成器,它生成了一個(gè)健壯的應用程序,生成的應用程序有以下特性: - 完整的用戶(hù)界面,包含布局,帶有鏈接指向每個(gè)模型的管理頁(yè)面。
- 每個(gè)模型有一個(gè)列表頁(yè)面,可以按任意列排序或根據所有列的內容進(jìn)行過(guò)濾。
- 支持 Ajax 的列表,可以顯示、編輯、刪除或創(chuàng )建條目。
- 導出程序可以生成視圖的 XML 或 CSV 導出。
隨著(zhù)年輕的 Streamlined 框架的發(fā)展,對于基礎應用程序模板所做的增強將可以用于所有 Streamlined 應用程序。目前已經(jīng)策劃的增強包括:基于 REST 和 SOAP 的 Web 服務(wù),以及稱(chēng)為 皮膚 的樣式模板。 定制默認應用程序 現在要定制 Trails 應用程序的代碼。如果想和我一起編寫(xiě)代碼,需要安裝 Ruby、Rails 和 Streamlined (參閱 參考資料)并構建 第 1 部分 中的應用程序。將在這個(gè)基礎上進(jìn)行添加和改動(dòng)來(lái)定制 Trails 應用程序。 如果還沒(méi)這樣做過(guò),請通過(guò)輸入 script/server 從項目目錄中啟動(dòng) Web 服務(wù)器(在 Windows 上是 ruby script/server)。把瀏覽器指向 http://localhost:3000/Trails/list 這個(gè) URL,啟動(dòng) Trails 視圖。圖 1 顯示了基本的 Streamlined 視圖。如果您已經(jīng)添加了數據,那么會(huì )注意到與 Rails 搭建不同,Streamlined 替您管理了關(guān)系。還會(huì )注意到關(guān)系依賴(lài) id 字段。 圖 1. 默認 Streamlined 應用程序
Streamlined 定制策略 這個(gè)實(shí)現可以工作,但對用戶(hù)來(lái)說(shuō)不是最好的實(shí)現。用戶(hù)希望看到賽道位于的城市的名稱(chēng),而不僅僅是個(gè) ID。實(shí)際上,這個(gè)應用程序的用戶(hù)永遠不需要看到 ID。在我解決這個(gè)問(wèn)題之前,我要先介紹 Streamlined 的定制策略。 有些應用程序生成框架會(huì )強迫用戶(hù)在這個(gè)時(shí)候修改生成的代碼。Rails 搭建當然可以這樣工作,但這僅僅是擴展生成的應用程序的三種方法之一: - 可以修改生成的代碼。
- 可以用編程方案(例如繼承)和定制掛鉤(例如 Visual Basic 腳本)編寫(xiě)擴展應用程序的新代碼。
- 可以改進(jìn)可用的元數據,這樣框架就可以生成更智能的應用程序。
這些方法各有各的用途。隨著(zhù)代碼復雜性的提高,第一個(gè)策略變得越來(lái)越無(wú)用,因為用開(kāi)發(fā)人員不可能編寫(xiě)復雜代碼,這會(huì )加大開(kāi)發(fā)人員的負擔。應當把這個(gè)策略局限在擴展非常簡(jiǎn)單的代碼上。Streamlined 通過(guò)添加新運作,使得可以容易地修改現有控制器。 第二種方法 —— 用新的定制代碼擴展應用程序 —— 是多數快速應用程序開(kāi)發(fā)環(huán)境采用的常用方法。一般來(lái)說(shuō),當框架設計者考慮添加定制的空間時(shí),這個(gè)策略工作得最好。Rails 與 Ruby 的動(dòng)態(tài)特性結合,提供了許多開(kāi)發(fā)人員意想不到的定制點(diǎn),使這個(gè)策略特別吸引人。在 Streamlined 中,經(jīng)常會(huì )創(chuàng )建應用程序視圖的新模板。也可以利用通用的 Rails 架構,在自己的橫切關(guān)注點(diǎn)上(例如安全性)展開(kāi)。 第三種方法,是改進(jìn)的元模型,它是 Streamlined 的基礎。在 第 1 部分 中,看到了搭建如何使用活動(dòng)記錄提供的元數據 —— 類(lèi)型、列名稱(chēng)和關(guān)系 —— 來(lái)構建復雜的用戶(hù)界面。在 Java 應用程序內,經(jīng)常用 XML 配置實(shí)現這個(gè)策略。這當然是提供元數據的一種方法,但是要查看的數據太多,我擔心有朝一日,我的眼睛會(huì )壞掉。
通過(guò)元模型修改顯示內容 | 郵件合并元編程比喻 在 跨越邊界 系列以前的文章中,已經(jīng)看到過(guò) Ruby 中的模板和元編程技術(shù)。請把 Ruby 模板想像成郵件合并文檔。然后,Rails 使用元編程來(lái)生成和使用元模型(例如數據庫模式或列定義),這好比郵件合并應用程序中的名稱(chēng)列表。Streamlined 引入了兩段基本代碼,模仿了這種方法。視圖模板允許 Streamlined 不僅僅生成一個(gè) “郵件合并” 文檔,而且還會(huì )生成應用程序中的所有類(lèi)似視圖。Streamlined 通過(guò)活動(dòng)記錄、也通過(guò)定制元模型來(lái)利用元數據。這些部分與 Streamlined 生成器和庫組合在一起,構成了一個(gè)很優(yōu)秀的應用程序生成平臺。 | | Streamlined 并不處理這個(gè)麻煩,而是支持通過(guò)元編程和模板的有效定制。要理解這個(gè)方法,請想像一下郵件合并應用程序。郵件合并應用程序得到名稱(chēng)列表和包含消息的模板。模板有些用來(lái)插入細節(例如名稱(chēng)、地址和電話(huà)號碼)的定制位置。請把名稱(chēng)列表想像成用來(lái)定制模板的元數據。 Streamlined 就像個(gè)郵件合并 —— 只是它為每個(gè)模型對象使用元數據來(lái)創(chuàng )建整個(gè)應用程序,而不是郵件。要提供 視圖模板 或使用 Streamlined 提供的默認視圖模板。首先,要求 Streamlined 生成應用程序模板和所指定的每個(gè)模型類(lèi)的元數據的占位器。接下來(lái),通過(guò)元模型指定定制的用戶(hù)界面選項,例如想讓 Streamlined 如何顯示關(guān)系。然后,告訴 Streamlined 如何呈現每個(gè)視圖。Streamlined 把元模型中的數據應用到模板,把它們組合起來(lái)形成 所指定的每個(gè)模型的新視圖,如圖 2 所示: 圖 2. Streamlined 元數據和模板 首先,我要介紹元模型。首先會(huì )看到元模板如何影響應用程序。然后,我要介紹如何用 Streamlined 模板定制應用程序。具體來(lái)說(shuō),請看 app/Streamlined/trail.rb 文件。將看到一個(gè)空白類(lèi)。就是在這里可以定制 trails/list 視圖的某些方面。請把這個(gè)文件編輯成如清單 1 一樣: 清單 1. trail.rb 的 Streamlined 元模型 require File.dirname(__FILE__) + ‘/streamlined_ui‘ class TrailUI < Streamlined::UI relationship :location, :fields => [:city, :state] end | 清單 1 代表 Trails 視圖的元數據。稍后會(huì )看到,Streamlined 可以把這些值替換進(jìn)視圖模板,形成完整的用戶(hù)界面屏幕?,F在,重新裝載瀏覽器。會(huì )看到刷新的視圖,如圖 3 所示: 圖 3. 修訂關(guān)系內容 請注意在關(guān)系表示中的變化。Location 列現在顯示兩個(gè)字段:城市和州。 Streamlined 的創(chuàng )造者們原本可以輕易地把定制數據添加到了活動(dòng)記錄。這個(gè)策略會(huì )把所有配置保持在同一位置。但是他們沒(méi)有這樣做,我非常感謝他們。他們的方法 —— 元數據在獨立目錄中 —— 有幾個(gè)關(guān)鍵優(yōu)勢: - 獨立元數據不會(huì )混合視圖數據與模型數據。在模型和視圖之間的清楚分離,對于降低耦合很重要,并使對視圖的根本修改不會(huì )影響到模型,反之亦然。
- streamlined 目錄支持額外的元數據,而不會(huì )污染活動(dòng)記錄。
- Streamlined 方法允許在一個(gè)地點(diǎn)合并常見(jiàn)視圖特征,非常像活動(dòng)記錄為了持久性考慮而做的那樣。
迄今為止,我已經(jīng)修改了 Streamlined 在 Trails 視圖中表示關(guān)系的方法。元編程框架經(jīng)常需要支持一些外部決策,例如決定哪個(gè)列或內部字段對用戶(hù)重要。默認情況下,Streamlined 排除了特殊的 Rails 字段,例如外鍵、主鍵、時(shí)間戳,而包含了其余字段??梢匀菀椎赜?user_columns 方法覆蓋這些默認設置,這個(gè)方法中有 include 和 exclude 選項。例如,我需要做這樣一個(gè)修改。Description 字段通常擁有文本的整個(gè)段落。通常,用戶(hù)界面不會(huì )在這樣的匯總列表中列出所有字段。我可以用元模型在列表視圖中排除某些字段。方法是編輯 app/streamlined/trail.rb,加上排除 Description 列的代碼,如清單 2 所示: 清單 2. 排除 description 列 require File.dirname(__FILE__) + ‘/streamlined_ui‘ class TrailUI < Streamlined::UI relationship :location, :fields => [:city, :state] user_columns :exclude => [:description] end | 然后,重載瀏覽器,得到圖 4 所示的結果: 圖 4. 刪除定制列
修改顯示結構 Streamlined 更強大的方面之一就是其協(xié)助管理關(guān)系的方法。裝入 http://localhost:3000/locations/list 的視圖。進(jìn)一步觀(guān)看關(guān)系用戶(hù)界面。會(huì )看到一個(gè)數字,它是與地點(diǎn)有關(guān)的賽道的數量。接下來(lái),單擊 location 列下的 Edit 鏈接。將看到一列復選框,如圖 5 所示。這些都是數據庫中的賽道,選中的代表它屬于指定地點(diǎn)。這個(gè)窗口是關(guān)系的視圖。 圖 5. 默認關(guān)系視圖 Streamlined 框架提供了三種不同的關(guān)系視圖: - 默認的
:membership 視圖用復選框代表關(guān)系,如圖 5 所示。選中的框表示成員。Streamlined 猜測要把哪個(gè)字段用于復選框,但是可以用叫作 view_fields 的選項覆蓋它的猜測。
-
圖 6 中的 :inset_table 視圖在內嵌表中顯示相關(guān)字段。在這個(gè)示例中,可以立即在嵌入表中看到相關(guān)數據。
圖 6. :inset_table 關(guān)系視圖
清單 3 顯示了圖 6 中視圖的代碼:
清單 3. 關(guān)系的 Inset 表視圖 require File.dirname(__FILE__) + ‘/streamlined_ui‘ class LocationUI < Streamlined::UI relationship :trails, :view => :inset_table end |
-
圖 7 還顯示了一個(gè)表格,它使用 :window 視圖放置了一個(gè)按需彈出窗口而不是 inset:
圖 7. :window 關(guān)系視圖
:window 關(guān)系視圖的代碼如清單 4 所示:
清單 4. 關(guān)系的 Window 視圖 require File.dirname(__FILE__) + ‘/streamlined_ui‘ class LocationUI < Streamlined::UI relationship :trails, :view => :window end |
這些視圖都支持 Ajax。當在成員視圖中選中一個(gè)復選框時(shí),視圖發(fā)出 Ajax 調用,更新服務(wù)器上的相關(guān)模型。修改是即時(shí)的,立即會(huì )有用戶(hù)反饋。Streamlined 有效地利用 Ajax 模糊了 Web 用戶(hù)界面和富客戶(hù)端之間的界線(xiàn)。完全支持 Ajax 的視圖并不是元編程的典型特性,但是一旦有了良好的基礎,就可以在不斷提高的抽象層次上工作。 Streamlined 框架還允許您決定在關(guān)系總結字段中表示什么。在 圖 3 中,會(huì )看到指定地點(diǎn)全部相關(guān) Trails 的總結。也可以配置總結,顯示合計、所有相關(guān)城市的字段或其他數學(xué)特性(例如它們的開(kāi)放的平均數)。例如,可以指定 relationship :trails, :summary => :list, :fields => [:name, :difficulty],看到圖 8 中的結果: 圖 8. 總結列表 Ruby 的特性支持這個(gè)語(yǔ)法??偨Y行還是 relationship :trails, :summary => :list, :fields => [:name, :difficulty]。這個(gè)代碼調用叫作 relationship 的方法,并傳遞進(jìn)一些參數??梢愿M(jìn)一步,找出到底發(fā)生了什么。在 清單 4 中,看到了每個(gè) Streamlined 配置都繼承自 Streamlined::UI(Streamlined 是個(gè)模塊,UI 是這個(gè)模塊中的類(lèi)。)打開(kāi) app/streamlined/streamlined_ui.rb 文件,可以看到 relationship 的方法定義。在清單 5 中可以看到方法定義: 清單 5. StreamlinedUI.rb 文件中的關(guān)系方法 def relationship(rel, options = {}) ensure_options_parity(options) initialize_relationships unless @relationships @relationships[rel] = {} unless @relationships[rel] @relationships[rel].merge!(options) end | 可以看到兩個(gè)參數:rel 和 options??赡芟肟吹礁鄥?,因為方法調用給出了針對 :trails、:summary 和 :fields 的參數。實(shí)際上,:trails 是 rel 參數,而 :summary => :list, :fields => [:name, difficulty] 是個(gè)名稱(chēng)-值對,代表 options 的單一哈希圖。開(kāi)始時(shí),這個(gè)語(yǔ)法有點(diǎn)讓人迷惑,但是它為 Ruby 提供了模擬已命名的可選參數的能力,而所需的語(yǔ)言的額外開(kāi)支卻很小。
擴展視圖模板 迄今為止,我還不需要修改現有應用程序中的代碼,但是 Streamlined 確實(shí)提供了可以讓視圖定制變得更容易的幾種機制??梢匀菀椎叵胂癯鲂枰趹贸绦騼刃薷囊粋€(gè)視圖甚至所有視圖的場(chǎng)景。首先,我要介紹 Streamlined 如何處理單一視圖。然后,我要展開(kāi)討論,包括進(jìn)應用程序中的所有視圖。 默認情況下,Streamlined 為應用程序中的每個(gè)模型創(chuàng )建一個(gè)視圖,還有一個(gè)針對所有應用程序視圖的視圖模板。要覆蓋單一視圖,可以只修改這個(gè)實(shí)現。例如,用一個(gè)單詞 Testing 代替 app/views/trails/list.rhtml 中的所有代碼。然后,單擊左側欄中的 Trails。會(huì )看到圖 9 中的視圖??梢杂眠@種方法定制系統中的任何單個(gè)視圖。 圖 9. 修改 Streamlined 視圖 獨立地修改視圖是很好的,但是如果想獲得元編程的全部威力,可能想用一個(gè)模板來(lái)構建應用程序中的全部視圖。這種方法會(huì )提供最佳效果,因為是為整個(gè)應用程序編寫(xiě)一個(gè)模板。刪除 app/views/trails 中的全部文件。這會(huì )在運行時(shí)要求 Streamlined 調用公共模板視圖。Streamlined 覆蓋叫作 render 的方法,以實(shí)現這個(gè)目的。停止和重啟 Web 服務(wù)器(因為已經(jīng)修改了底層結構)并重載瀏覽器。將看到 圖 1 中的原始 Trails 視圖恢復了。如果我能選擇,我會(huì )通過(guò)用 no_views 選項調用 Streamlined 生成器,用一個(gè)視圖模板生成我的初始應用程序。 可以在 app/views 中找到所有視圖代碼。側欄、頁(yè)頭、頁(yè)腳、菜單和公共文件都在 streamlined 目錄中。管理關(guān)系的視圖在 streamlined/relationships 中,部分主內容和主內容的視圖在 streamlined/generic_views 中。 清單 6 顯示了 Streamlined 的主模板 app/views/streamlined/generic_views/_list.rhtml 的一部分: 清單 6. Streamlined 模板的一部分(為了可讀性添加了分行) <% for item in @streamlined_items %> <% alt = 1 - alt %> <tr <%= "class=‘odd‘" if alt == 0 %>> <% for column in @model_ui.user_columns_for_display %> <td><%=h item.send(column.name) %></td> <% end %> <% for relationship in @model.reflect_on_all_associations %> <td> <div id="<%= relationship_div_id(relationship, item) %>"> <%= render(:partial => "#{@model_ui.summary_def(relationship.name).partial}", :locals=> {:item=> item, :relationship=> relationship, :fields=> @model_ui.summary_def(relationship.name).fields})%> </div> <%=link_to_function("Edit", "Streamlined.Relationships.open_relationship(‘#{ relationship_div_id(relationship, item)}‘, this, ‘/#{params[:controller]}‘)")%> </td> <% end %> ...deleted code to render links... <% end %> | 請注意兩個(gè) for 循環(huán)。每個(gè)循環(huán)都反映活動(dòng)記錄和 Streamlined 的元數據。第一個(gè)循環(huán)在每個(gè) Streamlined 列上迭代。Streamlined 通過(guò)從活動(dòng)記錄元數據中提取全部列、刪除某些特定于 Rails 的列(例如 id)、排除或包含在 Streamlined 元數據中指定的每個(gè)列(如 清單 2 所示)來(lái)確定有哪個(gè)列要參與。第二個(gè)循環(huán)在活動(dòng)目錄模型中的所有關(guān)系上迭代,并呈現每個(gè)關(guān)系的適當部分。 我也可以組合各種方法??梢跃帉?xiě)一個(gè)應用程序,用一個(gè) Streamlined 模板(帶站點(diǎn)范圍內的編程約定)代表多數視圖, 然后在我需要打破公共的站點(diǎn)范圍約定時(shí),再按照需要覆蓋單個(gè)視圖。這是一種極為強大的開(kāi)發(fā)范式,可以讓人從一個(gè)很好的默認實(shí)現開(kāi)始,然后再在站點(diǎn)范圍內或單一頁(yè)面的基礎上用定制實(shí)現來(lái)編輯或完全替換標準實(shí)現。
整體再回顧 Streamlined 是個(gè)帶支持應用程序生成的富元模型的框架。元模型變成了更高層次的編程語(yǔ)言,提高了效率。在某些方式上,這樣一個(gè)框架在 Rails 中是不可避免的,因為同樣的元編程工具使得搭建可以用于 Rails 框架的所有用戶(hù)。構建更有效的搭建系統只是個(gè)時(shí)間早晚的問(wèn)題。 在 Streamlined 中,得到了這樣一個(gè)框架,它組合了更高的語(yǔ)言抽象、代碼生成和用來(lái)擴展的定制掛鉤。公共的元模型驅動(dòng)著(zhù)每件事。使用這項技術(shù),框架的重點(diǎn)主要放在用戶(hù)界面上,但是可以看到它的前進(jìn)已經(jīng)超越了 Web 頁(yè)面。Streamlined 已經(jīng)或很快就會(huì )提供以下這些特性: - 用于內容聯(lián)合的 Atom 提要:這會(huì )讓 Web 上的其他應用程序可以利用由 Streamlined 應用程序生成的并符合公共標準的自動(dòng) XML 提要。
- XML 和 CSV 導出:Streamlined 允許公共數據格式的導出。
- 查詢(xún)和過(guò)濾器:Streamlined 允許用簡(jiǎn)單查詢(xún)過(guò)濾內容,然后使用結果。
- 基于 REST 的 Web 服務(wù):Streamlined 開(kāi)始時(shí)有 Web 服務(wù)支持,但是后來(lái)清除掉了,因為 Rails 架構師正在重新設計 Web 服務(wù)支持,要將其改成基于簡(jiǎn)單 REST 方式的插件系統(請參閱 參考資料)。
Streamlined 正在努力在不遠的將來(lái)成為一個(gè)元編程框架,允許您創(chuàng )建任意模板,插入現有 Rails 組件、生成器和插件。這個(gè)框架將超過(guò)外觀(guān)感受,進(jìn)而創(chuàng )建一個(gè)公共架構,有可能形成一個(gè)極為強大的企業(yè)應用程序生成器。Relevance, LLC 的 Justin Gehtland 說(shuō)過(guò): “Streamlined 的下幾個(gè)發(fā)行版,意在鞏固它作為元框架的位置。它將包含和重用標準 的 Rails 生成器、切實(shí)可用的插件和其他開(kāi)源框架,從而迅速裝配起整個(gè)應用程序,超出目前提供的 CRUD 支持。我們將可以用與以前同樣的方法進(jìn)行配置:依賴(lài)良好約定,但是提供用于修改默認項的聲明式 DSL?!? 帶有優(yōu)秀定制的更高抽象層次是任何語(yǔ)言(包括 Java 語(yǔ)言)的神圣目標。在下期的 跨越邊界 文章中,我要研究延遲綁定的好處。在那之前,請繼續尋找通過(guò)提升抽象來(lái)增強實(shí)力的方法,并請繼續跨越邊界。
參考資料 學(xué)習 獲得產(chǎn)品和技術(shù) - Streamlined:下載 Streamlined 應用程序生成器并試用。Streamlined 正在快速發(fā)展,所以要下載初始的 alpha 版本。
- Ruby on Rails:請下載開(kāi)源的 Ruby on Rails Web 框架。
- Ruby:從該項目的 Web 站點(diǎn)獲得 Ruby。
討論
關(guān)于作者 | | | | Bruce Tate 居住在得克薩斯州的首府奧斯汀,他是一位父親,同時(shí)也是山地車(chē)手和皮艇手。他是 3 本最暢銷(xiāo) Java 書(shū)籍的作者,其中包括榮獲 Jolt 大獎的 Better, Faster, Lighter Java 一書(shū),最近又出版了 Spring: A Developer‘s Notebook 一書(shū)。他在 IBM 工作了 13 年,現在是 J2Life, LLC 的創(chuàng )始人兼顧問(wèn)。他潛心研究基于 Java 和 Ruby 的輕量級開(kāi)發(fā)策略和架構。 | |