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

时间:2011-02-13 04:04:48

标签: ruby-on-rails exception-handling

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

5 个答案:

答案 0 :(得分:17)

我认为没有办法只针对单一类型的验证失败抛出异常。您可以执行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.') }

我不确定这是否是解决这个问题的唯一方法。

答案 1 :(得分:9)

您需要使用rescue_from

在您的控制器中

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

 ....

 protected

 def my_rescue_method
   ...
 end

但是,您不想让您的记录无效而不是抛出异常吗?

答案 2 :(得分:9)

我会改变一些关于验证的事情:

  1. 在单独的验证中进行存在,唯一性和格式验证。 (您在传递给“验证”的属性哈希中的唯一性键在验证中被覆盖)。我会让它看起来更像:

    validates_uniqueness_of:email,:scope =&gt; :位置

    validates_presence_of:电子邮件

    validates_format_of:email,:with =&gt; RFC_822#我们使用全局验证正则表达式

  2. 验证是应用程序级别,您应该将它们分开的原因之一是因为可以在不触及数据库的情况下完成在线状态和格式验证。唯一性验证将触及数据库,但不会使用您设置的唯一索引。应用程序级别验证不与它们生成SQL的数据库内部交互,并且基于查询结果确定有效性。您可以保留validates_uniqueness_of,但要为您的申请中的竞争条件做好准备。

  3. 由于验证是应用程序级别,它将请求行(类似“SELECT * FROM subscriptions WHERE email ='email_address'LIMIT 1”),如果返回一行,则验证失败。如果未返回行,则认为该行有效。

    但是,如果同时其他人使用相同的电子邮件地址注册并且他们都没有在创建新行之前返回一行,则第二次“保存”提交将触发唯一性数据库索引约束而不触发验证在申请中。 (因为很可能它们在不同的应用程序服务器上运行,或者至少在不同的VM或进程上运行)。

    验证失败时引发

    ActiveRecord :: RecordInvalid ,而不是在违反数据库的唯一索引约束时引发。 (在请求/响应生命周期的不同点可以触发多个级别的ActiveRecord异常)

    在第一级(应用程序级别)引发RecordInvalid,而在尝试提交并且数据库服务器确定事务不符合索引约束之后,可以引发 RecordNotUnique 。 ( ActiveRecord :: StatementInvalid 是将在此实例中引发的post fetch Exception的父级,如果您实际上尝试获取数据库反馈而不是应用程序级别验证,则应该将其解救) / p>

    如果您在控制器中“rescue_from”(如The Who所述)应该可以很好地从这些不同类型的错误中恢复,看起来最初的意图是以不同的方式处理它们所以你可以使用多个“rescue_from”来电。

答案 3 :(得分:5)

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

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

区分不同的RecordInvalid类型非常方便,不需要依赖异常错误消息。

请注意,它包含验证过程报告的所有错误,这使得处理多个唯一性验证错误变得更加容易。

例如,您可以检查model-attribute的所有验证错误是否只是唯一性错误:

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

答案 4 :(得分:2)

这个gem在模型级别挽救约束失败并添加模型错误(model.errors),使其行为与其他验证失败一样。请享用! https://github.com/reverbdotcom/rescue-unique-constraint