Ruby-on-rails 处理控制器中的唯一记录异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4982371/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 00:24:40  来源:igfitidea点击:

Handling Unique Record Exceptions in a Controller

ruby-on-railsexception-handling

提问by Dex

I have a model called Subscription that has a unique index on the fields [:email, :location]. This means one email address can subscribe per location.

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

In my model:

在我的模型中:

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

In my create method. I want to handle the the exception ActiveRecord::RecordNotUniquedifferently than a regular error. How would I add that in to this generic create method?

在我的创建方法中。我想以ActiveRecord::RecordNotUnique不同于常规错误的方式处理异常。我如何将它添加到这个通用的 create 方法中?

  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

回答by Chirantan

I don't think there is a way to have an exception thrown just for a single type of validation failure. Either you can do a save!which would raise exceptions for all save errors (including all validation errors) and have them handled separately.

我认为没有办法只针对单一类型的验证失败抛出异常。您可以执行一个save!会引发所有保存错误(包括所有验证错误)的异常并分别处理它们的方法。

What you can do is handle the exception ActiveRecord::RecordInvalidand match the exception message with Validation failed: Email has already been takenand then handle it separately. But this also means that you would have to handle other errors too.

您可以做的是处理异常ActiveRecord::RecordInvalid并将异常消息与之匹配Validation failed: Email has already been taken,然后单独处理。但这也意味着您还必须处理其他错误。

Something like,

就像是,

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.') }

I'm not sure if this is the only solution to this though.

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

回答by The Who

You will want to use rescue_from

你会想要使用 rescue_from

In your controller

在您的控制器中

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

 ....

 protected

 def my_rescue_method
   ...
 end

However, wouldn't you want to invalidate your record rather than throwing an exception?

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

回答by Brandon

A couple things I would change about the validation:

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

  1. Do the presence, uniqueness, and format validations in separate validations. (Your uniqueness key in the attributes hash you are passing to "validates" is being overwritten in your validation). I would make it look more like:

    validates_uniqueness_of :email, :scope => :location

    validates_presence_of :email

    validates_format_of :email, :with => RFC_822 # We use global validation regexes

  2. Validations are Application level, one of the reasons you should separate them is because the presence and format validations can be done without touching the database. The uniqueness validation will touch the database, but won't use the unique index that you setup. Application level validations don't interact with the database internals they generate SQL and based on the query results make a determination of validity. You can leave the validates_uniqueness_of but be prepared for race conditions in your application.

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

    validates_uniqueness_of :email, :scope => :location

    validates_presence_of :email

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

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

Since the validation is application level it will request the row (something like "SELECT * FROM subscriptions WHERE email = 'email_address' LIMIT 1"), if a row is returned then the validation fails. If a row is not returned then it is considered valid.

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

However, if at the same time someone else signs up with the same email address and they both do not return a row before creating a new one then the 2nd "save" commit will trigger the uniqueness Database index constraint without triggering the validation in the application. (Since most likely they are running on different application servers or at least different VM's or processes).

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

ActiveRecord::RecordInvalidis raised when the validation fails, not when the unique index constraint on the database is violated. (There are multiple levels of ActiveRecord Exceptions that can be triggered at different points in the request/response lifecycle)

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

RecordInvalid is raised at the first level (Application level) whereas RecordNotUniquecan be raised after the submission is attempted and the database server determines the transaction does not meet the index constraint. (ActiveRecord::StatementInvalidis the parent of the post fetch Exception that will be raised in this instance and you should rescue it if you are actually trying to get the database feedback and not the Application level validation)

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

If you are in your controller "rescue_from"(as outlined by The Who) should work just fine to recover from these different types of errors and it looks like the initial intent was to handle them differently so you can do so with multiple "rescue_from"calls.

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

回答by LinuCC

Adding to Chirantans answer, with Rails 5 (or 3/4, with this Backport) you can also use the new errors.details:

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

Which is very handy for differentiating between the different RecordInvalidtypes and does not require relying on the exceptions error-message.

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

Note that it includes all errors reported by the validation-process, which makes handling multiple uniqueness-validation-errors much easier.

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

For example, you can check if all validation-errors for a model-attribute are just uniqueness-errors:

例如,您可以检查模型属性的所有验证错误是否只是唯一性错误:

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

回答by Yan Pritzker

This gem rescues the constraint failure at the model level and adds a model error (model.errors) so that it behaves like other validation failures. Enjoy! https://github.com/reverbdotcom/rescue-unique-constraint

这个 gem 在模型级别挽救了约束失败并添加了一个模型错误(model.errors),使其表现得像其他验证失败。享受!https://github.com/reverbdotcom/rescue-unique-constraint