Ruby Taiwan 近況及 Ruby Tuesday #13

Update(2010/8/17): 投影片請見 Rails3 RC 發佈: 重點導覽

最近花了些時間弄了兩個 Ruby Taiwan 子站台。一個是 找文件,主要是翻譯文件,也包括了 Ruby 中文官網。另一個則是 找工作,一個免費的 Job Board。請大家多多支持。

另外就是 Ruby Tuesday 聚會第十三場確定了,因應 Rails3 RC 發佈,這一次的主題為 Rails3,包含三場小演講:

* ihower: Rails3 changesets
* tsechingho: Rails 3 專案產生器 (generator 實例應用)
* xdite: Upgrading to Rails3

時間: 2010/8/17(週二)晚上七點到九點半。
地點: 台北市 果子咖啡
報名網頁: registrano.com/events/ruby-tuesday-13

還有,我開了一個唯讀的 Mailing list 在 groups.google.com/group/ruby-taiwan,方便大家第一時間收到 Ruby Taiwan 的重要訊息,例如有什麼活動之類的。

Enterprise Rails: 資料庫篇

稿子是 2009 年底寫的,本來想一口氣連 SOA 的部份一起整理 (對 SOA 有興趣的話,可以看我之前的投影片 91 頁 ~ 119 頁),不過一直拖到現在。雖然內容還是有點些雜亂,而且現在流行 NoSQL (?),不過還是就貼出來吧。

這本 2009 年底的書,趁著轉職的空檔終於一口氣讀完了。有些重要的背景知識,如果你一直追所謂的最新技術,反而是學不到。這本書其實講 Rails 不多,前一大半都是在講 RDBMS 關聯式資料庫,後半則是 SOA 架構。

像 RDBMS 裡面的 referential integrity、composite key、DB View、triggers、materialized View 這些東西,因為身為 MySQL 使用者很少用到、Rails 也沒有內建支援,實際寫 code 好像也都在 application layer 應用層處理掉了,所以到底要幹嘛用的呢? 他們被發明一定有原因吧? 是在什麼情境上使用呢?

第四章 Database As a Fortress

作者非常強調資料庫的重要,一家企業最重要的資產是資料,而不是員工 (資料不見就全完了,員工沒了再找就有了)。Framework 會變、程式會有 bug、可能也不會只有 Rails 會去存取 DB (所以只用 ActiveRecord Validation 並不可靠),總之只有你的 RDBMS 可以保障資料的正確性,data integrity 長存。

PostgreSQL 也比 MySQL 適合企業應用,因為很多 SQL 標準 MySQL 並不支援 (很多直到 5.0 後才支援)。PostgreSQL 會輸掉市佔率的原因是 1. MySQL 背後有一家商業公司支持 2. PostgreSQL 有很長一段時間不像 MySQL 有提供 Windows 安裝包 (話說這本書對 MySQL 其實還蠻揶揄的)。

作者也不建議使用 migration,因為它沒有支援所有的 DDL (Data Definition Language)。反正只有跑一次,不像 DML (Data Manipulation Language) 會一直用,用 ORM 比較方便。

作者持續強調 referential integrity 對企業層級的上線應用程式非常重要。會說不重要的,一定是該死的 MySQL 使用者(died-hand MySQL users),千萬別因為你的工具(i.e. MySQL, Rails)沒有這個功能,就說不重要。

隨著資料越來越多,你的老闆會想要知道一些數據分析報表,這裡作者點出一個重要的議題:你的報表會害了你的網站!! 你越常去產生你的報表,你的網站就會越來越慢。”Report are killing your site!!” 為什麼呢? 因為去 DB 撈大量的資料做報表,是非常龐大複雜的 SQL 操作,你的 DB 的效能會被拖慢,整個網站的效能就跟著 down 下來。該怎麼解呢? 撈 report 的 queries 不要對 production database 做就沒事了吧?

這裡作者又對 MySQL users 揶揄了:因為大多數的 MySQL users 第一個想到的方案就是使用 master/slave 來解決,也就是讓 query report 對 slave DB 做,但是即時如此不會影響到 production 運作,也沒辦法解決 query 查詢很慢的問題。

這裡觀念上要區分的是 OLTP (Online Transaction Processing) 和 OLAP (Online Analytical Processing 這兩種類型的 query,前者是一般的讀/寫/更新,這種是會讓使用者可以馬上等結果,大部分的前台網站就是屬於這種。後者則是一個 query 就須需要收集上百萬的資料去分析,例如:有多少客戶買了產品A,又買了產品 B,依照地點跟時間。

因為 OLTP 和 OLAP 是如此不同,沒道理 database 的設計也相同。OLAP 要快,就必須使用 denormalized 非正規化的方式來存放資料。但是將正規化資料和逆正規化資料混雜在同一個 DB,則非常容易造成資料的混亂不同步,寫出 buggy 有臭虫的程式。

要解決這個問題的領域是使用獨立的 data warehouse 存放 denormalized 的資料。(書就到此打住了,作者推薦了 The Data Warehouse Toolkit: The Complete Guide to dimensional Modeling by Ralph Kimball 一書)

第五章 Building a Solid Data Model

Data layer 層級的 constraint 才保證一定正確,因為應用層的 Model validation 可以跳過,放在 code 裡也容易被改掉。作者也示範了這兩者都可以寫單元測試。

referial constraint 如果只用 Model 做,destroy 時就會失效。而 Model 要做 referial constraint 只能用 has_many :depentent 但是如果誤用了 :delete 就 orz 了。因此最保證的作法還是 data layer 做。

資料庫記得加 index 在 1. foreign key 2. 任何有 SQL where 條件的地方

第六章 Refactoring to Third Normal Form

3NF (三階正規化) 能做到 DB 資料不重複:只要不是 primary key、不是 foreign key、不是 intrinsic data(eg. name)、不是 measured value (例如 time, temperature),而是一個 literally bound data,都應該正規化出來新建一個 table。不先做,後來要加會十分痛苦,尤其在一個已經有資料的 production db 上。

範例中的有很多 table 都有相同的地址欄位定義:Postgres 支援 multipie inheritance,可以處理重複的 DDL,再搭配上 Rails 可用 plugin mixin 處理重複的 code。

這裡留下一個第八章才回答的問題,只有一個 primary key 沒辦法一次撈出 has_many 的 has_many 資料。多保留一個 foreign key 又可能造成 direct 和 in-direct 的資料不一定一致。

第七章 Domain Data

Domain data (指網站預先就有的必要初始資料,又叫做 seed data) 也應該使用 table 存,因為 1. 保持 referential integrity 2. 維護 3ND 跟擴充彈性。實作上則可以做成常數形式。

strategy patterns with domain tables 這招示範了將 Order PAYMENT_TYPE 變成 domain tables 來做,除了變成 constant object,情境是如果不同 payment_type 會有不同的 validation。首先變成 constant 後,可以順利將 validation code 都從 order 搬走。而再進一步 rails single table inheritance 和 template method 將 validation 分散到個別的 domain model。總之,作者將 domain data 的彈性做了非常好的示範。

雖然正規化會導致大量的 table 和 model,但是也因為如此每個都很小非常容易測試,bugs 也就容易集中在小區域容易找到。

第八章 Composite Keys and Domain Key/Normal Form

這章討論 composite key 的優缺,以及如何使用。

ID column 系統的優點 1. Rails 內建 2. 簡單,除了 primary key 之外皆可修改,物件的 primary key 一定不變 3. 提供與真實資料的間接性,因此也不需有修改 primary key 的機會。 4. unique key 好做

composite key 的優點就沒這麼顯而易見,也不一定用的到。他的用途在於提供一種特別的 data integrity。table 中不一定可以發現有 natural composite key, 但是如果有而你忽略他,可能會有大問題。這個情境就是:

只有一個 primary key 沒辦法一次撈出 has_many 的 has_many 資料,如果只是加上一個 reference key, 可能導致 refential integrity hole. 這時 composite key 才是唯一解決之道。

DFNF 比 3NF 更近一步保證 referential integrity 在複雜的 relationship 中。不能因為工具沒有,就覺得不重要。因為 Rails 沒有內建,導致很多人不知道 DK/NF or natural keys 等這些已經發展成熟的資料庫基礎理論,在設計 schema design 上而有很大的缺陷。

先來檢討 single column ID 是否應該用 Rails 內建的數字,首先找有沒有別的 unique column,再來覺得它是否不會 或 不常變更,特別是連編輯介面都沒有的 domain data 特別符合這個條件,如果是,則可以用 set_primary_key 換掉,移除不必要重複 id column。不過,如果不是 domain data, 我們就必須產生這個 primary key,用法是寫在 before_create 裡,另外要注意還是使用 self.id。一個額外的好處是,這些可能被當做 foreign_key 的 nature key,也是有用的資訊,不像本來的數字 id 一定還要去本來的 table 查。

這裡我有個疑問是 Is there a REAL performance difference between INT and VARCHAR primary keys? ,在我碰到的例子是,很多人擔心非 integer 當 key 會影響效能 :/ (題外話,很多人愛用 type code,但都被我建議改成 string constant ) 但是我想這差距的微乎其微,尤其在你沒有上百萬的資料列。重點還是,你選的 nature key 不需要有被修改的可能 :>

Rails 要支援 composite keys 有兩個方法,一個是使用 Dr. Nic Williams 的 plugin,一個是本書作者的 Rails-DK/NF hybrid 法:

具體的作法是,保留 ID column 欄位,但是 DB 還是加上 composite key 的 foreign key referential integrity 限制。好處在 Rails 裡面不方便改 primary key, 當有修改的需求時,使用 hybird 法就不錯簡單(不需要裝plugin)。但是回過頭想,如果你需要修改 nature key,可以先想想是不是最好的作法應該是刪掉舊的,插入新的。

一個小技巧是附寫掉本來的 writor,這樣就不需要手動設定 composite key 的值了。如果要改已經被 reference constraint 限制的 composite keys 怎麼辦? 直接改會爆,這時候需要使用 deferrable constraints 的機制和 transaction。

不過 hybrid 法的缺點是 1. 因為維護兩套 index key 的關係, 新增修改刪除的 index cost 比較高。 2. 需要多寫上述的 code 才能省掉手動設計的麻煩。

書沒寫哪個最好,看起來是如果非得有修改 nature key 的需求才只用 hybrid 法。

第九章 Guaranteeing Complex Relationships with Triggers

stored procedure 和 triggers
使用 PL/pgSQL 做例子。

第十章 Multiple Table Inheritance

Rails 的 polymorphic associations 功能讓你可以定義兩個 table 的關係,不需要事前知道是哪一個 table。

不過,polymorphic associations 違反了 referential integrity !! 原因很簡單,既然不知道 _id 會指到哪個 table,自然也就沒辦法在 DB layer 加上 foreign key constraint

什麼是多型?

要達成多型的方式,實作上要考慮的是 STI 或 MTI:前者 Rails 有內建,後者 Rails 有用了 polymorphic 方式來達成 XOR relationships,也算是一種簡易的 MTI。

這本書用了 logical 和 physical models 來分別描述概念上和實際上的切法

STI 適用於 subclass 共用很多 data。如果共用的不多,除了浪費 table, model 也會被 getter 和 setter 污染。另外因為 class name 寫在 table 了,如果要修改 class name 會很麻煩。

這裡提的 MTI 作法是,還是為了要用到的 foreign key 開欄位,並且也為每個關係加上 belongs_to,但是允許 null 值,因為要做 XOR on columns: 要用 boolean 做 XOR,兩個還好做,超過三個就複雜了。這裡的作法很聰明,轉成數字再相加,只要檢查是不是等於 1,用這樣的方法做 database constrant check。

都加了 belongs_to,要怎麼做到多型。這裡作者使用了 reflection 和 inheritance relationship 的技巧,在 parent object 上實作了 getter 和 setter method,厲害。我們也可以實作一個 Factory method 在 parent 上,來建構適合的 sub-class。

第11章 View-Backed Models

有些複雜的 join 和 conditions 單靠 active record 寫不出來(畢竟簡單好用的東西,畢竟是犧牲一些不常使用的功能),而必須寫一些很醜的 SQL 混雜 active record,但是這樣就失去使用 active record 的意義(與DB-independent)。如果要漂亮,只能先盡可能先撈出來,然後再從 application layer 層過濾資料,但是這樣又失去效能,畢竟撈資料是 DB 的專長。

解法是 view-backed model,materialized view。

database view 有兩個定義 1. named subquery 2. a table that is defined by an algorithm
後者的實作是,我們先用 SQL 定義 DB view,然後就可以用 ActiveRecord 代表那個 view/table,更 cool 的是,還可以加上 association!! 當然,這是 read-only 的,記得不要加 destroy。

subquery 的定義也表示,你不能對 view/ view-backed models 操作 insert/update/delete/reference/constraint/index。
references 也不需要,如果你要 reference,就表示你應該加到 view 裡,constraint 也是。

indexing 也無,畢竟是 just-in-time 撈出來的資料。但是本來的 index 欄位當然還是有作用。別氣餒,畢竟本來使用 View 的目的不是效能,而是為了讓 AR 更簡潔清楚。如果真的需要效能,下一章的 materialized view。

第12章 Materialized Views

materialized view 就是 cached 的 view。既然是 cache,就一樣有 cache 的議題: 資料的更新,何時由誰 expire?

materialized view 是 cache-complete copy of view, 也是全部都有留資料,不像 memcached 會把不常用或超過記憶體的資料移除。

幾個重點: 1. materialized view 既然是實體的 table,也就可以加 index 2. 要加上 refresh function 當偵測到 base table 有修改 3. 或是 invalidation function 如果不想馬上更新 4. 在 base table 加上 triggers 以偵測修改 5. auxiliart view, reconciler view 隱藏實作細節

不過看完還是覺得使用 materialized view 真是挺難的!! XD

Rails3: 新的 Metal 機制

新的 Rails3 Controller 重構後,變成 ActionController::Base < ActionController::Metal < AbstractController 的繼承體系。除了 ActionMailer 繼承自 AbstractController,讓 API 與 Controller 一致之外,新的 ActionController::Metal 更是充滿玩味。

故事可以從上個禮拜,Yehuda 把 Rails 2 的 Metal 移除了(commit),根據 commit 的說明,Rails 2 的 Metal 在 Rails 3 裡面,可以用 1. 放在 Route 之前的 Rack middleware 或 2. 放在 Route 之後的一個 Rack endpoint,而這個 Rack endpoint,除了自己實作 Rack app,我們也可以用 ActionController::Metal 來實作出自己的客製化 Controller。

Rails2 發明 Metal 原因是,有些事情不需要完整的 Controller 功能,希望能夠越快越好,例如 XML/JSON API 等。而 Rails2 的 Metal 雖然非常快,但是沒什麼彈性,完全不能使用 Controller 的功能,例如用 Layout, Template, Sessions 等,基本上就跟單純的 Rack middleware 沒什麼兩樣。但是在 Rails3 中,可以透過自繼承 ActionController::Metal 做出白紙般的客製 Controller,可以有非常快的速度,如果有需要用到 Controller 的功能,也可以任意選擇組合加入,十分彈性。

例如,我們來實作一個超級精簡的 Static Controller:


# lib/static_controller.rb
class StaticController < ActionController::Metal
  include ActionController::Rendering
    
  append_view_path "#{Rails.root}/app/views"
    
  def about
    render "about"
  end
end
    
# config/route.rb
match '/about', :to => "static#about", :as => :about

這個範例有接近於 Metal 的速度,並加入了 Controller 的 Template 功能 (你可以參考 ActionController::Base 這是擁有全部功能的版本)。其中 “static#about” 是 StaticController 的 about action 縮寫,而在 Rails3 中,controller action 也都是一個 Rack app。

除了我的這個簡單範例,在 The Powerful New Rails RouterUpgrading a Rails 2 App to Rails 3 的影片中,也有分別舉例。

總而言之,如果你在 Rails3 中不需要全部的 Controller 的功能,想要盡量拉高效能,有幾種推薦作法:

* 寫成 Rack Middleware,然後在 config/application.rb 中插入 config.middleware.use YourMiddleWare
* 寫成 Rack App,在 config.route.rb 中將某個對應的網址指到這個 Rack App
* 繼承自 ActionController::Metal 實作一個 Controller,其中的 action 也是一個 Rack App

其中的差異就在於後兩者會在 Rails Route 之後(好處是統一由 route.rb 管理 URL 路徑),如果繼承自 ActionController::Metal 可以有彈性獲得更多 Controller 功能。原則上,我想我會推薦 ActionController::Metal,寫起來最為簡單,一致性跟維護性較高。

另外,還有個小玩意, ActionController::Middleware 是 Controller 層級的 Rack Middleware,讓你可以在放入到某個特定 Controller 之中(也就是只有該 Controller 使用這個 Middleware)。不過呢,這個功能我到現在還沒看到任何實用的例子就是了。

最後,Yehuda 提供了一個 參考數據


fast: through middleware inserted at 0
slwr: through middleware inserted via @use@
rotr: through endpoint sent via the router
bare: through ActionController::Metal with self.response_body
cntr: through ActionController::Base with self.response_body
text: through ActionController::Base with render :text
tmpl: through ActionController::Base with simple render :template
layt: through ActionController::Base with render :template with layout

         real     rps
fast   0.004271   2900 Rack 極限
slwr   0.067029   2200 使用 config.middleware.use YourMiddleware
rotr   0.088085   2000 經過 Rails Route 之後
bare   0.103868   1900 使用 ActionController::Metal 的最快速度
cntr   0.355898   1070 使用 ActionController::Base 的最快速度
text   0.557127    825 使用 ActionController::Base 加上 render :text
tmpl   0.639581    765 使用 ActionController::Base 加上 render :template
layt   1.678789    375 使用 ActionController::Base 加上 Template 跟 Layout

Bundler: Rails3 用來管理 Gem dependencies 的神器

Update(2011): Bundler Pro Tips
Update(2010/7/27): Bundle 1.0.0 之後不需要 bundle lock 了,只要 bundle install 就會自動 lock。預設都裝到 system gem 的位置。

Bundler 是一套為了 Rails3 所打造的全新 Gem dependencies 管理工具:一套基於 Rubygems 的更高階套件管理工具,適合讓 Application 管理多套 Gems 依存關係的複雜情境。而你在 Rails3 中 (Bundler 不只用在 Rails3,其他例如 Sinatra 或是 Rails2 也都可以使用) 要使用的 Gems,也都必須宣告在它的 Gemfile 裡,沒寫在裡面的話,就算手動 require 也找不到。這跟已往你可以直接 require 任意 rubygems 不同,在使用 Bundler 的環境中,要 require 什麼 rubygems 必須透過 Gemfile 管理。

Gemfile 的寫法大致如下:


  # 第二個參數可以指定版本
  gem "rails", "3.0.0.beta3" 
  
  # 如果 require 的檔名不同,可以加上 :require
  gem "sqlite3-ruby", :require => "sqlite3"
  
  # 可以用 Git 當做來源,甚至可以指定 branch, tag 或 ref。
  gem 'authlogic', :git => 'git://github.com/odorcicd/authlogic.git', 
                            :branch => 'rails3'
  
  # 可以直接用電腦裡的其他目錄
  gem "rails", :path => '/Users/ihower/github/rails'
  
  
  # Group 功能可以讓特定環境才會載入
  group :test do
    gem "rspec-rails", ">= 2.0.0.beta.8"
    gem "webrat"
  end


設定好 Gemfile 之後,我們有一些指令可以用:

  • bundle check 可以檢查目前缺少哪些 rubygem,然後你可以手動透過 sudo gem install 安裝到系統裡。
  • bundle install 安裝所有需要的套件。如果系統已經有裝了,就用系統的,不然會裝到 $BUNDLE_PATH 下,預設是你家目錄 ~/.bundle (因此請不要用 sudo 執行 bundle install)。如果來源是 git (例如上述的 authlogic),每次執行 bundle install 就會自動 git pull 更新,十分方便。
  • bundle lock 和 bundle unlock 會做 snapshotting 記錄下目前所有套件的版本在 Gemfile.lock,建議這個檔案也一起 commit 出去。適合要佈署或多人開發時,可以確保大家的版本都一致。不用手動lock了,bundle install 時就會產生 Gemfile.lock
  • bundle package 如果你的 Server 沒聯外網路,或是怕 rubygems.org 連不上,可以用這個指令把所有套件都打包到 vendor/cache 下。基本上,跟以往 Rails 1.X 2.X 時代佈署時會建議你盡量打包依存套件並 commit 出去,在使用 Bundler 後已經大大地不需要了,因為透過 bundle lock 我們就可以確保每台機器上執行的套件版本一致。
  • bundle exec 因為 Bundle 可以說是獨立出一個套件環境,所以如果有非 Rails 的指令需要執行,而且你的系統 Gems 又沒有安裝,那就會需要透過 bundle exec XXX 來執行。例如 bundle exec cucumber。
  • bundle show gem_name 可以查看這個 gem 的目錄位置
  • bundle open gem_name 可以用編輯器打開這個 gem 的目錄

開發 Rails3 實際用一陣子之後,發現很偏好將套件裝成 Gem 了(如果有提供 Gem 版的話),之前 Rails 1.X 2.X 時代會比較喜歡裝成 Plugin,因為想說別人要裝 Gem 可能會有問題,以及佈署也怕出包。但是有了 Bundler 之後,只要 Bundle install 就可以裝好並確保大家的版本一致會動。不像已往的 rake gems:install 超不可靠。可以透過 Bundle 裝這些依存套件也減少了需要 commit 出去的 vendor/plugin 檔案,讓你的專案 repository 變乾淨了。另外,我也超喜歡的 Bundler 可以支援 Git 來源,只要 bundle install 就會更新,不需要額外的管理工具去煩惱更新 plugins。

其他推薦閱讀:

演講: Designing Beautiful Ruby APIs @ RubyConf China 2010

感謝 Shanghai on Rails 社群再度邀請我前往上海,參加今年的 RubyConf China 2010 大會,日期是 2010/6/26。

我的演講的題目和上個月我在 RubyConf Taiwan 一樣是 Designing Beautiful Ruby APIs,所以可以先預習我之前的投影片。雖然題目一樣,不過內容應該會再好好編排過,預期再加入一些 DSL 素材範例。

說到上海,好像應該去世博會走一走(?)

Rubygems 套件管理工具

(本篇的對象是 Library 開發者,如果您是單純的 Library 使用者,可以先參考 RubyGems 簡介)

套句流行的話,RubyForge Must Die!! 大概是今年二月底的事情 (外電報導簡中翻譯),新的 gemcutter.org 取代了 rubyforge.org,目前已經成為 Ruby 社群的官方 Gem 儲存庫:rubygems.org,包括 gems.rubyforge.org 也是指向 rubygems.org

RubyForge.org 最大的問題之一,就是他不是用 Ruby 寫的(無誤),而新的 rubygems.org,使用了 Rails 當做前台,以及 Sinatra 當做 Gem server,背後技術包括 delayed::job 跟 Amazon S3 來處理所有上傳的 Gem package,讓發佈 Gem 的速度也大大提昇。整個站甚至也 open source 出來在 github.com/qrush/gemcutter。問題之二,就是 RubyForge 的介面實在不怎麼樣,功能也雜亂,而新 rubygems.org 有著全新的簡潔介面,提供具有親和力的資訊。問題之三,就是 API 了,新的 rubygems.org 配合新版的 rubygems library (1.3.6以上,如果你還沒升級,請打 sudo gem update –system),讓發佈 Gem 變得非常簡單。

還有個插曲,之前 github.com 有一度可以支援當做 Gem server,不過自從 gemcutter 計畫展開之後,他們就決定取消這個功能,請見 hasmygembuiltyet.org

如何打包及發佈 Gem

說到發佈 Gem 啊,我一直以為是有點麻煩的事情,最早我曾經用過 Hoe,也看過 jeweler 這個工具。不過一直到前一陣子看了 Yehuda 的 Using .gemspecs as Intended 這篇文章之後,才發現其實很簡單,根本不需要用到其餘的工具啊,那到底要怎麼做呢?

首先,我們要把你的 library 打包成一個 Gem package,假設我們的 library 叫 foobar 好了:

步驟一: 撰寫 foobar.gemspec 檔,這是一個描述 Gem package 的 metadata 檔案。以下是一個基本夠用的範例。如同 Yehuda 所說的,你其實不需要用其他的工具來產生這個 gemspec 檔案。

 

 Gem::Specification.new do |s|
  s.name        = "foobar"
  s.version     = "1.1.1"
  s.date        = "2010-05-14"
  s.authors     = ["Wen-Tien Chang"]
  s.email       = ["[email protected]"]
  s.homepage    = "http://example.org"
  s.summary     = "blah"
  s.description = "blah blah"
 
  # s.add_dependency('log4r', '>= 1.0.5')
  # s.add_dependency('log4r', '~> 1.1.0') # 表示 1.1.y 都可以 
  # s.add_dependency('log4r', '~> 1.0') # 表示 1.x.y 都可以 
  # s.add_runtime_dependency # add_dependency 的別名
  # s.add_development_dependency # 只有在 gem install xxx --development 才會安裝
   
  s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README) # 只有列在這裡的檔案會打包到 Gem package 裡面。
  # s.executables = [] # 放在 bin 下的執行檔有哪些
end

Update1: 這裡有另一個範例參考 Be awesome: write your .gemspec yourself
Update2: Bundler 也可以產生空的 Gem,如果想讓 library 也用 bundler 管理 gem dependencies 可以考慮Developing a RubyGem using Bundler

更多完整的規格請參考 docs.rubygems.org/read/chapter/20

步驟二:執行 gem build foobar.gemspec 便會包裝出單一 Gem package 套件檔 foobar-1.1.1.gem

這時候,你就可以透過 gem install foobar-1.1.1.gem 來安裝到自己的電腦了。如果跑 gem server 起來,別人也可以透過你的 gem server 安裝這個套件 (gem install GEMNAME –source http://your_gem_server_host:8808)。

接下來,怎麼發佈 Gem 到 rubygems.org 讓全世界的開發者都可以安裝你的大作呢? 在 Rubygems 1.3.6 之後已經內建有 gem push 功能:

步驟一:在 rubygems.org/ 註冊一個帳號,拿到 API key,加到 ~/.gem/credentials 裡。
步驟二:執行 gem push foobar-1.1.1.gem 就會發佈出去了

Ruby Library 最佳實務

Update: 可以參考 Ruby Packaging Standard, 0.5-draft

一個 Ruby library 的組成,大致都是這樣的:

* README
* lib 目錄和會被使用者 require 的檔案,例如 lib/foobar.rb
* test 目錄 (optional)
* example (optional)
* bin 目錄 (有執行檔的話)
* LICENSE

如果你沒什麼概念,可以看看 Ruby Best Practice 一書的第八章 Skillful Project Maintenance,作者講解了一個 Ruby Library Project 的組成,還有介紹到如何用 RDoc 跟 Rakefile。如果你比較初學,建議一看。

接著 Gem Packaging: Best Practices 這一篇非常值得一看,介紹一些最佳實務,像是:

1. 不要在你的 library 裡依賴 rubygems。例如 require ‘rubygems’、rescue Gem::Load、gem “foo” 等等都不要用
2. 因為 lib 這一層目錄會進 $LOAD_PATH,所以不要放不是要給終端使用者 require 的檔案在 lib 下 (而且命名也不要太 general,不然可能會跟其他 library 撞到名字),其他檔案用 module namespace 的方式放到 lib 的子目錄下。例如你看 github.com/nex3/haml 的 lib 下就只放了 haml.rb 跟 sass.rb。
3. 承上,在 foobar.rb 中如果要 require 其他檔案,不需要寫 File.dirname(__FILE__) ,直接 require “foo/bar” 就會 require lib/foo/bar.rb 這個檔案了。

如果你想繼續多了解一些故事,可以看看:

* Gemcutter(rubygems.org) 作者在 RubyConf 2009 的演講:Gemcutter: The Next Step in Gem Hosting 以及他的投影片 next.heroku.com/
* Yuhada 在 RubyConf 2009 的演講: Polishing Rubygems