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

Dex*_*Dex 11 exception-handling ruby-on-rails

我有一个名为Subscription的模型,它在字段[:email,:location]上有唯一索引.这意味着每个位置可以订阅一个电子邮件地址.

在我的模型中:

class Subscription < ActiveRecord::Base
  validates :email, :presence => true, :uniqueness => true, :email_format => true, :uniqueness => {:scope => :location}
end
Run Code Online (Sandbox Code Playgroud)

在我的创建方法中.我想以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
Run Code Online (Sandbox Code Playgroud)

Chi*_*tan 18

我认为没有办法只针对单一类型的验证失败抛出异常.要么你可以做一个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.') }
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是唯一的解决方案.

  • 请不要从`Exception`中解救,因为这是Ruby中所有异常的超类.您正在有效地拯救*所有*异常.只需`ActiveRecord :: RecordInvalid`即可. (7认同)

The*_*Who 10

你会想要使用 rescue_from

在你的控制器中

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

 ....

 protected

 def my_rescue_method
   ...
 end
Run Code Online (Sandbox Code Playgroud)

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


Bra*_*don 9

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

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

    validates_uniqueness_of:email,:scope =>:location

    validates_presence_of:电子邮件

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

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

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

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

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

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

如果您在控制器中"rescue_from"(如The Who所述)应该可以正常工作以从这些不同类型的错误中恢复,看起来最初的意图是以不同方式处理它们,以便您可以使用多个"rescue_from"调用.


小智 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
Run Code Online (Sandbox Code Playgroud)

这对于区分不同RecordInvalid类型非常方便,并且不需要依赖异常错误消息.

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

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

exception.record.errors.details.all? do |hash_element|
  error_details = hash_element[1]  
  error_details.all? { |detail| detail[:error] == :taken }
end
Run Code Online (Sandbox Code Playgroud)


小智 5

该 gem 在模型级别挽救约束失败并添加模型错误 (model.errors),以便其行为与其他验证失败类似。享受!https://github.com/reverbdotcom/rescue-unique-constraint