处理控制器中的唯一记录异常

问题描述:

我有一个称为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 

我不认为有一种方法可以为单一类型的验证失败抛出异常。要么你可以做一个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

你将要使用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

一对夫妇的事情,我会改变有关验证:

  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这样做“电话。

添加到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 

这种宝石抢救约束失效在模型级,并增加了模型误差(模型.errors),这样它的行为就像其他验证失败一样。请享用! https://github.com/reverbdotcom/rescue-unique-constraint