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       = ["ihower@gmail.com"]
  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

Mac 推薦軟體 (2010~2011)

Update(2012/9): 有 2012 年版的 Mac 推薦軟體了
Update(2011/5): 新增 Sparrow、Twitter、iTerm2、Divvy、Flux。

好像每年都整理一次的推薦清單。最近趁換了顆 Intel SSD 硬碟,第一次重灌雪豹,跟去年的推薦軟體相比拿掉了不少 App (越來越清心寡欲了?),新進榜則有四位。(加 * 的是付費軟體)

  • Yahoo! KeyKey: 必備的中文輸入法
  • (2010新進榜) Chrome: 最近幾個月愛用的瀏覽器
  • SubEthaEdit * 協同編輯器,若同事也都用 Mac 超級推薦,可以多人同時(即時)編輯一份文件。當做一般文字編輯也非常順手。
  • iWork* : 包含投影片 Keynote、試算表 Numbers、文件編輯 Pages 三套軟體,也可以拿來開 Microsoft Excel、Word 也大多都可以順利開出來。
  • (2010新進榜) NOTATIONAL VELOCITY: 超級簡單的雲端記事本,最大的好處是可以跟 iPhone 的 Simplenote 同步
  • LittlleSnapper*: 抓網頁或螢幕的 screenshot 並加以管理、加註。
  • Skitch: 螢幕抓圖工具,更棒的這可以直接網路分享,非常方便可以當做溝通工具。
  • VMware Fusion*: 可以在 Mac 上跑其他作業系統,最常見的被迫需求情境是 Windows + IE 瀏覽器。
  • LaunchBar* : 鍵盤快速啟動應用程式的好物。不想花錢的話可以試試 QuickSilver
  • VLC 放影片
  • Pixelmator*: 影像編輯軟體,功能類似 Photoshop,但不用這麼貴… XD
  • Burn: 燒錄軟體
  • chicken of the VNC: VNC client
  • Adobe Reader
  • KKBOX*: 聽正版音樂,有出 Mac 版就甘心。
  • (2011新進榜)Sparrow*: Email 桌面軟體
  • (2011新進榜) DivvySizeUp* :
    透過快速鍵切割視窗畫面,可以很方便地安排左右視窗,適合大螢幕
  • (2011新進榜)Flux: 根據時間自動調整螢幕亮度

網路相關

  • (2010新進榜) Google Reader: 捨之前用的 NetNewsWire 主要的理由是我現在都用 iPhone 做第一次的 RSS 瀏覽(如果有興趣再加到 Instapaper),而大部分的 iPhone RSS reader 都支援跟 Google Reader 同步,例如我用 NewsRack
  • Nally: Mac 上最棒的 BBS 軟體
  • Panic Transmit*: Mac 上超棒的FTP軟體,好操作介面佳,還支援 Amazon S3。
  • Tweetie Twitter: Twitter 的桌面軟體,我會特別需要桌面版的 Twitter 軟體是因為我有多個帳號需求。
  • Adium: 整合msn/gtalk等的IM
  • Skype: 喂喂

系統管理

  • AppZapper*: 反安裝軟體,可以清比較乾淨
  • Growl 好玩的自動提示,可以吃其他應用軟體的訊息。
  • smcFanControl 手動控制風扇
  • OmniDiskSweeper: 磁碟清理工具,可以很方便地找出到底哪個檔案跟目錄是最佔空間
  • coconutBattery: 電池狀態查詢跟紀錄

程式開發

  • iTerm iTerm2: 支援多 Tab 的 Terminal,我主要拿來做 SSH 登入
  • Visor: 把 Mac 內建的 Terminal 改成透過熱鍵即可呼叫出來,我主要拿來下程式開發中的各種 script 指令
  • (2010新進榜) Homebrew: 套件管理工具,詳見這篇文章
  • Textmate * Rails Developer 都會有的一套編輯器,請進一步參考推薦安裝Plugins
  • GitX: Git GUI (有個 Fork 版有 sidebar 不錯)
  • Sequel Pro: MySQL 的 GUI
  • Navicat* : 比較專業的 MySQL GUI,覺得最方便的是支援 SSH 登入遠端操作資料庫(畢竟很少情況會開 3306 Port 可以遠端連線),另外處理編碼也很聰明,印象中很少看到出現亂碼。如果你有舊版本MySQL的編碼問題無法順利dump出來,可以試試。

網頁設計

Dashboard