Исключения ActiveRecord не спасены - PullRequest
4 голосов
/ 06 января 2011

У меня есть следующий блок кода:

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 исключения возникают из сохранения!и не спасен от спасательного блока.Есть идеи?

РЕДАКТИРОВАТЬ:

Хорошо, это странно.Я должен что-то упустить.Я реорганизовал код в соответствии с советами Симоны, чтобы он выглядел следующим образом:

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 'foo@bar.com' for key 'index_users_on_email': INSERT INTO `users` ...

в строке, где написано «разве что user.save».Как это может быть?Rails думает, что пользователь может быть создан, потому что электронная почта уникальна, но тогда уникальный индекс Mysql предотвращает вставку?Насколько вероятно это?И как этого избежать?

Ответы [ 2 ]

3 голосов
/ 28 апреля 2011

Проверки Rails не могут обнаружить условия гонки в базе данных;Решение, которое мы используем, заключается также в добавлении ограничений базы данных.

Вот наша краткая страница ссылок по этому поводу: Проверка Rails ActiveRecord: validates_uniqueness_of races

3 голосов
/ 06 января 2011

В этом случае вы можете использовать миграцию для создания уникального индекса для ключа таблицы пользователя, чтобы база данных выдавала ошибку.

Кроме того, не забудьте добавить 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
...