Обработка уникальных исключений записей в контроллере - PullRequest
9 голосов
/ 13 февраля 2011

У меня есть модель с именем Подписка, которая имеет уникальный индекс для полей [: email,: location].Это означает, что один адрес электронной почты может подписаться на местоположение.

В моей модели:

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

В моем методе создания.Я хочу обработать исключение ActiveRecord::RecordNotUnique иначе, чем обычная ошибка.Как бы я добавил это в этот общий метод создания?

  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

Ответы [ 5 ]

17 голосов
/ 13 февраля 2011

Я не думаю, что есть способ создать исключение только для одного типа ошибки проверки. Либо вы можете сделать save!, который вызовет исключения для всех ошибок сохранения (включая все ошибки проверки) и обработает их отдельно.

Что вы можете сделать, это обработать исключение ActiveRecord::RecordInvalid и сопоставить сообщение об исключении с Validation failed: Email has already been taken, а затем обработать его отдельно. Но это также означает, что вам придется обрабатывать и другие ошибки.

Что-то вроде,

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

Хотя я не уверен, что это единственное решение этой проблемы.

9 голосов
/ 11 марта 2011

Несколько вещей, которые я бы изменил в проверке:

  1. Выполните проверки наличия, уникальности и формата в отдельных проверках. (Ваш ключ уникальности в хэше атрибутов, который вы передаете "validates", перезаписывается в вашей проверке). Я хотел бы, чтобы это выглядело больше как:

    validates_uniqueness_of: электронная почта,: scope =>: местоположение

    validates_presence_of: электронная почта

    validates_format_of: email,: with => RFC_822 # Мы используем глобальные регулярные выражения проверки

  2. Проверки - это уровень приложения, одна из причин, по которой вы должны их разделить, заключается в том, что проверки наличия и формата можно выполнять, не касаясь базы данных. Проверка уникальности коснется базы данных, но не будет использовать уникальный индекс, который вы настроили. Проверки уровня приложения не взаимодействуют с внутренними компонентами базы данных, они генерируют SQL и на основании результатов запроса определяют достоверность. Вы можете оставить validates_uniqueness_of, но будьте готовы к условиям гонки в вашей заявке.

Поскольку проверка выполняется на уровне приложения, она будет запрашивать строку (что-то вроде "SELECT * FROM subscription WHERE email = 'email_address' LIMIT 1" ), если строка возвращается, проверка завершается неудачно. Если строка не возвращается, она считается действительной.

Однако, если в то же самое время кто-то другой регистрируется с тем же адресом электронной почты, и они оба не возвращают строку перед созданием нового, то вторая фиксация «сохранения» вызовет ограничение индекса базы данных уникальности, не вызывая проверку в приложении. (Поскольку, скорее всего, они работают на разных серверах приложений или, по крайней мере, на разных виртуальных машинах или процессах).

ActiveRecord :: RecordInvalid возникает при сбое проверки, а не при нарушении ограничения уникального индекса для базы данных. (Существует несколько уровней исключений ActiveRecord, которые могут запускаться в разные моменты жизненного цикла запроса / ответа)

RecordInvalid вызывается на первом уровне (уровень приложения), тогда как RecordNotUnique может быть вызвано после попытки отправки, и сервер базы данных определяет, что транзакция не соответствует ограничению индекса. ( ActiveRecord :: StatementInvalid является родителем исключения после выборки, которое будет сгенерировано в этом экземпляре, и вы должны его спасти, если вы действительно пытаетесь получить обратную связь с базой данных, а не проверку уровня приложения)

Если вы находитесь в вашем контроллере "rescue_from" (как обрисовано в общих чертах у The Who), должно нормально работать для восстановления после этих различных типов ошибок, и похоже, что первоначальное намерение было обрабатывать их по-разному, Вы можете сделать это с помощью нескольких "rescue_from" вызовов.

9 голосов
/ 13 февраля 2011

Вы хотите использовать rescue_from

В вашем контроллере

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

 ....

 protected

 def my_rescue_method
   ...
 end

Однако, не хотите ли вы сделать запись недействительной, а не выдавать исключение?

5 голосов
/ 26 августа 2015

Добавление к ответу Chirantans, с Rails 5 (или 3/4, с этим Backport ) вы также можете использовать новый errors.details:

begin
  @subscription.save!
rescue ActiveRecord::RecordInvalid => e
  e.record.errors.details
  # => {"email":[{"error":"taken","value":"user@example.org"}]}
end

Это очень удобно для различения различных типов RecordInvalid и не требует использования сообщения об ошибке исключения.

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

Например, вы можете проверить, являются ли все ошибки валидации для атрибута модели просто ошибками уникальности:

exception.record.errors.details.all? do |hash_element|
  error_details = hash_element[1]  
  error_details.all? { |detail| detail[:error] == :taken }
end
2 голосов
/ 15 января 2016

Этот драгоценный камень спасает сбой ограничения на уровне модели и добавляет ошибку модели (model.errors), чтобы он вел себя как другие ошибки валидации.Наслаждайтесь!https://github.com/reverbdotcom/rescue-unique-constraint

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