<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><atom:link rel="hub" href="http://tumblr.superfeedr.com/" xmlns:atom="http://www.w3.org/2005/Atom"/><description>We are a studio focused on Ruby, Rails and Agile Development.</description><title>禾川資訊 Grass Brook</title><generator>Tumblr (3.0; @grassbrook)</generator><link>http://blog.grassbrook.com/</link><item><title>HTTP ACCEPT of mobile devices in Rails based sites</title><description>&lt;p&gt;最近在 hoptoad 上看到一些詭異的 log，似乎是網站升到 Rails 3 後才出現的：&lt;/p&gt;

&lt;pre&gt;
ActionView::MissingTemplate: Missing template posts/show with {:formats=&gt;["application/youtube-client", "*/*"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb], :locale=&gt;[:"zh-TW", :"zh-TW"]} in view paths
&lt;/pre&gt;

&lt;p&gt;沒錯，怎麼會有 format 是 “application/youtube-client”, “&lt;em&gt;/&lt;/em&gt;“，這到底是什麼鬼玩意送出來的？（狀態顯示為才疏學淺）詳細看了一下 HTTP header 資訊發現&lt;/p&gt;

&lt;pre&gt;
HTTP_ACCEPT "*/*, application/youtube-client"
HTTP_USER_AGENT "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; HD2_T8585; Windows Phone 6.5)"
HTTP_UA_OS "Windows CE (Pocket PC) - Version 5.2"
&lt;/pre&gt;

&lt;p&gt;原來是 Windows Phone 這樣的 mobile 裝置送的。&lt;/p&gt;

&lt;p&gt;但由於數量不少，於是再繼續看看是不是有其他奇怪的 log 有類似情況的：&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;居然有 PSP 的，好樣的：&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template posts/show with {:handlers=&gt;[:rjs, :builder, :rhtml, :rxml, :erb], :locale=&gt;[:"zh-TW", :"zh-TW"], :formats=&gt;["*/*;q=0.01"]} in view paths
HTTP_ACCEPT "*/*;q=0.01"
HTTP_USER_AGENT "Mozilla/4.0 (PSP (PlayStation Portable); 2.00)"
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;華為科技的產品，是分享器嗎？&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template photos/show with {:formats=&gt;["application/vnd.wap.wmlc", "application/vnd.wap.wmlscriptc", "text/vnd.wap.wml", "image/vnd.wap.wbmp", "*/*"], :locale=&gt;[:"zh-TW", :"zh-TW"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb]} in view paths
HTTP_ACCEPT "application/vnd.wap.wmlc, application/vnd.wap.wmlscriptc, text/vnd.wap.wml, image/vnd.wap.wbmp, */*"
HTTP_USER_AGENT "Mozilla/5.0 (Linux; U; Android 2.1-update1; zh-tw; MB525 Build/JRDNEM_U3_2.51.0) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17"
HTTP_VIA "(infoX WAP Gateway), HTTP/1.1, Huawei Technologies"
HTTP_X_UP_BEAR_TYPE "WCDMA"
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;微軟的行動辦公室？&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template pages/welcome with {:formats=&gt;["text/*"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb], :locale=&gt;[:"zh-TW", :"zh-TW"]} in view paths
HTTP_ACCEPT "text/*"
HTTP_USER_AGENT "Microsoft Office Mobile /14.0"
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;傳說中的魔王 IE6 / Windows XP：&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template posts/show with {:formats=&gt;["image/gif", "image/x-xbitmap", "image/jpeg", "image/pjpeg"], :locale=&gt;[:"zh-TW", :"zh-TW"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb]} in view paths
HTTP_ACCEPT "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg"
HTTP_USER_AGENT "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;完全沒有 HTTP_USER_AGENT 的資訊，似乎是 POST 動作後導向失敗造成的？&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template digiphoto_posts/show with {:formats=&gt;[:html, :text, :js, :css, :ics, :csv, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json], :locale=&gt;[:"zh-TW", :"zh-TW"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb]} in view paths
HTTP_ACCEPT "*/*"
&lt;/pre&gt;

&lt;ul&gt;&lt;li&gt;同上，有正確的 format，但是呈現不同的 HTTP_ACCEPT 樣式（多筆 log 資料彙整）：&lt;/li&gt;
&lt;/ul&gt;&lt;pre&gt;
ActionView::MissingTemplate: Missing template digiphoto_posts/show with {:formats=&gt;[:html], :locale=&gt;[:"zh-TW", :"zh-TW"], :handlers=&gt;[:rjs, :rxml, :rhtml, :builder, :erb]} in view paths
HTTP_ACCEPT "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/msword, application/vnd.ms-excel, application/x-shockwave-flash, */*"
HTTP_ACCEPT "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
HTTP_ACCEPT "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"
HTTP_ACCEPT "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, application/vnd.ms-excel, */*"
&lt;/pre&gt;

&lt;p&gt;看到這樣的現象，直覺反應是，事情絕對不是想像中的那麼簡單，必定有惡魔藏在細節裡。&lt;/p&gt;

&lt;p&gt;仔細地整理一下，上述的錯誤，所要求的頁面 format 應該都要判斷為 :html 才是正確的，而其中共通的特性是 HTTP_ACCEPT 都不外乎是下列幾種：&lt;/p&gt;

&lt;pre&gt;
"*/*", "*/*;q=0.01", "text/*", "application/youtube-client", "application/vnd.wap.wmlc"
&lt;/pre&gt;

&lt;p&gt;並且似乎也跟 mobile 裝置有關，這就讓我想到有人抱怨 HTC 手機不能順利瀏覽 T 客邦網站這件事。於是乎抓了幾天的 log 來分析，居然有驚奇的發現&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/9a396c56a73b57d5ad72"&gt;https://gist.github.com/9a396c56a73b57d5ad72&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;行動裝置的 HTTP_ACCEPT 長得都不太一樣，同樣是 Android 系統，卻沒幾個能正確送出 text/html，幾乎都是送 application/xml，太令人訝異了。&lt;/p&gt;

&lt;p&gt;那 Rails 3 到底是怎麼利用 HTTP_ACCEPT 資訊，又怎麼判斷 format 的呢？&lt;/p&gt;

&lt;p&gt;直接查原始碼可以發現 ActionDispatch::Http::MimeNegotiation 有個 formats 方法，當找不到 paramater[:format] 這個傳遞資訊時，會由 HTTP_ACCEPT 猜測一個適當的 format，最後才預設為 [Mime::HTML]。（Rails 3.0.7）&lt;/p&gt;

&lt;p&gt;而這就是問題所在，猜測的邏輯沒有包含到這些狀況，上述那些怪異的 HTTP_ACCEPT，在 Rails 3.0.7 以前（含）並不能正確的處理，於是乎輕則看到這些錯誤 log，重則直接吐 502 給你。那可不妙，我可是要支援 mobile 裝置啊～&lt;/p&gt;

&lt;p&gt;幸好，活躍的 Rails 圈有人提出了問題並獲得解答：&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://rails.lighthouseapp.com/projects/8994/tickets/6022"&gt;https://rails.lighthouseapp.com/projects/8994/tickets/6022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rails.lighthouseapp.com/projects/8994/tickets/5833"&gt;https://rails.lighthouseapp.com/projects/8994/tickets/5833&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但解法目前只有 apply 在 rails master branch，我可是要解燃眉之急呀，於是自己搞了個 patch 來修補 Rails 3.0.7：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/33c10952fe3cadaec3c4"&gt;https://gist.github.com/33c10952fe3cadaec3c4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;直接抓回來放在 config/initializers 後，重新啟動 application 就行了。&lt;/p&gt;

&lt;p&gt;這個 patch 同時解決了兩件事：&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;針對特殊的 HTTP_ACCEPT，如 “&lt;em&gt;/&lt;/em&gt;”, “&lt;em&gt;/&lt;/em&gt;;q=0.01”, “text/*” 都能正確判斷 format 是 [Mime::JS] 或 [Mime::HTML]。&lt;/li&gt;
&lt;li&gt;針對行動裝置送的 application/xxx 這類型 HTTP_ACCEPT 也能返回正確的 Mime::SET。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如此一來就能好好面對行動裝置的挑戰了。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/5385296347</link><guid>http://blog.grassbrook.com/post/5385296347</guid><pubDate>Wed, 11 May 2011 14:41:05 +0800</pubDate><dc:creator>tsechingho</dc:creator></item><item><title>Ruby 1.9.2 在 Rails 上的 Encoding 問題</title><description>&lt;p&gt;如果在 Ruby 1.9.2 開發 Rails 程式，最頭痛的問題應該就是編碼（Encoding）錯誤的問題。除了要考慮資料庫本身的編碼問題，所有的字串 IO 都會經過編碼處理，Ruby 1.9.2 提供了廣域設定與實體方法設定的方式，但該在什麼時機設定呢？&lt;/p&gt;
&lt;p&gt;基本上 Yahuda Kats 已經寫了篇蠻詳盡的說明（&lt;a href="http://yehudakatz.com/2010/05/05/ruby-1-9-encodings-a-primer-and-the-solution-for-rails/"&gt;Ruby 1.9 Encodings: A Primer and Solution for Rails&lt;/a&gt;），我這裡所要提的重點是，如果你的資料庫（如 MySQL）設定是用 UTF-8 做為儲存編碼，而 Web Server 所傳送的文字編碼是 ASCII-8BIT 的話（可以從 server log 得知），可能會遇到&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“\xE4” from ASCII-8BIT to UTF-8&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這樣的錯誤訊息，基本上可以用 Ruby 1.9.2 的廣域編碼設定來解決問題，只要在 $RAILS_ROOT/config/environment.rb 的檔案開頭加上這幾行設定即可：&lt;/p&gt;
&lt;pre&gt;if defined? Encoding&lt;br/&gt;  Encoding.default_internal = 'UTF-8'&lt;br/&gt;  Encoding.default_external = 'ASCII-8BIT'&lt;br/&gt;end
&lt;/pre&gt;
&lt;p&gt;這樣就可以正常地從 Web Form 裡面輸入中文，並以 UTF-8 字串儲存到資料庫中。如果 Web Server 並不是傳送 ASCII-8BIT 的話，那就要將 default_external 改成適當的編碼。&lt;/p&gt;
&lt;p&gt;PS. 我是在 Ruby-1.9.2-head + Rails-3.0.0.beta4 + mysql-2.8.1 gem 的環境下測試，但 Ruby-1.9.2-preview3 之後應該都是同樣的解法。&lt;/p&gt;
&lt;p&gt;PS2. Yahuda Kats 的這篇 &lt;a href="http://yehudakatz.com/2010/05/17/encodings-unabridged/"&gt;Encodings, Unabridged&lt;/a&gt; 將目前 Ruby 1.9 處理 Encoding 的來由述說的很清楚，值得仔細研究。&lt;/p&gt;
&lt;p&gt;PS3. 感謝 ihower 提醒 &lt;a href="https://rails.lighthouseapp.com/projects/8994/tickets/4336-ruby19-submitted-string-form-parameters-with-non-ascii-characters-cause-encoding-errors"&gt;rails ticket #4336&lt;/a&gt; 有另外用 before_filter 對 params 做 force_encoding 的解法，這樣就方便看 log 做 debug。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/680397440</link><guid>http://blog.grassbrook.com/post/680397440</guid><pubDate>Wed, 09 Jun 2010 23:21:00 +0800</pubDate><category>rails 3</category><category>ruby 1.9.2</category><category>encoding</category><category>mysql</category><dc:creator>tsechingho</dc:creator></item><item><title>研究 Rails 3 的 Action::Dispatch</title><description>&lt;p&gt;當開發 Rails 3 應用程式時，了解新的 call stack 設計對除錯和開發新功能的幫助會很大：&lt;/p&gt;
&lt;p&gt;&lt;img height="442" width="500" alt="call stack of Rails 2 and 3" src="http://media.tumblr.com/tumblr_kwkd3pi9Il1qz4tvk.png"/&gt;&lt;/p&gt;
&lt;p&gt;首先 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch.rb"&gt;Action::Dispatch&lt;/a&gt; 成為整個 call stack 的主角，&lt;a href="http://omgbloglol.com/post/344792822/the-path-to-rails-3-introduction"&gt;Jeremy McAnally&lt;/a&gt; 歸納出幾個特點：&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Request handling and parameter parsing&lt;/li&gt;
&lt;li&gt;Sessions, Rails’ flash, and cookie storage&lt;/li&gt;
&lt;li&gt;File uploads&lt;/li&gt;
&lt;li&gt;Routing, URL matching, and rescuing errors&lt;/li&gt;
&lt;li&gt;HTTP conditional GETs&lt;/li&gt;
&lt;li&gt;Client response and HTTP status code&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果熟悉 &lt;a href="http://blog.grassbrook.com/post/460040593/look-into-rails-3-boot-process-and-initialization-proces"&gt;Rails 3 的初始化程序&lt;/a&gt;，應該可以了解 rack server 在接收到一個 HTTP request 後，要怎麼誘發 Rails::Application 的 .call 實體方法：&lt;/p&gt;
&lt;blockquote&gt;Rails::Server.new.start&lt;br/&gt;Rack::Handler::Mongrel.run(wrapped_app)&lt;br/&gt;Mongrel::HttpServer.new.run&lt;br/&gt;Mongrel::HttpServer.new.process_client&lt;br/&gt;Rack::Handler::Mongrel.new.process&lt;br/&gt;Rack::Chunked.new.call(env)&lt;br/&gt;Rack::ContentLength.new.call(env)&lt;br/&gt;Rails::Rack::LogTailer.new.call(env)&lt;br/&gt;YourApp::Application.call(env)&lt;br/&gt;Rails::Application.call(env)&lt;br/&gt;Rails::Application.app.call(env)&lt;br/&gt;Rails::Application.middleware.build(routes).call(env) # @app.call(env)&lt;br/&gt;&lt;/blockquote&gt;
&lt;p&gt;Rails 3 為了符合 Rack 的設計理念（能回應 call 方法的物件），於是將 routes 和 middlewares 設計成串接式（cascade）型態的物件，並在執行 initializers 時實體化成 rails app stack 物件。其中有三個 initializer 跟 Action::Dispatch 最相關：&lt;/p&gt;
&lt;pre&gt;## actionpack/lib/action_dispatch/railtie.rb
initializer "action_dispatch.prepare_dispatcher" do |app|
  require 'rails/dispatcher'
  ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed }
end
## railties/lib/rails/engine.rb
initializer :add_routing_paths do |app|
  paths.config.routes.to_a.each do |route|
    app.routes_reloader.paths.unshift(route) if File.exists?(route)
  end
end
## railties/lib/rails/application/finisher.rb
initializer :build_middleware_stack do
  app
end&lt;/pre&gt;
&lt;p&gt;其中 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/railtie.rb"&gt;action_dispatch.prepare_dispatcher&lt;/a&gt; 會設定一個 ActionDispatch 的 callback 內容。而 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb"&gt;add_routing_paths&lt;/a&gt; 會用 Rails::Application.routes_reloader 方法實體化一個 Rails::Application::RoutesReloader 物件，並將所有 Rails::Engine 的 config/routes.rb 加入到 @path 陣列。當執行 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/finisher.rb"&gt;build_middleware_stack&lt;/a&gt; 時，會實體化 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb"&gt;ActionDispatch::Routing::RouteSet&lt;/a&gt; 物件，連同各式剛實體化好的 middleware 物件一起放入 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/stack.rb"&gt;ActionDispatch::MiddlewareStack&lt;/a&gt; 陣列物件中，最後轉換成串接式型態的物件：&lt;/p&gt;
&lt;pre&gt;## railties/lib/rails/application.rb
class Rails::Application
  def app
    @app ||= middleware.build(routes)
  end
  def routes
    @routes ||= ActionDispatch::Routing::RouteSet.new
  end
end
## railties/lib/rails/engine.rb
class Rails::Engine
  delegate :middleware, :paths, :root, :to =&gt; :config
end
## railties/lib/rails/application/configuration.rb
class Rails::Application::Configuration
  def middleware
    @middleware ||= default_middleware_stack
  end
  def default_middleware_stack
    ActionDispatch::MiddlewareStack.new.tap do |middleware|
      ......
      middleware.use('::ActionDispatch::Callbacks', lambda { !cache_classes })
      ......
    end
  end
end
## actionpack/lib/action_dispatch/middleware/stack.rb
class ActionDispatch::MiddlewareStack
  def build(app = nil, &amp;blk)
    app ||= blk
    raise "MiddlewareStack#build requires an app" unless app
    active.reverse.inject(app) { |a, e| e.build(a) }
  end
  def use(*args, &amp;block)
    middleware = Middleware.new(*args, &amp;block)
    push(middleware)
  end
end&lt;/pre&gt;
&lt;p&gt;仔細地看 Rails::Application.app 物件真正的樣貌，就是把一個擁有 @app 類別變數的 middleware 物件當成另一個擁有 @app 類別變數的 middleware 物件的初始化參數，一個個串接下去形成一個大型的 rack app stack 物件。這 rack app stack 物件也會被當成參數成為 rack server stack 的串接式物件，以便呼應 rack server 傳來的 call 方法：&lt;/p&gt;
&lt;blockquote&gt;#&gt; s1 = ::ActiveRecord::QueryCache.new(@routes)&lt;br/&gt;#&gt; s9 = ::ActionDispatch::Callbacks.new(s8, !cache_classes)&lt;br/&gt;#&gt; ……&lt;br/&gt;#&gt; @app = ::ActionDispatch::Static.new(s13, Rails.public_path)&lt;br/&gt;#&gt; Rails::Application.app.call(env) = @app.call(env)&lt;br/&gt;@app = #&lt;ActionDispatch::Static:0x2ff9fa4&lt;br/&gt;  @app=#&lt;Rack::Lock:0x2ffa1ac&lt;br/&gt;  @app=#&lt;Rack::Runtime:0x2ffa2b0&lt;br/&gt;  @app=#&lt;Rails::Rack::Logger:0x2ffaa1c&lt;br/&gt;  @app=#&lt;ActionDispatch::ShowExceptions:0x2ffaabc&lt;br/&gt;  @app=#&lt;ActionDispatch::Callbacks:0x2ffac60&lt;br/&gt;  @app=#&lt;ActionDispatch::Cookies:0x2ffae40&lt;br/&gt;  @app=#&lt;ActiveRecord::SessionStore:0x2ffaf1c&lt;br/&gt;  @app=#&lt;ActionDispatch::Flash:0x2ffb0ac&lt;br/&gt;  @app=#&lt;ActionDispatch::ParamsParser:0x2ffb50c&lt;br/&gt;  @app=#&lt;Rack::MethodOverride:0x2ffb64c&lt;br/&gt;  @app=#&lt;ActionDispatch::Head:0x2ffb944&lt;br/&gt;  @app=#&lt;ActiveRecord::ConnectionAdapters::ConnectionManagement:0x300e2d8 &lt;br/&gt;  @app=#&lt;ActiveRecord::QueryCache:0x300e33c&lt;br/&gt;  @app=#&lt;ActionDispatch::Routing::RouteSet:0x151c588»»»»»»»&gt;&lt;br/&gt;&lt;/blockquote&gt;
&lt;p&gt;這裡要特別注意 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/callback.rb"&gt;ActionDispatch::Callbacks&lt;/a&gt; 這個 middleware 物件，當它初始化時會執行名為 :prepare 的 callback（先前的 initializer 已設定好內容）。這個 callback 執行時會用 Rails::Application.routes_reloader 方法來實體化一個 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/routes_reloader.rb"&gt;Rails::Application::RoutesReloader&lt;/a&gt; 物件，並動用 reload! 方法。這時會得到 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb"&gt;ActionDispatch::Routing::RouteSet&lt;/a&gt; 實體物件，接著利用 clear! 方法取得一個新的 &lt;a href="http://github.com/josh/rack-mount/blob/master/lib/rack/mount/route_set.rb"&gt;Rack::Mount::RouteSet&lt;/a&gt; 實體物件 @set，依序載入所有 Rails::Engine 的 config/routes.rb，最後執行 finalizer! 方法，將 @set 凍結住避免變動：&lt;/p&gt;
&lt;pre&gt;## railties/lib/rails/application.rb
class Rails::Application
  def routes_reloader
    @routes_reloader ||= RoutesReloader.new
  end
end
## actionpack/lib/action_dispatch/middleware/callback.rb
class ActionDispatch::Callbacks
  define_callbacks :prepare, :scope =&gt; :name
  def self.to_prepare(*args, &amp;block)
    if args.first.is_a?(Symbol) &amp;&amp; block_given?
      define_method :"__#{args.first}", &amp;block
      set_callback(:prepare, :"__#{args.first}")
    else
      set_callback(:prepare, *args, &amp;block)
    end
  end
  def initialize(app, prepare_each_request = false)
    @app, @prepare_each_request = app, prepare_each_request
    run_callbacks(:prepare)
  end
end
## railties/lib/rails/application/routes_reloader.rb
class Rails::Application::RoutesReloader
  def reload!
    routes = Rails::Application.routes
    routes.disable_clear_and_finalize = true
    routes.clear!
    paths.each { |path| load(path) }
    ActiveSupport.on_load(:action_controller) { routes.finalize! }
    nil
  ensure
    routes.disable_clear_and_finalize = false
  end
end
## actionpack/lib/action_dispatch/routing/route_set.rb
class ActionDispatch::Routing::RouteSet
  def clear!
    # Clear the controller cache so we may discover new ones
    @controller_constraints = nil
    @finalized = false
    routes.clear
    named_routes.clear
    @set = ::Rack::Mount::RouteSet.new(:parameters_key =&gt; PARAMETERS_KEY)
  end
  def finalize!
    return if @finalized
    @finalized = true
    @set.add_route(NotFound)
    @set.freeze
  end
end&lt;/pre&gt;
&lt;p&gt;當 &lt;em&gt;routes_reloader&lt;/em&gt; 載入 config/routes.rb 時，會誘發 Rails::Application.routes 執行 draw 實體方法。這個 draw 方法會把 &lt;em&gt;routes&lt;/em&gt; 自己（&lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb"&gt;ActionDispatch::Routing::RouteSet&lt;/a&gt; 實體物件）當成參數實體化一個 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/mapper.rb"&gt;ActionDispatch::Routing::Mapper&lt;/a&gt; 物件 &lt;em&gt;mapper&lt;/em&gt;，並成為 &lt;em&gt;mapper&lt;/em&gt; 的 ＠set 變數，而 &lt;em&gt;mapper&lt;/em&gt; 會負責解譯 block 中定義的 Rails routes mapping DSL 內容。&lt;/p&gt;
&lt;p&gt;例如當定義一個 match(‘/’, :to =&gt; “your_home#index”) 時，首先會把傳入 match 方法的參數轉換成 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/mapper.rb"&gt;ActionDispatch::Routing::Mapper::Mapping&lt;/a&gt; 實體物件，再轉換成參數陣列 &lt;em&gt;mapping。&lt;/em&gt;接著 &lt;em&gt;mapper&lt;/em&gt; 的 ＠set 變數（&lt;em&gt;routes&lt;/em&gt;）會以 add_route 方法把 &lt;em&gt;mapping&lt;/em&gt; 參數實體化成 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route.rb"&gt;ActionDispatch::Routing::Route&lt;/a&gt; 物件 &lt;em&gt;route&lt;/em&gt;。由於 &lt;em&gt;routes&lt;/em&gt; 本身也有一個 @set 變數是個 &lt;a href="http://github.com/josh/rack-mount/blob/master/lib/rack/mount/route_set.rb"&gt;Rack::Mount::RouteSet&lt;/a&gt; 實體物件，同樣會以 add_route 方法將 &lt;em&gt;route&lt;/em&gt; 加入這個 @set 變數，以便串接 rack mount 的 router 辨識功能：&lt;/p&gt;
&lt;pre&gt;## actionpack/lib/action_dispatch/routing/route_set.rb
class ActionDispatch::Routing::RouteSet
  def draw(&amp;block)
    clear! unless @disable_clear_and_finalize
    mapper = Mapper.new(self)
    if block.arity == 1
      mapper.instance_exec(DeprecatedMapper.new(self), &amp;block)
    else
      mapper.instance_exec(&amp;block)
    end
    finalize! unless @disable_clear_and_finalize
    nil
  end
  # @set = ::Rack::Mount::RouteSet.new
  def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
    route = Route.new(app, conditions, requirements, defaults, name, anchor)
    @set.add_route(*route)
    named_routes[name] = route if name
    routes &lt;&lt; route
    route
  end
end
class ActionDispatch::Routing::Dispatcher
  def initialize(options={})
    @defaults = options[:defaults]
    @glob_param = options.delete(:glob)
  end
end
## actionpack/lib/action_dispatch/routing/mapper.rb
class ActionDispatch::Routing::Mapper
  def initialize(set)
    @set = set
  end
  # @set = ::ActionDispatch::Routing::RouteSet.new
  def match(*args)
    mapping = Mapping.new(@set, @scope, args).to_route
    @set.add_route(*mapping)
    self
  end
end
class ActionDispatch::Routing::Mapper::Mapping
  def to_route
    [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
  end
  def app
    Constraints.new(
      to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults =&gt; defaults),
      blocks
    )
  end
end&lt;/pre&gt;
&lt;p&gt;產生 &lt;em&gt;mapping&lt;/em&gt; 陣列時會實體化一個 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb"&gt;Routing::RouteSet::Dispatcher&lt;/a&gt; 物件 &lt;em&gt;app&lt;/em&gt;，負責拆解 params，執行特定 controller 的 action 內容，屬於仲介 &lt;em&gt;route&lt;/em&gt; 到 controller 的主要 app stack 物件。這時 initializer 才算完成整個 rails app stack 的準備工作。&lt;/p&gt;
&lt;p&gt;當一個 HTTP request 從 rack server stack 傳遞到 rails app stack 後，會先經過 middlewares 一層層地處理，直到 ActionDispatch::Routing::RouteSet 實體物件傳遞給 Rack::Mount::RouteSet 實體物件，才會辨識 http request 該由哪個 ActionDispatch::Routing::Route 實體物件來執行，並經由 ActionDispatch::Routing::RouteSet::Dispatcher 實體物件誘發特定 controller 的 action 內容：&lt;/p&gt;
&lt;blockquote&gt;Rails::Application.middleware.build(routes).call(env) # @app.call(env)&lt;br/&gt;ActionDispatch::Static.new.call(env) # @app.call(env)&lt;br/&gt;…… # cascade @app.call(env) of middlewares&lt;br/&gt;ActionDispatch::Routing::RouteSet.new.call(env) # @app.call(env)&lt;br/&gt;Rack::Mount::RouteSet.new.call(env) # @set.call(env)&lt;br/&gt;ActionDispatch::Routing::Route.new.call(env) # route.app.call(env)&lt;br/&gt;ActionDispatch::Routing::RouteSet::Dispatcher.new.call(env) # @app.call&lt;br/&gt;YourHomeController.action(params[:action]).call(env)&lt;br/&gt;YourHomeController.middleware_stack.build(default_stack_proc).call(env)&lt;br/&gt;#&gt; default_stack_proc = Proc.new { new.dispatch(name, klass.new(env)) }&lt;br/&gt;YourHomeController.new.dispatch(params[:action], request)&lt;br/&gt;#&gt; request = ActionDispatch::Request.new(env)&lt;br/&gt;YourHomeController.new.send(params[:action])&lt;br/&gt;YourHomeController.new.to_a&lt;br/&gt;&lt;/blockquote&gt;
&lt;p&gt;從程式碼中可以發覺 ActionDispatch::Routing::RouteSet::Dispatcher 實體物件利用 controller.action(params[:action]) 方法來取得任一個 controller 的 action dispatch stack 串接式物件（ActionDispatch::MiddlewareStack 物件），這個 stack 預設是一個 Proc 物件，能夠執行 controller 的某個 action：&lt;/p&gt;
&lt;pre&gt;## railties/lib/rails/application.rb
class Rails::Application
  def call(env)
    env["action_dispatch.parameter_filter"] = config.filter_parameters
    app.call(env)
  end
end
## actionpack/lib/action_dispatch/routing/route_set.rb
class ActionDispatch::Routing::RouteSet
  def call(env)
    finalize!
    @set.call(env)
  end
end
## rack-mount/lib/rack/mount/recognition/route_set.rb
class Rack::Mount::RouteSet
  def call(env)
    raise 'route set not finalized' unless @recognition_graph
    env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
    request = nil
    req = @request_class.new(env)
    recognize(req) do |route, matches, params|
      # TODO: We only want to unescape params from uri related methods
      params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
      if route.prefix?
        env[Prefix::KEY] = matches[:path_info].to_s
      end
      env[@parameters_key] = params
      result = route.app.call(env)
      return result unless result[1][X_CASCADE] == PASS
    end
    request || [404, {'Content-Type' =&gt; 'text/html', 'X-Cascade' =&gt; 'pass'}, ['Not Found']]
  end
end
## actionpack/lib/action_dispatch/routing/route_set.rb
class ActionDispatch::Routing::RouteSet::Dispatcher
  def call(env)
    params = env[PARAMETERS_KEY]
    prepare_params!(params)
    # Just raise undefined constant errors if a controller was specified as default.
    unless controller = controller(params, @defaults.key?(:controller))
      return [404, {'X-Cascade' =&gt; 'pass'}, []]
    end
    controller.action(params[:action]).call(env)
  end
end
## actionpack/action_controller/metal.rb
class ActionController::Metal
  class_attribute :middleware_stack
  self.middleware_stack = ActionDispatch::MiddlewareStack.new
  def self.inherited(base)
    self.middleware_stack = base.middleware_stack.dup
    super
  end
  def self.action(name, klass = ActionDispatch::Request)
    middleware_stack.build do |env|
      new.dispatch(name, klass.new(env))
    end
  end
  def dispatch(name, request)
    @_request = request
    @_env = request.env
    @_env['action_controller.instance'] = self
    process(name)
    to_a
  end
  def to_a
    response ? response.to_a : [status, headers, response_body]
  end
end
## actionpack/abstract_controller/base.rb
class AbstractController::Base
  def process(action, *args)
    @_action_name = action_name = action.to_s
    unless action_name = method_for_action(action_name)
      raise ActionNotFound, "The action '#{action}' could not be found"
    end
    @_response_body = nil
    process_action(action_name, *args)
  end
  def process_action(method_name, *args)
    send_action(method_name, *args)
  end
  alias send_action send
end&lt;/pre&gt;
&lt;p&gt;由於 controller 的 action 不會再傳遞 HTTP request 到其他物件，而是直接回傳標準的 rack response 陣列 [status, headers, response_body]，成了所謂的 rack endpoint。最後再由 rack server 把 HTTP response 送回到使用者個瀏覽器中解讀。&lt;/p&gt;
&lt;p&gt;以上就是 Action::Dispatch 整個 call stack 大略的樣貌。如果想要在這中間做些處理，則是安插個 middleware 進去，如 &lt;span&gt;José Valim 就寫了個&lt;span&gt; &lt;a href="http://github.com/plataformatec/devise"&gt;devise&lt;/a&gt; 提供 rack 風格的 authentication 功能。&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/501857432</link><guid>http://blog.grassbrook.com/post/501857432</guid><pubDate>Wed, 07 Apr 2010 08:18:41 +0800</pubDate><category>rails 3</category><category>action dispatch</category><category>call stack</category><category>middleware stack</category><category>dispatch stack</category><dc:creator>tsechingho</dc:creator></item><item><title>研究 Rails 3 的 boot process 和 initialization process</title><description>&lt;p&gt;徹底了解 &lt;a href="http://ryanbigg.com/guides/initialization.html"&gt;Rails 3 的 boot/initialization 流程&lt;/a&gt;對於控制 Rails 應用程式是很重要的一個課題，當我們下了 rails server 命令，究竟發生了什麼事，&lt;a href="http://github.com/carlhuda/bundler"&gt;Bundler&lt;/a&gt; 和 &lt;a href="http://github.com/rack/rack"&gt;Rack&lt;/a&gt; 又扮演了什麼角色，讓我們來瞧個仔細。&lt;/p&gt;
&lt;p&gt;說明：環境變數 GEM_PATH 是 rubygems 的安裝路徑，ROOT_PATH 是應用程式根目錄，而本篇文章使用 RAILTIES_PATH 代表 railties 這個 gem 的根目錄，依此類推。引述段落則是依序指出重點檔案或指令。&lt;/p&gt;
&lt;p&gt;在 Rails 3 應用程式根目錄下，執行 rails server 指令，第一步便是透過 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/generators/rails/app/templates/config/boot.rb"&gt;boot.rb&lt;/a&gt; 誘發 bundler 將所有相關 rubygems 的 lib 都加到 $LOAD_PATH：&lt;/p&gt;
&lt;blockquote&gt;GEM_PATH/bin/rails server&lt;br/&gt;RAILTIES_PATH/bin/rails&lt;br/&gt;ROOT_PATH/script/rails&lt;br/&gt;#&gt; also define ENV_PATH, BOOT_PATH, APP_PATH&lt;br/&gt;ROOT_PATH/config/boot.rb&lt;br/&gt;#&gt; add required gems’s lib into $LOAD_PATH&lt;/blockquote&gt;
&lt;p&gt;bundler 預設用 &lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler/definition.rb"&gt;Definition.from_gemfile&lt;/a&gt; 解讀 ROOT_PATH/Gemfile，再以 &lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler.rb"&gt;Bundler.setup&lt;/a&gt; 方法呼叫 &lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler/runtime.rb"&gt;Bundle::Runtime&lt;/a&gt; 設定 rubygems；或是先執行 bundle lock 產生 ROOT_PATH/.bundle/environment 檔案（由 &lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler/definition.rb"&gt;Definition.from_lock&lt;/a&gt; 解讀 ROOT_PATH/Gemfile.lock，再由 &lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler/runtime.rb"&gt;Bundle::Runtime&lt;/a&gt; 生成），當被 require 時會執行&lt;a href="http://github.com/carlhuda/bundler/blob/master/lib/bundler/templates/environment.erb"&gt;新的 Bundler.setup&lt;/a&gt; 方法設定 rubygems。&lt;/p&gt;
&lt;p&gt;而 ROOT_PATH/script/rails 最後所 require 的 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/commands.rb"&gt;commands.rb&lt;/a&gt; 便會依序載入 rails.rb，設定基礎 Rails 環境，加載 rails/application.rb 及各式 Railtie，掛載透過 bundler 設定的 rubygems，啟動 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/commands/server.rb"&gt;Rails::Server&lt;/a&gt; 通知 &lt;a href="http://github.com/rack/rack/blob/master/lib/rack/server.rb"&gt;Rack::Server&lt;/a&gt; 跑 Rails::Application：&lt;/p&gt;
&lt;blockquote&gt;RAILTIES_PATH/lib/rails/commands.rb&lt;br/&gt;RAILTIES_PATH/lib/rails/commands/server.rb&lt;br/&gt;rails_server = Rails::Server.new  #=&gt; also set ENV[“RAILS_ENV”]&lt;br/&gt;ROOT_PATH/config/application.rb&lt;br/&gt;RAILTIES_PATH/lib/rails/all.rb&lt;br/&gt;RAILTIES_PATH/lib/rails.rb&lt;br/&gt;ACTIVESUPPORT_PATH/lib/active_support.rb&lt;br/&gt;……&lt;br/&gt;RAILTIES_PATH/lib/rails/application.rb&lt;br/&gt;……&lt;br/&gt;Bundler.require :default, Rails.env&lt;br/&gt;……&lt;br/&gt;rails_server.start&lt;/blockquote&gt;
&lt;p&gt;這時 Rack::Server.start 會呼叫 .app 實體方法，先讓 &lt;a href="http://github.com/rack/rack/blob/master/lib/rack/builder.rb"&gt;Rack::Builder&lt;/a&gt; 以 #parse_file 類別方法解讀 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/generators/rails/app/templates/config.ru"&gt;config.ru&lt;/a&gt;，產生 Rakc::Builder 實體物件。此 Rack::Builder 物件會&lt;strong&gt;載入 environment.rb 來初始化 YourApp::Application&lt;/strong&gt;，用 .run 實體方法把 YourApp::Application 這個&lt;a href="http://en.oreilly.com/railswinter10/public/schedule/detail/13304"&gt;龐大的 Rack application&lt;/a&gt;（&lt;strong&gt;&lt;a href="http://rack.rubyforge.org/doc/SPEC.html"&gt;可回應 call 方法傳入 env 參數並回傳 [status, header, body] 的 Ruby 實體物件/Proc 物件&lt;/a&gt;&lt;/strong&gt;）也加入 Rack::Builder@ins 陣列，並回傳成 Rack::Server@app 陣列。最後 Rack::Server 透過 .wrapped_app 實體方法把 Rails::Server 的 middlewares 包裹成 @app 陣列，並實體化為 rack application，一起丟給 &lt;a href="http://github.com/rack/rack/blob/master/lib/rack/handler.rb"&gt;Rack::Handler&lt;/a&gt; 處理：&lt;/p&gt;
&lt;blockquote&gt;rails_server.start&lt;br/&gt;……&lt;br/&gt;wrapped_app = rails_server.build_app(@app)&lt;br/&gt;#&gt; @app = Rack::Builder.parse_file(‘config.ru’) = YourApp::Application&lt;br/&gt;##&gt; ROOT_PATH/config.ru&lt;br/&gt;##&gt; ROOT_PATH/config/environment.rb&lt;br/&gt;##&gt; YourApp::Application.initialize!&lt;br/&gt;##&gt; run YourApp::Application&lt;br/&gt;#&gt; rails_server.middleware[‘development’]&lt;br/&gt;rack_app = wrapped_app = &lt;em&gt;#&lt;Rails::Rack::LogTailer:0x2f92228 @app=YourApp::Application, @cursor=612423, @file=#&lt;File:log/development.log&gt;, @last_checked=1270047942.86726&gt;&lt;/em&gt;&lt;br/&gt;……&lt;br/&gt;rails_server.server.run(wrapped_app)&lt;br/&gt;#&gt; Rack::Server.new.server.run(wrapped_app)&lt;br/&gt;#&gt; Rack::Handler.default(options).run(wrapped_app)&lt;br/&gt;rails_server = &lt;em&gt;#&lt;Rails::Server:0x2dafa14 @app=YourApp::Application, @_server=Rack::Handler::Mongrel, @options={:server=&gt;nil, :AccessLog=&gt;[], :pid=&gt;”tmp/pids/server.pid”, :Host=&gt;”0.0.0.0”, :debugger=&gt;false, :daemonize=&gt;false, :environment=&gt;”development”, :config=&gt;”config.ru”, :Port=&gt;3000}&gt;&lt;/em&gt;&lt;br/&gt;……&lt;br/&gt;Rack::Handler::Mongrel.run(rack_app)&lt;br/&gt;#&gt; http_server = Mongrel::HttpServer.new&lt;br/&gt;#&gt; handler = Rack::Handler::Mongrel.new(rack_app)&lt;br/&gt;#&gt; handler = Rack::Chunked.new(Rack::ContentLength.new(rack_app))&lt;br/&gt;#&gt; http_server.register(‘/’, handler)&lt;br/&gt;……&lt;br/&gt;Rack::Handler::Mongrel.process&lt;/blockquote&gt;
&lt;p&gt;如果跑 mongrel server 則 Rack::Handler.default 會以 &lt;a href="http://github.com/rack/rack/blob/master/lib/rack/handler/mongrel.rb"&gt;Rack::Handler::Mongrel.run&lt;/a&gt; 方法跑一個 &lt;a href="http://github.com/mongrel/mongrel/blob/master/lib/mongrel.rb"&gt;Mongrel::HttpServer&lt;/a&gt; 實體物件，並把每個 rack application（獨立的 Rack::Handler::Mongrel 實體物件）都註冊起來。&lt;/p&gt;
&lt;p&gt;當 mongrel server 收到 http request 後，會送給 Rack::Handler::Mongrel 的 .process 實體方法，呼叫 rack application 的 .call 實體方法送出 http response。因為 Rails 3 每個 controller action、metal 都被設計成 rack application，所以創造出無限可能。&lt;/p&gt;
&lt;p&gt;另外要提的是 Phusion Passenger 2.2.9 之後也採用類似的方法跑 Rails 3 application（主要是 require 的順序不一樣）：&lt;/p&gt;
&lt;blockquote&gt;PhusionPassenger::Railz::ApplicationSpawner#preload_application&lt;br/&gt;ROOT_PATH/config/environment.rb&lt;br/&gt;ROOT_PATH/config/application.rb&lt;br/&gt;ROOT_PATH/config/boot.rb&lt;br/&gt;….&lt;br/&gt;YourApp::Application.initialize!&lt;br/&gt;run YourApp::Application&lt;/blockquote&gt;
&lt;p&gt;以上是 rails boot process 的概要，接著來看 rails initialization process 的部份。當 Rack Server 在解讀 config.ru 時，會產生一個這樣的 rack application 物件：&lt;/p&gt;
&lt;pre&gt;rack_app = Rack::Builder.new {
  require ::File.expand_path('../config/environment',  __FILE__)
  run YourApp::Application
}.to_app&lt;/pre&gt;
&lt;p&gt;而 Rails::Application 初始化等於是展開 config.ru 這個 block 內容，最終會產生一個描述完整的 Rack::Builder 實體物件：&lt;/p&gt;
&lt;pre&gt;rack_app = Rack::Builder.new {
  use ActionDispatch::Static
  use Rack::Lock
  use Rack::Runtime
  use Rails::Rack::Logger
  use ActionDispatch::ShowExceptions
  use ActionDispatch::RemoteIp
  use Rack::Sendfile
  use ActionDispatch::Callbacks
  use ActionDispatch::Cookies
  use ActionDispatch::Session::CookieStore
  use ActionDispatch::Flash
  use ActionDispatch::ParamsParser
  use Rack::MethodOverride
  use ActionDispatch::Head
  use ActiveRecord::ConnectionAdapters::ConnectionManagement
  use ActiveRecord::QueryCache
  run YourApp::Application.routes
}.to_app&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;一切都從類別宣告開始。&lt;/strong&gt;從 ROOT_PATH/config/environment.rb 把 ROOT_PATH/config/application.rb 給 require 後的宣告開始：&lt;/p&gt;
&lt;pre&gt;YourApp::Application &lt; Rails::Application &lt; Rails::Engine &lt; Rails::Railtie&lt;/pre&gt;
&lt;p&gt;這 YourApp::Application 的繼承宣告主要做了幾件事，定義 called_from 變數，引入 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/configurable.rb"&gt;Rails::Application::Configurable&lt;/a&gt; 模組&lt;span&gt;&lt;span&gt;，定義 #config 類別方法用來建立 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/configuration.rb"&gt;Rails::Application::Configuration&lt;/a&gt; 實體物件，排 subclasses 順序，把自己定義成 Singleton，最後把自己實體化：&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;## snippet of Rails::Application
def inherited(base)
  raise "You cannot have more than one Rails::Application" if Rails.application
  super
  Rails.application = base.instance
end

## snippet of Rails::Engine
def inherited(base)
  unless base.abstract_railtie?
    base.called_from = begin
      # Remove the line number from backtraces making sure we don't leave anything behind
      call_stack = caller.map { |p| p.split(':')[0..-2].join(':') }
      File.dirname(call_stack.detect { |p| p !~ %r[railties[\w\-\.]*/lib/rails|rack[\w\-\.]*/lib/rack] })
    end
  end
  super
end

## snippet of Rails::Railtie
def inherited(base)
  unless base.abstract_railtie?
    base.send(:include, self::Configurable)
    subclasses &lt;&lt; base
  end
end&lt;/pre&gt;
&lt;p&gt;每種 Railtie（Application, Engine, Plugin 都是一種 &lt;a href="http://blog.grassbrook.com/post/449706333/look-into-railtie"&gt;Railtie 型態&lt;/a&gt;）的 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/railtie/configurable.rb"&gt;#config 類別方法&lt;/a&gt;都是在管理 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/railtie/configuration.rb"&gt;Rails::Railtie::Configuration&lt;/a&gt;（子）物件。如呼叫 Rails::Application.config 時，會以 called_from 路徑中第一個有 config.ru 的路徑做為 root 參數建立 Rails::Application::Configuration 實體物件。&lt;/p&gt;
&lt;pre&gt;## snippet of Rails::Application::Configurable
def config
  @config ||= Application::Configuration.new(self.class.find_root_with_flag("config.ru", Dir.pwd))
end

## snippet of Rails::Application::Configuration
def initialize(*)
  super
  @allow_concurrency   = false
  @filter_parameters   = []
  @dependency_loading  = true
  @serve_static_assets = true
  @time_zone           = "UTC"
  @consider_all_requests_local = true
  @session_store = :cookie_store
  @session_options = {}
end

## snippet of Rails::Engine::Configuration
def initialize(root=nil)
  super
  @root = root
end

## snippet of Rails::Railtie::Configuration
def initialize
  @@options ||= {}
end&lt;/pre&gt;
&lt;p&gt;基本上各 Railtie 利用 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/initializable.rb"&gt;#initializer&lt;/a&gt; 類別方法定義了許多初始化時要執行的 initializer block，當 YourApp::Application.initialize! 執行時，會透過 #method_missing 類別方法得到 YourApp::Application 實體物件，透過 railties.all 實體方法取得所有 Railtie 實體物件，接著取得各 initializers 實體物件，再依序執行這些 initializer block，完成整個應用程式初始化動作：&lt;/p&gt;
&lt;pre&gt;## snippet of Rails::Application
def self.method_missing(*args, &amp;block)
  instance.send(*args, &amp;block)
end

def initialize!
  run_initializers(self)
  self
end

def initializers
  initializers = Bootstrap.initializers_for(self)
  railties.all { |r| initializers += r.initializers }
  initializers += super
  initializers += Finisher.initializers_for(self)
  initializers
end

## Rails::Initializable
def run_initializers(*args)
  return if instance_variable_defined?(:@ran)
  initializers.each do |initializer|
    initializer.run(*args)
  end
  @ran = true
end

def self.initializer(name, opts = {}, &amp;blk)
  raise ArgumentError, "A block must be passed when defining an initializer" unless blk
  opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
  initializers &lt;&lt; Initializer.new(name, nil, opts, &amp;blk)
end&lt;/pre&gt;
&lt;p&gt;而 initializers 實體物件群組執行順序是 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/bootstrap.rb"&gt;Rails::Application::Bootstrap&lt;/a&gt; -&gt; Rails::Railtie.subclasses -&gt; Rails::Engine.subclasses -&gt; &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/plugin.rb"&gt;Rails::Plugin.all&lt;/a&gt; -&gt; &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb"&gt;Rails::Engine&lt;/a&gt; -&gt; &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application/finisher.rb"&gt;Rails::Application::Finisher&lt;/a&gt;。雖然 initializer 執行順序預設是依照宣告順序，也可用 :before, :after 參數排序，但 Railtie、Engine、Plugin 各群組內依名稱字母排序的特性，實際撰寫 Engine 或 Plugin 時還是要多留心一下：&lt;/p&gt;
&lt;blockquote&gt;Rails::Application::Bootstrap&lt;br/&gt;ActiveSupport::Railtie&lt;br/&gt;I18n::Railtie&lt;br/&gt;ActionDispatch::Railtie&lt;br/&gt;ActionView::Railtie&lt;br/&gt;ActionController::Railtie&lt;br/&gt;ActiveRecord::Railtie&lt;br/&gt;ActionMailer::Railtie&lt;br/&gt;ActiveResource::Railtie&lt;br/&gt;Rails::TestUnitRailtie&lt;br/&gt;&lt;em&gt;AllKindsOfEnginesOrderedByAlphabet&lt;/em&gt; (Rails::Engine)&lt;br/&gt;&lt;em&gt;AllKindsOfPluginsOrderedByAlphabet&lt;/em&gt; (Rails::Plugin)&lt;br/&gt;&lt;em&gt;YourApp&lt;/em&gt;::Application (Rails::Engine)&lt;br/&gt;Rails::Application::Finisher&lt;/blockquote&gt;
&lt;p&gt;其中 Rails::Engine 有個名為 :add_routing_paths 的 initializer 會處理各 engine 的 routes.rb 載入；另 Rails::Application::Finisher 中有個名為 :build_middleware_stack 的 initializer 會呼叫 Rails::Application 的 .app 實體方法將所有 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/railtie/configuration.rb"&gt;default_middleware&lt;/a&gt;、Railtie 的 middlewares 或自定的 middlewares 都丟到一個 &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/stack.rb"&gt;ActionDispatch::MiddlewareStack&lt;/a&gt; 陣列中，並建立這些 middlewares 實體物件，這都是值得留意的基本 initializer。當所有 initializers 都執行完畢，一個 Rails 3 的 rack application（@app）便建立完成，達到初始化的最終目的：&lt;/p&gt;
&lt;pre&gt;## Rails::Application
def app
  @app ||= middleware.build(routes)
end

def call(env)
  env["action_dispatch.parameter_filter"] = config.filter_parameters
  app.call(env)
end&lt;/pre&gt;
&lt;p&gt;接著就是搬椅子等待 Rack server 呼叫 Rails::Application 的 .call 實體方法囉。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;更新：內文已更新為 rails-3.0.0.beta2 的版本為主。&lt;/em&gt;&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/460040593</link><guid>http://blog.grassbrook.com/post/460040593</guid><pubDate>Sat, 20 Mar 2010 10:40:00 +0800</pubDate><category>bundler</category><category>rack</category><category>rails 3</category><category>rails boot process</category><category>rails initialization process</category><dc:creator>tsechingho</dc:creator></item><item><title>研究 Rails 3 的 Rails::Engine 和 Rails::Railtie</title><description>&lt;p&gt;當我們在撰寫 Rails 3 的 Plugin/Engine 時，會被要求養成&lt;a href="http://weblog.rubyonrails.org/2010/2/9/plugin-authors-toward-a-better-future"&gt;好的習慣&lt;/a&gt;，如你要覆寫 ActiveRecord 等底層模組/類別時，請先 require 它和你的覆寫模組（注意順序），再把覆寫模組給 include 進去：&lt;/p&gt;
&lt;pre&gt;## in lib/my_gem.rb
require "active_record"
require "my_gem/active_record_sti_fix" 
ActiveRecord::Base.class_eval { include MyGem::ActiveRecordStiFix }&lt;/pre&gt;
&lt;p&gt;對於常寫 Rails plugin 的人，會想直接更動 Rails 3 原本的 Life Cycle 來滿足需求，這時只要引入一個 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/railtie.rb"&gt;Rails::Railtie&lt;/a&gt; 子類別便可變更啟動程序，這是因為 &lt;a href="http://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/railtie.rb"&gt;ActionMailer&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_controller/railtie.rb"&gt;ActionController&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/railtie.rb"&gt;ActionDispatch&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/actionpack/lib/action_view/railtie.rb"&gt;ActionView&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/activemodel/lib/active_model/railtie.rb"&gt;ActiveModel&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/activerecord/lib/active_record/railtie.rb"&gt;ActiveRecord&lt;/a&gt;, &lt;a href="http://github.com/rails/rails/blob/master/activeresource/lib/active_resource/railtie.rb"&gt;ActiveResource&lt;/a&gt; 和 &lt;a href="http://github.com/rails/rails/blob/master/activesupport/lib/active_support/railtie.rb"&gt;ActiveSupport&lt;/a&gt; 等都擁有一個繼承 Rails::Railtie 的類別，Rails 3 啟動程序主要工作就是正確誘發這些 railtie（請注意 require 的順序）：&lt;/p&gt;
&lt;pre&gt;## in lib/my_gem.rb
module MyGem
  # Make sure your Gem loads the railtie.rb file if Rails is loaded first.
  require 'my_gem/railtie' if defined?(Rails::Railtie)
end

## in lib/my_gem/railtie.rb
require 'my_gem'
require 'rails'

module MyGem
  class Railtie &lt; ::Rails::Railtie
    # This creates a config.my_gem in the user's Application&lt;br/&gt;    config.my_gem = MyGem

    # configuration shared by all railties and the application
    config.generators.test_framework :rspec
    config.middlewares.use MyGem::MyMiddleware
    config.to_prepare do
      MyGem.config_setup!
    end

    # rails 3 notification system
    require "my_gem/railties/log_subscriber"
    log_subscriber MyGem::Railties::LogSubscriber.new

    rake_tasks do
      load "my_gem/setup.rake"
    end

    generators do
      require "../../generators/another_generator.rb"
    end

    initializer :setup_my_gem, :before =&gt; :load_environment_config do |app|
      app.config.locale = 'zh-TW'
      app.config.time_zone = 'taipei'
    end
  end
end&lt;/pre&gt;
&lt;p&gt;關於以上程式內容，設定 config.my_gem 作為這個 rubygem 特有的設定值&lt;span&gt;&lt;span&gt;辨識名，config.* &lt;span&gt;&lt;span&gt;可以設定所有 railtie 都讀得到的設定值，可定義 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/log_subscriber.rb"&gt;Rails::LogSubscriber&lt;/a&gt; 來處理 log 記錄，可用 rake_tasks 方法載入 rake tasks，用 generators 方法指定 generators 的位置，以及用 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/initializable.rb"&gt;initializer&lt;/a&gt; 方法定義一些常量、變數等等（可以不需要在 config/initializers 下建設定檔），最後這些方法都會在執行 YourApp::Application &lt; &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application.rb"&gt;Rails::Application&lt;/a&gt; 時被誘發。&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;而平常寫的 Rails 3 Engine（小型嵌入式 rails 應用程式，會自動載入 app, config, lib 等目錄）分為兩種。一種是 Engines in gems，需要額外載入 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb"&gt;Rails::Engine&lt;/a&gt; 類別（在 Rails.root/config/environment.rb 一開始就 require 進來或在 Gemfile 定義 gem ‘my_engine’），才會把這個 rubygem 啟動成為 rails engine，這是因為 Rails::Engine 本身也是 Rails::Railtie 的子類別（多了預設的 initializers 和特定的 methods），因此擁有類似的載入和設定方式：&lt;/p&gt;
&lt;pre&gt;## lib/my_engine.rb
module MyEngine
  # Make sure your Gem loads the railtie.rb file if Rails is loaded first.
  require 'my_engine/engine' if defined?(Rails::Railtie)
end

## lib/my_engine/engine.rb
require 'my_engine'
require 'rails'

module MyEngine
  class Engine &lt; ::Rails::Engine
    config.my_engine = MyEngine

    # Add a load path for this specific Engine
    config.load_paths &lt;&lt; File.expand_path("../../a_path", __FILE__)
    config.eager_load_paths &lt;&lt; File.expand_path("../../b_path", __FILE__)
    config.load_once_paths &lt;&lt; File.expand_path("../../c_path", __FILE__)

    # Add/specify the load path for this specific Engine
    paths.app.controllers &lt;&lt; "lib/controllers"
    paths.app.metals = "lib/metal"
  end
end&lt;/pre&gt;
&lt;p&gt;另一種是 Engines in plugins，不需額外載入 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/plugin.rb"&gt;Rails::Plugin&lt;/a&gt; 類別（這是 Rails::Engine 的子類別），必須依循預設規則，只要把 engine 程式放在 vender/plugins 下，Rails 3 就自動幫你處裡好，如同以往也會自動載入 PLUGIN_ROOT/init.rb，唯一要注意的是 Rails::Plugin 是最終類別，且同一個 engine 中不能同時載入另一個 Rails::Engine。&lt;/p&gt;
&lt;p&gt;最後來看 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/application.rb"&gt;Rails::Application&lt;/a&gt;，它本身就是個巨大的 Rails::Engine，以 Singleton 方式存在，主要工作是協調各 Railtie 的啟動程序，如 initialization, configuration, routes, middleware 和 metal（定義在 &lt;a href="http://github.com/rails/rails/tree/master/railties/lib/rails/application/"&gt;Rails::Application 目錄&lt;/a&gt;下 ），讓彼此和睦相處。&lt;/p&gt;
&lt;p&gt;註：以上主要編修自 &lt;a href="http://github.com/josevalim"&gt;josevalim&lt;/a&gt; 的 &lt;a href="https://gist.github.com/af7e572c2dc973add221"&gt;gist 文件&lt;/a&gt;，並以 rails-3.0.0.beta2 這個版本為主，詳細的 method 用法請參考 &lt;a href="http://github.com/rails/rails/tree"&gt;Rails 原始碼&lt;/a&gt;。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/449706333</link><guid>http://blog.grassbrook.com/post/449706333</guid><pubDate>Mon, 15 Mar 2010 18:18:00 +0800</pubDate><category>application</category><category>engine</category><category>plugin</category><category>railtie</category><category>rails 3</category><dc:creator>tsechingho</dc:creator></item><item><title>實用的 Rails Module 方法</title><description>&lt;p&gt;研究 Rails 3 的時候看到 &lt;a href="http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/"&gt;The Rails Module (in Rails 3)&lt;/a&gt;&lt;a&gt; &lt;/a&gt;這篇文章覺得很有趣，於是利用 &lt;a href="http://github.com/oggy/looksee"&gt;looksee&lt;/a&gt; 把所有的 Rails module public method 都翻出來：&lt;/p&gt;
&lt;pre&gt;[Rails]
  application        cache          initialize!   logger       public_path=
  application=       configuration  initialized=  logger=      root        
  backtrace_cleaner  env            initialized?  public_path  version&lt;/pre&gt;
&lt;p&gt;雖然大部分在 Rails 2.3.5 都已經支援，也不難查到，但還是得看過 source code，了解的夠深入，才能運用的恰到好處：&lt;/p&gt;
&lt;pre&gt;Rails.root.join("config", "database.yml")&lt;br/&gt;Rails.env.development?&lt;br/&gt;Rails.application.routes.recognize_path("rails/info/properties")&lt;/pre&gt;
&lt;p&gt;純粹做個記錄。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/441544390</link><guid>http://blog.grassbrook.com/post/441544390</guid><pubDate>Fri, 12 Mar 2010 02:09:00 +0800</pubDate><category>module</category><category>rails 3</category><dc:creator>tsechingho</dc:creator></item><item><title>jquery_corpus 幫你的 rails 3 application 自動安裝 jquery plugins</title><description>&lt;p&gt;Rails 3 提供開發人員可改用 jquery-ujs，但並沒有將方法整合的很完整、方便，只好自己利用 generator 來管理 jquery 的 plugin，整理一下就變成 &lt;a href="http://github.com/tsechingho/jquery_corpus"&gt;jquery_corpus&lt;/a&gt; 了。使用例子如下：&lt;/p&gt;
&lt;pre&gt;rails generate jquery:ui 1.8rc3
rails generate jquery:colorbox&lt;/pre&gt;
&lt;p&gt;由於機制上是透過 generator 來處理 jquery plugin 的 fetch、unzip、file copy、file create、file delete 等工作，因此使用者可以看到每個步驟的 log，並決定版本衝突時是否更動，增加了使用上的彈性。&lt;/p&gt;
&lt;p&gt;因為一個 generator 對應一個 jquery plugin，必要時開發者只需要寫個 generator 和 spec 便可將新的 jquery plugin 納入管理，而目前 generator 只寫了上面兩個，有待日後的努力慢慢補足。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/438781839</link><guid>http://blog.grassbrook.com/post/438781839</guid><pubDate>Wed, 10 Mar 2010 18:41:00 +0800</pubDate><category>generator</category><category>jquery</category><category>jquery_corpus</category><category>rails 3</category><dc:creator>tsechingho</dc:creator></item><item><title>撰寫與發表 rubygem -- 以 Rails 3/Thor 的 generator 為例</title><description>&lt;p&gt;開發 Rails 應用程式時，常會使用 generate scaffold 的指令來建立基本的 MVC 架構，安裝 rubygem 時也會有 generate spec 這類的指令要執行，這些都是利用 generator 來達到設定上的自動化，Rails 3 改寫了這部份的實作方法，有必要重新熟悉一下。&lt;/p&gt;
&lt;p&gt;基本上 Rails 3 的 generator 是繼承自 &lt;a href="http://rdoc.info/projects/wycats/thor"&gt;thor&lt;/a&gt; 的 &lt;a href="http://rdoc.info/rdoc/wycats/thor/blob/4f5da62d135aa10122e8c51f0c0dfa25fbfd293f/Thor/Group.html"&gt;Thor::Group&lt;/a&gt; 物件，並整合了 Rails 2.3 原本 generator 和 template 的方法成為 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/generators/actions.rb"&gt;Rails::Generators::Actions&lt;/a&gt;。實作方面，我們只需要在 lib/generators 下建立一個 _generator.rb 檔，宣告一個繼承 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/generators/base.rb"&gt;Rails::Generators::Base&lt;/a&gt;（或 &lt;a href="http://github.com/rails/rails/blob/master/railties/lib/rails/generators/named_base.rb"&gt;Rails::Generators::NamedBase&lt;/a&gt;、Thor::Group）的 Class，並定義要執行的 tasks (public instance method) 就完成了。&lt;/p&gt;
&lt;p&gt;使用 &lt;a href="http://github.com/carlhuda/bundler"&gt;bundler&lt;/a&gt; 管理 Rails 應用程式所需的 rubygem 非常好用，而用 &lt;a href="http://github.com/technicalpickles/jeweler"&gt;jeweler&lt;/a&gt; 產生 rubygem 也十分簡單，以下便介紹如何撰寫一個 generator 來抓取 &lt;a href="http://github.com/rails/jquery-ujs"&gt;jquery-ujs&lt;/a&gt; 及其使用方法，並說明打包 rubygem 的流程。&lt;/p&gt;
&lt;p&gt;首先安裝 jeweler 並建立一個新的 rubygem，命名為 generator_demo，採用 &lt;a href="http://github.com/rspec/rspec"&gt;rspec&lt;/a&gt; 來做為 test framework，預計打包到 rubygems.org：&lt;/p&gt;
&lt;blockquote&gt;gem install jeweler&lt;br/&gt;jeweler —rspec —gemcutter generator_demo&lt;br/&gt;cd generator_demo&lt;br/&gt;git log&lt;br/&gt;&lt;/blockquote&gt;
&lt;p&gt;jeweler 會用它的 generator 幫你建立 generator_demo 這個目錄及相關檔案，並自動使用 git 做版本控管。接著我們需要一個名為 jquery_ujs_generator.rb 的檔案。Rails 3 本身提供建立 generator template 的 generate 指令，使用 rails generate generator jquery_ujs 會自動在 Rails 3 應用程式的 lib 下建立 generator 所需的檔案。但這裡我們必須手動建立：&lt;/p&gt;
&lt;blockquote&gt;mkdir -p lib/generators/jquery_ujs&lt;br/&gt;mkdir -p lib/generators/jquery_ujs/templates&lt;br/&gt;touch lib/generators/jquery_ujs/jquery_ujs_generator.rb&lt;br/&gt;touch lib/generators/jquery_ujs/USAGE&lt;/blockquote&gt;
&lt;p&gt;編輯 jquery_ujs_generator.rb 填入以下內容：&lt;/p&gt;
&lt;pre&gt;require 'rails/generators/base'

class JqueryUjsGenerator &lt; Rails::Generators::Base
  def self.source_root
    @source_root ||= File.expand_path('../templates', __FILE__)
  end

  def fetch_rails_jquery_ujs_file
    url = 'http://github.com/rails/jquery-ujs/raw/master/src/rails.js'
    get url, 'public/javascripts/rails.js'
  end
end
&lt;/pre&gt;
&lt;p&gt;這裡我們定義一個 JqeuryUjsGenerator 類別，每個 instance method 將會依序直接被 invoke，而 method 的命名建議使用直述式的（類似 rpsec 的 it 敘述的命名）。其中 fetch_rails_jquery_ujs_file 使用了 thor 的 get 方法取得遠端的檔案，並存放於 public/javascript 底下。而 source_root 是定義本地端範本檔案所在的位置，因為我們直接從網路抓取檔案，所以用不到。這樣最簡單的 generator 便完成，其他常用的 method 請查閱 &lt;a href="http://rdoc.info/projects/wycats/thor"&gt;thor 的 rdoc&lt;/a&gt; 說明。&lt;/p&gt;
&lt;p&gt;一般建議要養成撰寫測試的習慣，先將 spec/generator_demo_spec.rb 的 fail 敘述刪掉，再建立 generator 對應的 spec 檔案：&lt;/p&gt;
&lt;blockquote&gt;mkdir -p spec/generators&lt;br/&gt;touch spec/generators/jquery_ujs_generator_spec.rb&lt;/blockquote&gt;
&lt;p&gt;編輯 jquery_ujs_generator_spec.rb，填入以下內容：&lt;/p&gt;
&lt;pre&gt;require 'spec_helper'
require 'generators/jquery_ujs/jquery_ujs_generator'

describe JqueryUjsGenerator do
  before do
    @generator = JqueryUjsGenerator.new([], {}, {})
  end

  it "fetch rails jquery-ujs file" do
    url = 'http://github.com/rails/jquery-ujs/raw/master/src/rails.js'
    @generator.should_receive(:get).with(url, 'public/javascripts/rails.js').and_return(true)
    @generator.fetch_rails_jquery_ujs_file
  end
end
&lt;/pre&gt;
&lt;p&gt;如果使用 rspec 1.x 的話，直接在 spec/spec_helper.rb 加入：&lt;/p&gt;
&lt;pre&gt;require 'rails/generators'&lt;/pre&gt;
&lt;p&gt;便可檢測 spec 是否通過：&lt;/p&gt;
&lt;blockquote&gt;spec spec&lt;/blockquote&gt;
&lt;p&gt;但這裡打算直接使用 rspec 2，請先安裝好：&lt;/p&gt;
&lt;blockquote&gt;gem install rspec -v=2.0.0.beta.3&lt;/blockquote&gt;
&lt;p&gt;需要修改 spec/spec_helper.rb 成為：&lt;/p&gt;
&lt;pre&gt;$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'rubygems'
require 'rspec'
require 'rspec/autorun'

require 'rails/generators'
require 'generator_demo'

Rspec.configure do |config|
  config.mock_framework = :rspec
end
&lt;/pre&gt;
&lt;p&gt;改用新的指令進行檢測：&lt;/p&gt;
&lt;blockquote&gt;rspec spec&lt;/blockquote&gt;
&lt;p&gt;最後準備打包。由於 jeweler 提供不少相關 rake task 可配合使用，包含自動產生 generator_demo.gemspec 的指令，這個 .gemspec 檔案是用來定義將程式打包成 rubygem 所需的 &lt;a href="http://www.rubygems.org/read/chapter/20"&gt;Gem::Specification&lt;/a&gt;，透過撰寫 jeweler task 的方式來簡化流程，因此只要將相關資訊填入 Rakefile 即可：&lt;/p&gt;
&lt;pre&gt;begin
  require 'jeweler'
  Jeweler::Tasks.new do |gem|
    gem.name = "generator_demo"
    gem.summary = %Q{A rails generator demo}
    gem.description = %Q{tell you creating generator is quit simple}
    gem.email = "tsechingho@gmail.com"
    gem.homepage = "http://github.com/tsechingho/generator_demo"
    gem.authors = ["Tse-Ching Ho"]
    gem.add_development_dependency "rspec", "&gt;= 2.0.0.beta.3"
  end
  Jeweler::GemcutterTasks.new
rescue LoadError
  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
end

begin
  require 'rspec/core/rake_task'
  Rspec::Core::RakeTask.new(:spec)
  Rspec::Core::RakeTask.new(:rcov) do |spec|
    spec.rcov = true
  end
  task :spec =&gt; :check_dependencies
rescue LoadError
  task :spec do
    abort "Rspec 2 is not available. In order to run specs, you must: [sudo] gem install Rspec -v=2.0.0.beta.3"
  end
end&lt;/pre&gt;
&lt;p&gt;第一段是建立 rubygem 的 jeweler task 設定，第二段則是替換 jeweler 原本的 spec task 改使用 rspec 2 的 spec task 寫法。接著便可在 local 端將這個 rubygem 裝起來：&lt;/p&gt;
&lt;blockquote&gt;rake -T&lt;br/&gt;rake spec&lt;br/&gt;rake version:write&lt;br/&gt;rake gemspec&lt;br/&gt;(rake build)&lt;br/&gt;rake install&lt;br/&gt;git add .&lt;br/&gt;git commit -m ‘release 0.0.0; add jquery-ujs generator.’&lt;/blockquote&gt;
&lt;p&gt;先跑完 spec 後，建立 jeweler 需要的 VERSION 檔案來描述 rubygem 的版號，再產生 gemspec，安裝後可用 gem search generator_demo 找到 0.0.0 的版本，記得將更動納入版本控管。&lt;/p&gt;
&lt;p&gt;在 Rails 3 應用程式的 Gemfile 中加入：&lt;/p&gt;
&lt;pre&gt;gem 'generator_demo'&lt;/pre&gt;
&lt;p&gt;便能直接使用這個 generator 了：&lt;/p&gt;
&lt;blockquote&gt;rails generate jquery_ujs&lt;/blockquote&gt;
&lt;p&gt;後續可將程式碼放到 &lt;a href="http://github.com"&gt;github.com&lt;/a&gt; 上。先設定好 &lt;a href="http://help.github.com/"&gt;github 的 ssh token&lt;/a&gt; 並在 github 上建立好同名的 repository（這裡是 generator_demo），依照 github 的指示設定 git config（jeweler 已經預設好同名的 origin remote），之後便可 push 到 github 上去了：&lt;/p&gt;
&lt;blockquote&gt;git push origin master&lt;/blockquote&gt;
&lt;p&gt;再將產生的 rubygem 放到 &lt;a href="http://rubygems.org"&gt;rubygems.org&lt;/a&gt; 上供人使用。先註冊好 rubygems.org 的帳號，通過測試、增加 rubygem 的版號、納入版本控管並包裝後，便可 push 出去：&lt;/p&gt;
&lt;blockquote&gt;rake spec&lt;br/&gt;rake version:bump:minor&lt;br/&gt;rake gemspec&lt;br/&gt;rake install&lt;br/&gt;git add .&lt;br/&gt;git commit -m ‘release 0.1.0’&lt;br/&gt;git push origin master&lt;br/&gt;gem push pkg/generator_demo-0.1.0.gem&lt;/blockquote&gt;
&lt;p&gt;以上便是一整套撰寫與發表 rubygem 的流程，以及 Rails 3/Thor generator 的簡易例子，希望說明夠簡潔易懂。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/438732991</link><guid>http://blog.grassbrook.com/post/438732991</guid><pubDate>Wed, 10 Mar 2010 17:55:00 +0800</pubDate><category>generator</category><category>jeweler</category><category>rails 3</category><category>rspec 2</category><category>thor</category><category>rubygems</category><category>jquery-ujs</category><dc:creator>tsechingho</dc:creator></item><item><title>用 Bundler 管理 Rails 2.3.5 應用程式所需的 gems 套件</title><description>&lt;p&gt;&lt;a href="http://guides.rails.info/3_0_release_notes.html"&gt;Rails 3 beta&lt;/a&gt; 釋放出來一個月了，需要開始為 Rails 2.3.5 的應用程式做轉換準備，首先第一步便是先把 gems 的管理交給 &lt;a href="http://github.com/carlhuda/bundler"&gt;bundler&lt;/a&gt; 來處理。&lt;/p&gt;
&lt;p&gt;步驟很簡單，首先把 bundler (&gt;=0.9.5) 裝起來：&lt;/p&gt;
&lt;blockquote&gt;gem install bundler&lt;/blockquote&gt;
&lt;p&gt;再來修改 Rails 2 應用程式內的 config/boot.rb 檔案，在 Rails.boot! 這行之前加入下面的 patch 來將 bundler 包裹到 Rails 2 的啟動程序：&lt;/p&gt;
&lt;pre&gt;# add this to the bottom of config/boot.rb, before the line `Rails.boot!`
class Rails::Boot
  def run
    load_initializer
    extend_environment
    Rails::Initializer.run(:set_load_path)
  end

  def extend_environment
    Rails::Initializer.class_eval do
      old_load = instance_method(:load_environment)
      define_method(:load_environment) do
        Bundler.require :default, Rails.env
        old_load.bind(self).call
      end
    end
  end
end&lt;/pre&gt;
&lt;p&gt;接著，建立新的設定檔 config/preinitializer.rb 來套用 bundler 設定：&lt;/p&gt;
&lt;pre&gt;# this code goes in config/preinitializer.rb, which you should create if it doesn't exist
begin
  # Require the preresolved locked set of gems.
  require File.expand_path('../../.bundle/environment', __FILE__)
rescue LoadError
  # Fallback on doing the resolve at runtime.
  require "rubygems"
  require "bundler"
  if Gem::Version.new(Bundler::VERSION) &lt;= Gem::Version.new("0.9.5")
    raise RuntimeError, "Bundler incompatible.\n" +
      "Your bundler version is incompatible with Rails 2.3 and an unlocked bundle.\n" +
      "Run `gem install bundler` to upgrade or `bundle lock` to lock."
  else
    Bundler.setup
  end
end&lt;/pre&gt;
&lt;p&gt;然後建立 bundler 環境和 Gemfile 檔案：&lt;/p&gt;
&lt;blockquote&gt;bundle init&lt;/blockquote&gt;
&lt;p&gt;這個指令會自動幫你在 Rails 2 應用程式根目錄下加入 .bundle 目錄和 Gemfile 設定檔：&lt;/p&gt;
&lt;pre&gt;# A sample Gemfile
source :gemcutter
#
# gem "rails"&lt;/pre&gt;
&lt;p&gt;請記得將 .bundle 這個隱藏目錄加入 .gitignore 裡面：&lt;/p&gt;
&lt;pre&gt;.bundle&lt;/pre&gt;
&lt;p&gt;最後，將原本在 config/environment.rb 上的 config.gem 設定都搬到 Gemfile 內，例如：&lt;/p&gt;
&lt;pre&gt;source :gemcutter

gem 'rails', '~&gt; 2.3.5', :require =&gt; nil
gem 'sqlite3-ruby', :require =&gt; 'sqlite3'
gem 'mongrel'

group :development do
  gem 'rails-footnotes', '&gt;= 3.6.3'
end

group :test do
  gem 'rspec-rails'
end

group :cucumber do
  gem 'database_cleaner', '&gt;= 0.5.0', :require =&gt; nil
  gem 'cucumber-rails', '&gt;= 0.3.0', :require =&gt; nil
  gem 'webrat', '&gt;= 0.7.0', :require =&gt; nil
  gem 'rspec-rails', '&gt;= 1.3.2', :require =&gt; nil
end&lt;/pre&gt;
&lt;p&gt;再來檢查設定有沒有成功：&lt;/p&gt;
&lt;blockquote&gt;bundle check&lt;br/&gt;bundle install&lt;br/&gt;bundle show&lt;br/&gt;script/server&lt;/blockquote&gt;
&lt;p&gt;一切無誤，大功告成。&lt;/p&gt;
&lt;hr&gt;&lt;p&gt;&lt;i&gt;番外篇：&lt;br/&gt;有時候還是會遇上些小問題，我在使用 preferences 這個 gem 時，就會遇到 uninitialized constant Preferences::InstanceMethods::Preference 的錯誤，解決方法是乖乖的把 config.gem ’preferences’ 加回 config/environment.rb 去。&lt;/i&gt;&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/427669470</link><guid>http://blog.grassbrook.com/post/427669470</guid><pubDate>Fri, 05 Mar 2010 12:56:00 +0800</pubDate><category>bundler</category><category>rails 2.3.5</category><dc:creator>tsechingho</dc:creator></item><item><title>Authlogic Bundle - 打包 Rails 使用者認證與授權功能</title><description>&lt;p&gt;以現今的技術來說，要開發一套網路應用程式，幾乎都需要建立使用者認證（authentication）與授權（authorization）機制，加上身為華人的我們，中文界面（i18n）更是必要的功能，像 Rails 這種以敏捷開發為特色的網頁程式語言框架（web framework），更是擁有眾多好用且多樣的開放源碼（Open Source）套件可供選擇。&lt;/p&gt;
&lt;p&gt;隨著開發者使用的套件越來越多樣，要啟動一個新專案開發一套新的應用程式，變得不斷在重複弄相關必需套件的設定與修改，然後才能專心於新專案功能的開發。&lt;a title="Rails 2.3" href="http://guides.rubyonrails.org/2_3_release_notes.html"&gt;Rails 2.3&lt;/a&gt; 引入了 template 的功能，讓開發者能將熟稔的套件組合拼湊成一個模板，自動化地套用在新應用程式中，加上內建的 engines 機制，讓程式 MVC 部份可以切割出來放在 Plugin 中，強化了程式模組化的能力，根據這兩樣功能，打造一個程式開發包變成輕鬆且有意義。&lt;/p&gt;
&lt;p&gt;以目前主流的 Rails 開放源碼套件來說，使用者認證以 &lt;a title="Resful_authentication" href="http://github.com/technoweenie/restful-authentication/tree"&gt;Resful_authentication&lt;/a&gt; , &lt;a title="Clearance" href="http://github.com/thoughtbot/clearance/tree"&gt;Clearance&lt;/a&gt; , &lt;a title="Authlogic" href="http://github.com/binarylogic/authlogic/tree"&gt;Authlogic&lt;/a&gt; 這三個套件最受歡迎，並且常會配合使用 &lt;a title="Open ID Authentication" href="http://github.com/rails/open_id_authentication/tree"&gt;Open ID Authentication&lt;/a&gt; 作為單一認證的方式。而使用者授權套件則更為多樣，從簡單的 &lt;a title="Role Requirement" href="http://github.com/jasherai/role_requirement/tree"&gt;Role Requirement&lt;/a&gt; 到功能齊全的 &lt;a title="Rails Authorization Plugin" href="http://github.com/DocSavage/rails-authorization-plugin/tree"&gt;Rails Authorization Plugin&lt;/a&gt; , &lt;a title="Declarative Authorization" href="http://github.com/stffn/declarative_authorization/tree"&gt;Declarative Authorization&lt;/a&gt; 都有。&lt;/p&gt;
&lt;p&gt;於是根據個人的喜好，將 Authlogic, Open ID Authentication, Declarative Authorization 打包成 &lt;a title="Authlogic Bundle" href="http://github.com/tsechingho/authlogic_bundle/tree/master"&gt;Authlogic Bundle&lt;/a&gt;，並支援 &lt;a title="Rails-i18n" href="http://rails-i18n.org/"&gt;Rails-i18n&lt;/a&gt; 及 &lt;a title="Git" href="http://git-scm.com/"&gt;Git&lt;/a&gt; 版本控制系統，以開放源碼的形式給喜愛 Rails 的開發者使用，使用方式十分簡單，只要一行指令：&lt;/p&gt;
&lt;p&gt;$ rails your-app-name -m &lt;a href="http://github.com/tsechingho/authlogic_bundle/raw/master/templates/remote.rb"&gt;http://github.com/tsechingho/authlogic_bundle/raw/master/templates/remote.rb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;便可以輕鬆地開始撰寫專案程式了。修改方式也很容易，利用 engines 的特色，只要將 Plugin 中的程式檔案拷貝到相對應路徑便可以改寫程式行為與界面。如果有任何建議，隨時歡迎指教。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/415133030</link><guid>http://blog.grassbrook.com/post/415133030</guid><pubDate>Sat, 27 Feb 2010 17:55:58 +0800</pubDate><category>authlogic bundle</category><dc:creator>tsechingho</dc:creator></item><item><title>合約的簽法</title><description>&lt;p&gt;ihower 丟出個有趣的議題：&lt;a href="http://ihower.idv.tw/blog/archives/2449"&gt;採用敏捷方法的軟體開發合約該怎麼簽？&lt;/a&gt;，剛好重新讓自己整理一下可能的做法。&lt;/p&gt;
&lt;p&gt;&lt;span&gt;四個重要的專案變數：成本、時間、品質、開發範圍，個人希望用一定的金錢人力成本維持軟體品質，所以將開發範圍做為變動變數是不錯的選擇，加上人生有限，將專案時程以三個月一期劃分是理想的範圍，每期驗收與結算一次，簽約跟報價也就以此為限。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;預備開發期設定在一個月以內，主要是磨合雙方的工作方式，定義基本的需求和規格，基本上要先收取一次費用。此時的需求大概也是業主最希望做到的特點但不一定會完整，其中最重要的工作是協助業主定義出符合特點的 User Story，個人不認為算是捨棄掉敏捷開發一開始不需確定規格的特點，而是先將點子概念具象化。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;而依據預測的規格細節和難度，不一定能在一期內完成，因此在後續劃分的每一個實做開發期才需要再簽約和報價，並討論與確認更詳細的 User Story，而每天或每週的檢討還是需要進行。如果有增加需求的意外，則是和業主討論該如何剔除原有的工作項目，來安插新增的項目，及合約範圍的修正，以便能按時完成每期進度，這樣對雙方都比較有保障。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;當然，業主要先同意工作項目會隨著需求變動，而影響最初合約範圍的界定，甚至專案費用的變動，另外必須同意上述的工作形式。因此合約該如何簽？好的軟體是會不斷演化的，敏捷開發合約也該給予調整與捨棄的空間，專案是可以切割的，合約當然也可以分次簽。&lt;/span&gt;&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/415132931</link><guid>http://blog.grassbrook.com/post/415132931</guid><pubDate>Sat, 27 Feb 2010 17:55:53 +0800</pubDate><dc:creator>tsechingho</dc:creator></item><item><title>推薦：實戰敏捷開發 Practices of an Agile Developer</title><description>&lt;p&gt;關於 &lt;a href="http://www.pragprog.com/titles/pad/practices-of-an-agile-developer"&gt;Practices of an Agile Developer&lt;/a&gt; 一書，&lt;a href="http://ihower.idv.tw/blog/"&gt;ihower&lt;/a&gt; 整理了六篇專業的讀書心得，相信對於寫了幾年程式的人來說都心有戚戚焉，對於有心學習敏捷開發的人更是提供了良好的指導守則，這六篇文章也點出了專業程式設計師在開發過程中各方面的自我要求是很高的，應該可以讓一般對專業程式設計不甚熟悉的人們稍微了解設計師們的堅持。&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/1750"&gt;專業態度篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/1756"&gt;需求篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/1758"&gt;測試篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/1898"&gt;程式篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/2333"&gt;除錯篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href="http://ihower.idv.tw/blog/archives/2369"&gt;團隊開發篇&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description><link>http://blog.grassbrook.com/post/415132847</link><guid>http://blog.grassbrook.com/post/415132847</guid><pubDate>Sat, 27 Feb 2010 17:55:00 +0800</pubDate><category>agile programming</category><dc:creator>tsechingho</dc:creator></item><item><title>關於禾川</title><description>&lt;p&gt;禾川資訊致力於網路應用程式的開發，採用網頁設計標準，遵循敏捷式開發守則，替客戶打造直覺、便利、好用、易維護的系統。&lt;/p&gt;
&lt;p&gt;我們主要採用的技術以 Ruby、Rails、AJAX、HTML、CSS 為主，搭配主機端網頁伺服器（如 Apache）、資料庫系統（如 MySQL）、版本控制系統（如 Git、Subversion）和其他各式函式庫、程式套件等，完成系統的整合與建置。&lt;/p&gt;
&lt;p&gt;&lt;span&gt;目前禾川資訊主要提供的服務類型包含： &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;span&gt;網路應用程式的系統規劃、前後台設計、諮詢與建置（Full Design/Development）。&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;網路應用程式設計外包、合作（You source out, We take in）。&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;網路應用程式更新、改造與維護（Update/Rewrite/Maintain Services）。&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;span&gt;同時歡迎您的其他提案。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;禾川資訊位於台灣中部的台中市，有任何建議或提案請 E-Mail 至 tsechingho@gmail.com 與我們聯絡或電洽 04-2317-7697 何先生。&lt;/p&gt;</description><link>http://blog.grassbrook.com/post/415127970</link><guid>http://blog.grassbrook.com/post/415127970</guid><pubDate>Sat, 27 Feb 2010 17:50:00 +0800</pubDate><category>grassbrook</category><dc:creator>tsechingho</dc:creator></item></channel></rss>

