2011-02-13 105 views
9

我有一個稱爲Subscription的模型,它在字段[:email,:location]上具有唯一索引。這意味着每個位置可以訂閱一個電子郵件地址。處理控制器中的唯一記錄異常

在我的模型:

class Subscription < ActiveRecord::Base 
    validates :email, :presence => true, :uniqueness => true, :email_format => true, :uniqueness => {:scope => :location} 
end 

在我的創作方法。我想處理異常ActiveRecord::RecordNotUnique與常規錯誤不同。我將如何添加到這種通用的創建方法?

def create 
    @subscription = Subscription.new(params[:subscription]) 
    respond_to do |format| 
     if @subscription.save 
     format.html { redirect_to(root_url, :notice => 'Subscription was successfully created.') } 
     else 
     format.html { render :action => 'new' } 
     end 
    end 
    end 

回答

16

我不認爲有一種方法可以爲單一類型的驗證失敗拋出異常。要麼你可以做一個save!這會引發所有保存錯誤(包括所有驗證錯誤)的異常,並讓它們分開處理。

您可以做的是處理異常ActiveRecord::RecordInvalid並將異常消息與Validation failed: Email has already been taken相匹配,然後單獨處理。但這也意味着你將不得不處理其他錯誤。

喜歡的東西,

begin 
    @subscription.save! 
rescue ActiveRecord::RecordInvalid => e 
    if e.message == 'Validation failed: Email has already been taken' 
    # Do your thing.... 
    else 
    format.html { render :action => 'new' } 
    end 
end 
format.html { redirect_to(root_url, :notice => 'Subscription was successfully created.') } 

我不知道這是否是這個,雖然唯一的解決辦法。

+0

`save!`是我錯過的。但是,兩者都可以工作,但是,您的解決方案更徹底。我還在救援線上做了一些小小的修改,說SO需要進行同行評審。 – Dex 2011-02-13 04:49:29

8

你將要使用rescue_from

在你的控制器

rescue_from ActiveRecord::RecordNotUnique, :with => :my_rescue_method 

.... 

protected 

def my_rescue_method 
    ... 
end 

但是,你會不會想取消你記錄,而不是拋出異常?

+0

這是如果在數據庫中已經找到。無需使其失效,但我願意提供更好的建議。此外,它看起來像被拋出的錯誤實際上是`的ActiveRecord :: RecordInvalid異常:驗證失敗:電子郵件已經採取了」,而不是RecordNotUnique。 – Dex 2011-02-13 04:17:29

+0

雖然代碼似乎仍然不起作用。 – Dex 2011-02-13 04:23:45

+0

作爲敏捷指出,正確的例外是的ActiveRecord :: RecordInvalid。 – 2011-02-13 08:58:10

9

一對夫婦的事情,我會改變有關驗證:

  1. 執行存在,獨特性和格式驗證在不同的驗證。 (您驗證中將覆蓋您傳遞給「驗證」的屬性哈希中的唯一性鍵)。我想使它看起來更像:

    validates_uniqueness_of:電子郵件:範圍=>:位置

    validates_presence_of:電子郵件

    validates_format_of:電子郵件:與=> RFC_822#我們使用全球驗證的正則表達式

  2. 驗證是應用程序級別,您應該將它們分開的原因之一是因爲可以在不接觸數據庫的情況下完成狀態和格式驗證。唯一性驗證將觸及數據庫,但不會使用您設置的唯一索引。應用程序級驗證不會與它們生成SQL的數據庫內部進行交互,並且基於查詢結果確定有效性。您可以離開validates_uniqueness_of,但爲應用程序中的競爭條件做好準備。

由於驗證是應用層面,將請求行(像「SELECT * FROM訂閱WHERE電子郵件=‘EMAIL_ADDRESS’LIMIT 1」),如果返回行則驗證失敗。如果一行沒有被返回,那麼它被認爲是有效的。

但是,如果在同一時間其他人註冊時使用相同的電子郵件地址,並且它們在創建新電子郵件地址時都沒有返回一行,那麼第二次「保存」提交將觸發唯一性數據庫索引約束,而不觸發在應用程序中驗證。 (因爲它們很可能在不同的應用程序服務器上運行,或者至少運行在不同的虛擬機或進程上)。

ActiveRecord :: RecordInvalid在驗證失敗時引發,而不是違反數據庫上的唯一索引約束。 (存在可以在請求/響應的生命週期的不同點被觸發的ActiveRecord例外的多個級別)

RecordInvalid在第一電平(應用級)升高而RecordNotUnique可以提交嘗試之後被提升和數據庫服務器確定事務不符合索引約束。 (的ActiveRecord :: StatementInvalid是後的母公司獲取的異常將在此情況下得到提升,你應該救它,如果你實際上是試圖讓數據庫的反饋,而不是應用程序級別的驗證)

如果您在你的控制器「rescue_from」(由世界衛生組織所概述)應該只是罰款從這些不同類型的錯誤恢復,它看起來像最初的目的是爲了不同的方式處理他們,你可以與多個「rescue_from這樣做「電話。

3

添加到Chirantans答案,使用Rails 5(或3/4,與此Backport),您也可以使用新的errors.details

begin 
    @subscription.save! 
rescue ActiveRecord::RecordInvalid => e 
    e.record.errors.details 
    # => {"email":[{"error":"taken","value":"[email protected]"}]} 
end 

這對於不同類型的RecordInvalid和不區分非常方便不要求依賴異常錯誤消息。

注意它通過驗證過程,這使得處理多個唯一性驗證,錯誤更容易報告的所有錯誤。

例如,您可以檢查是否爲一個模型屬性的所有驗證,錯誤只是唯一性錯誤:

exception.record.errors.details.all? do |hash_element| 
    error_details = hash_element[1] 
    error_details.all? { |detail| detail[:error] == :taken } 
end