Обработка исключений уникальных ограничений внутри транзакции - PullRequest
0 голосов
/ 26 июня 2018

У меня есть две модели / таблицы Rails, которые я хочу вставить как часть транзакции.

Например, вот примерно мои таблицы:

  • Сообщений: (id)
  • Комментарии: (id, post_id, comment_text, user_id)
  • Комментаторы: (id, post_id, user_id), уникальное ограничение на (post_id, user_id)

Прямо сейчасЯ пытаюсь примерно эквивалентно:

ActiveRecord::Base.transaction do
  Comment.create!(post: post, user: user, comment_text: '...')

  begin
    Commenters.find_or_create_by!(post: post, user: user)
  rescue PG::UniqueViolation
  end
end

Это работает 99,9% времени, но иногда два одновременных комментария вызовут PG :: UniqueViolation.

Даже если я ловлю иподавляя PG :: UniqueViolation, вся транзакция завершается неудачно из-за:

ОШИБКА: текущая транзакция отменяется, команды игнорируются до конца блока транзакции

Я понимаю, что могуже достигли этого, присоединившись к таблице Post и Comment, но это упрощенный пример.

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

Ответы [ 2 ]

0 голосов
/ 29 июня 2018

Исключение, возникающее внутри транзакции, делает две вещи:

  1. Откатывает транзакцию, поэтому никакие изменения, сделанные внутри транзакции, не сохраняются в базе данных.
  2. Распространение исключенияза пределами блока транзакции.

Таким образом, вы можете переместить обработчик исключений за пределы вызова transaction:

begin
  ActiveRecord::Base.transaction do
    Comment.create!(post: post, user: user, comment_text: '...')
    Commenters.find_or_create_by!(post: post, user: user)
  end
rescue PG::UniqueViolation
  retry
end

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

0 голосов
/ 26 июня 2018

У вас должны быть правильно установлены ассоциации внутри ваших моделей, поэтому rails сделает проверку за вас.Тогда вы можете просто спасти возможный ActiveRecord::RecordInvalid (с сообщением Validation failed: Атрибут has already been taken).

Если вы хотите прочитать больше о проверке уникальности, это может пригодиться:http://guides.rubyonrails.org/active_record_validations.html#uniqueness

Мне действительно кажется, что ваша Commenter модель на самом деле не нужна.Он просто состоит из производной информации, которую также можно извлечь непосредственно из модели Comments (там вы уже храните post_id и user_id), чтобы вы могли полностью исключить класс Commenter.Затем позаботьтесь о проверке Comment при создании, например, установив

class Comment
 belongs_to :post
 belongs_to :user
 validates :user_id, uniqueness: {scope: :post_id}
end

Но таким образом вы позволите пользователю комментировать только один раз.

Я предлагаю вам удалить ограничение уникальности исоздайте информацию, хранящуюся в Commenter, сделав выбор distinct на модели Comment.

PS: модели в рельсах пишутся в верхнем регистре и в единственном числе, тогда как к таблицам обращаются (символами) в нижнем регистре имножественное число.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...