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 ac开发者_开发知识库cording 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 'foo@bar.com' 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?
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
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
精彩评论