此文翻譯自Reading Rails - Handling Exceptions,限於本人水平,翻譯不當之處,敬請指教!html
咱們今天開始會讀一些Rails的源碼。咱們有雙重的目的,先經過學習(Rails)如何處理異常,再擴展到整個Ruby中基礎知識的學習。
git
Rails經過讓你使用rescue_from
方法,讓你在你的controller
裏邊爲常見的異常定義處理方法。舉例來講吧,你能夠在用戶試圖訪問他們還沒有付費的功能時將他們重定向到指定的付費頁面。github
class ApplicationController # Redirect users if they try to use disabled features. rescue_from FeatureDisabledError, InsufficientAccessError do |ex| flash[:alert] = "Your account does not support #{ex.feature_name}" redirect_to "/pricing" end #...
咱們將會探索Rails是如何定義異常處理器,如何將它們與具體的異常進行匹配,以及如何使用它們去rescue失敗的action。編程
若是須要跟着個人步驟走,請使用qwandry打開每個相關的代碼庫,或者直接從github查看源碼便可。數組
ActiveSupport包含了一個用於定義異常如何被處理的模塊Rescuable
。第一個須要瞭解的方法就是rescue_from
。這個方法經過方法名或者代碼塊爲你想rescue的異常註冊處理器(提示:查看代碼,請在命令行中輸入qw activesupport
):ruby
def rescue_from(*klasses, &block) options = klasses.extract_options! unless options.has_key?(:with) if block_given? options[:with] = block else #...
首先,*klasses
接收數量不定的異常類,因此你能夠進行相似rescue_from(FeatureDisabledError, InsufficientAccessError)
這樣的調用。它們將會被存放在一個數組裏。less
接下來,請留意extract_options!
的使用。這是一個常見的用於從一個數組生成一個options哈希表的技巧。假如klasses裏邊的最後一個元素是一個哈希表,那麼這個元素會被彈出數組。如今Rails將會使用:with
項所指定的方法,或者是使用傳遞給rescue_from的代碼塊。Rails中的這種技巧創造了一個靈活的接口。ide
接着繼續往下看這個方法,咱們看到每個異常類都被轉換成一個String對象,咱們待會便會看到爲何要這麼作。學習
def rescue_from(*klasses, &block) #... key = if klass.is_a?(Class) && klass <= Exception klass.name elsif klass.is_a?(String) klass else #...
這裏你應該注意的是,Rails是如何斷定klass
是否是繼承自Exception
的。一般狀況下,你可能會經過使用obj.is_a?(Exception)
來判斷一個對象是否是某一個具體類型的實例,即便如此,klass
並非Exception
,而只是Class
。那麼咱們又怎麼找出它使哪一類呢?Ruby在Module
上定義了相似<=
這樣的用於比較的操做符。當操做符左邊的對象是操做符右邊對象的子類的時候,它會返回true。舉個例子,ActiveRecord::RecordNotFound < Exception
返回true,而ActiveRecord::RecordNotFound > Exception
返回false。ui
在這個方法的末尾,咱們看到表示異常類的String
對象稍後被儲存在二元數組中:
def rescue_from(*klasses, &block) #... self.rescue_handlers += [[key, options[:with]]] end
如今咱們已經知道了處理器是如何儲存的,可是當Rails須要處理異常的時候,它又是如何查找這些處理器的呢?
通過對rescue_handlers
的快速搜索發現,這一切使用到了handler_for_rescue
。咱們能夠看到每個可能的處理器都被一一檢查,直到咱們找到可以與exception
匹配的處理器:
def handler_for_rescue(exception) # 咱們遵循從右到左的順序,是由於每當發現一個rescue_from聲明的時候, # 相應的klass_name, handler對就會被壓入resuce_handlers裏。 _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler| #... klass = self.class.const_get(klass_name) rescue nil klass ||= klass_name.constantize rescue nil exception.is_a?(klass) if klass end #...
如同註釋所言,rescue_handlers
被反序讀取。假若有兩個處理器可以處理同一個異常,那麼最後定義的處理器會被優先選中。假如你先定義了一個針對ActiveRecord::NotFoundError
異常的處理器,接着又定義了針對Exception
異常的處理器,那麼前者將永遠都不會被調用,由於針對Exception
的處理器老是會優先匹配。
如今,在代碼塊裏邊,又發生了什麼呢?
首先,字符串對象klass_name
被當作當前類內部的常量進行查找,在找不到的狀況下會繼續判斷它是否是定義在程序內部其餘地方的常量,以此將klass_name
轉換爲實際的類。每一步都經過返回nil
進行rescue。這麼作的一個緣由就是當前處理器多是針對某個還沒有加載的異常的類型。舉例來講,一個插件裏可能爲ActiveRecord::NotFoundError
定義了錯誤處理,可是你可能並無使用ActiveRecord
。在這樣的狀況下,引用這個異常將會致使異常。每一行最後的rescue nil
可以在沒法找到類時無聲無息地組織異常的拋出。
最後咱們檢查這個異常(等待匹配的異常)是不是這個處理器所對應異常類的實例。若是是,數組[klass_name, handler]
將會被返回。返回到上邊看看_, rescuer = ...
這一行代碼,這一一個數組拆分的例子。由於咱們實際上只想要返回數組的第二個元素,也就是處理器,因此_
在這裏只是一個佔位符。
如今咱們知道了程序是如何查找異常處理器的,可是它又是如何被調用的呢?爲了回答這最後一個問題,咱們能夠返回到源代碼文件的頂部而後探索一下rescue_with_handler
方法。當給它傳遞一個異常的時候,它將會嘗試經過調用合適的處理器來處理這個異常。
def rescue_with_handler(exception) if handler = handler_for_rescue(exception) handler.arity != 0 ? handler.call(exception) : handler.call end end
爲了瞭解這個方法是如何在你的controller裏邊生效的,咱們須要查看ActionPack包裏邊的代碼。(提示:能夠在命令行中鍵入qw actionpack
打開ActionPace的代碼)Rails定義了一個叫作ActionController::Rescue
的中間件,它被混入到了Rescuable
模塊裏邊,而且經過precess_action
調用。
def process_action(*args) super rescue Exception => exception rescue_with_handler(exception) || raise(exception) end
Rails在收到每個請求時都會調用process_action
,假如請求致使一個異常即將被拋出,rescue_with_handler
都會試圖去處理這個異常。
Rescuable
可以被混入到其它代碼之中。假如你想集中化你的異常處理部分的邏輯,那麼你能夠考慮一下使用Rescuable
。舉個例子,假如你有不少發向遠程服務的請求,而且你不想在每個方法裏邊重複異常處理的邏輯:
class RemoteService include Rescuable rescue_from Net::HTTPNotFound, Net::HTTPNotAcceptable do |ex| disable_service! log_http_failure(@endpoint, ex) end rescue_from Net::HTTPNetworkAuthenticationRequired do |ex| authorize! end def get_status #... rescue Exception => exception rescue_with_handler(exception) || raise(exception) end def update_status #... rescue Exception => exception rescue_with_handler(exception) || raise(exception) end end
使用一點元編程的技巧,你甚至能夠經過相似的模式對已有的方法進行封裝以免rescue代碼塊。
ActiveSupport的Rescuable
模塊容許咱們定義異常處理方法。ActionController的Rescue
中間件捕捉異常,並試圖處理這些異常。
咱們也同時瞭解到:
rescue_from(*klasses)
的方法能夠接收數量不定的參數。Array#extract_options!
方法是一個用於從arguments數組獲得options的技巧。klass <= Exception
這樣的代碼判讀一個類是否某個類的子類。rescue nil
將會靜默地消除異常。就算是再小的代碼片斷都包含了很是多有用的信息,請讓我知道你下一步想要了解什麼東西,咱們還會看到可以從Rails裏邊挖掘到的新奇玩意。
閱讀另外8篇「解讀Rails」中的文章。