处理控制器中的唯一记录exception

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

在我的模型中:

class Subscription  true, :uniqueness => true, :email_format => true, :uniqueness => {:scope => :location} end 

在我的创建方法中。 我想以不同于常规错误的方式处理exceptionActiveRecord::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 

我认为没有办法只针对单一类型的validation失败抛出exception。 要么你可以做save! 这将引发所有保存错误(包括所有validation错误)的exception,并将它们单独处理。

您可以做的是处理exceptionActiveRecord::RecordInvalid并将exception消息与Validation failed: Email has already been taken匹配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.') } 

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

您将需要使用rescue_from

在你的控制器中

  rescue_from ActiveRecord::RecordNotUnique, :with => :my_rescue_method .... protected def my_rescue_method ... end 

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

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

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

    validates_uniqueness_of:email,:scope =>:location

    validates_presence_of:电子邮件

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

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

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

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

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

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

如果您在控制器中“rescue_from” (如The Who所述)应该可以正常工作以从这些不同类型的错误中恢复,看起来最初的意图是以不同的方式处理它们,这样您就可以使用多个“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":"user@example.org"}]} end 

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

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

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

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

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