ActiveRecord 异常未获救

发布于 2024-10-10 08:49:33 字数 1409 浏览 6 评论 0原文

我有以下代码块:

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

正如您可能猜到的那样,原因是多个进程可能会同时调用此方法来创建用户(如果它尚不存在),因此当第一个进程进入该块时并开始初始化一个新用户,设置属性,最后调用 save!,该用户可能已经创建了。 在这种情况下,我想再次检查用户是否存在,并且仅在仍然不存在时才引发异常(=如果同时没有其他进程创建它)。

问题是,保存时会定期引发 ActiveRecord::RecordInvalid 异常!并且没有从救援区获救。 有什么想法吗?

编辑:

好吧,这很奇怪。我一定是错过了什么。我根据 Simone 的提示重构了代码,如下所示:

unless User.find_by_email(...).present?
  # Here we know the user does not exist yet
  user = User.new(...)
  # Set more attributes of user
  unless user.save
    # User could not be saved for some reason, maybe created by another request?
    raise StandardError, "Could not create user for order #{self.id}." unless User.exists?(:email => ...)
  end
end

现在我得到了以下异常:

ActiveRecord::RecordNotUnique: Mysql::DupEntry: Duplicate entry '[email protected]' for key 'index_users_on_email': INSERT INTO `users` ...

在“除非 user.save”所在的行中抛出。 怎么可能呢? Rails 认为可以创建用户,因为电子邮件是唯一的,但 Mysql 唯一索引会阻止插入?这有多大可能?如何避免呢?

I have the following code block:

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

The reason is, as you can probably guess, that multiple processes might call this method at the same time to create the user (if it doesn't already exist), so while the first one enters the block and starts initializing a new user, setting the attributes and finally calling save!, the user might already be created.
In that case I want to check again if the user exists and only raise the exception if it still doesn't (= if no other process has created it in the meantime).

The problem is, that regularly ActiveRecord::RecordInvalid exceptions are raised from the save! and not rescued from the rescue block.
Any ideas?

EDIT:

Alright, this is weird. I must be missing something. I refactored the code according to Simone's tip to look like this:

unless User.find_by_email(...).present?
  # Here we know the user does not exist yet
  user = User.new(...)
  # Set more attributes of user
  unless user.save
    # User could not be saved for some reason, maybe created by another request?
    raise StandardError, "Could not create user for order #{self.id}." unless User.exists?(:email => ...)
  end
end

Now I got the following exception:

ActiveRecord::RecordNotUnique: Mysql::DupEntry: Duplicate entry '[email protected]' for key 'index_users_on_email': INSERT INTO `users` ...

thrown in the line where it says 'unless user.save'.
How can that be? Rails thinks the user can be created because the email is unique but then the Mysql unique index prevents the insert? How likely is that? And how can it be avoided?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

╰つ倒转 2024-10-17 08:49:33

在这种情况下,您可能希望使用迁移在用户表键上创建唯一索引,这样数据库就会引发错误。

另外,不要忘记在用户模型中添加 validates_uniqueness_of 验证。

验证并不总是可以防止重复数据(两个并发请求在同一毫秒写入的可能性确实很小)。
如果您将 validates_uniqueness_of 与索引结合使用,则不需要所有这些代码。

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

变成

user = User.new(...)
# Set more attributes of user
if user.save
  # saved
else
  # user.errors will return
  # the list of errors
end

In this case, you might want to use a migration to create an unique index on a user table key, so that the database will raise an error.

Also, don't forget to add a validates_uniqueness_of validation in your user model.

The validation doesn't always prevent duplicate data (there's a really minimum chance that two concurrent requests are written at the same millisecond).
If you use the validates_uniqueness_of in combination with an index, you don't need all that code.

unless User.exist?(...)
  begin
    user = User.new(...)
    # Set more attributes of user
    user.save!
  rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
    # Check if that user was created in the meantime
    user = User.exists?(...)
    raise e if user.nil?
  end
end

becomes

user = User.new(...)
# Set more attributes of user
if user.save
  # saved
else
  # user.errors will return
  # the list of errors
end
涙—继续流 2024-10-17 08:49:33

Rails 验证无法检测数据库中的竞争条件;我们使用的解决方案是还添加数据库约束。

以下是我们关于此内容的简短链接页面: Rails ActiveRecord 验证:验证种族的唯一性

Rails validations aren't able to detect race conditions in the database; the solution we use is to also add database constraints.

Here's our brief page of links about this: Rails ActiveRecord Validations: validates_uniqueness_of races

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文