Manejo de excepciones de registro único en un controlador

Tengo un modelo llamado Suscripción que tiene un índice único en los campos [: correo electrónico,: ubicación]. Esto significa que una dirección de correo electrónico puede suscribirse por ubicación.

En mi modelo:

class Subscription  true, :uniqueness => true, :email_format => true, :uniqueness => {:scope => :location} end 

En mi método de creación. Quiero manejar la excepción ActiveRecord::RecordNotUnique diferente a un error regular. ¿Cómo agregaría eso a este método genérico de creación?

  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 

No creo que haya una manera de lanzar una excepción solo por un solo tipo de error de validación. ¡O puedes hacer una save! lo que generaría excepciones para todos los errores de guardado (incluidos todos los errores de validación) y los manejaría por separado.

Lo que puede hacer es manejar la excepción ActiveRecord::RecordInvalid y hacer coincidir el mensaje de excepción con el error de Validation failed: Email has already been taken y luego manejarlo por separado. Pero esto también significa que usted tendría que manejar otros errores también.

Algo como,

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

No estoy seguro si esta es la única solución a esto sin embargo.

Usted querrá usar rescue_from

En tu mando

  rescue_from ActiveRecord::RecordNotUnique, :with => :my_rescue_method .... protected def my_rescue_method ... end 

Sin embargo, ¿no querría invalidar su registro en lugar de lanzar una excepción?

Un par de cosas que cambiaría sobre la validación:

  1. Realice las validaciones de presencia, exclusividad y formato en validaciones separadas. (Su clave de unicidad en el hash de atributos que está pasando a “valida” se está sobrescribiendo en su validación). Lo haría ver más como

    validates_uniqueness_of: email,: scope =>: location

    validates_presence_of: email

    validates_format_of: email,: with => RFC_822 # Usamos expresiones regulares de validación global

  2. Las validaciones son de nivel de aplicación, una de las razones por las que debe separarlas es que las validaciones de presencia y formato pueden realizarse sin tocar la base de datos. La validación de singularidad tocará la base de datos, pero no utilizará el índice único que configuró. Las validaciones a nivel de la aplicación no interactúan con las bases de datos internas que generan SQL y, según los resultados de la consulta, determinan la validez. Puede dejar validates_uniqueness_of pero estar preparado para las condiciones de carrera en su aplicación.

Dado que la validación es a nivel de aplicación, solicitará la fila (algo como “SELECCIONAR * DE las suscripciones DÓNDE correo electrónico = ‘dirección_de_mail’ ‘LÍMITE 1” ), si se devuelve una fila, la validación falla. Si no se devuelve una fila, se considera válida.

Sin embargo, si al mismo tiempo alguien más se registra con la misma dirección de correo electrónico y ambos no devuelven una fila antes de crear una nueva, la segunda confirmación de “guardado” activará la restricción del índice de la base de datos sin activar la validación en la aplicación. . (Dado que lo más probable es que se estén ejecutando en diferentes servidores de aplicaciones o al menos diferentes VM o procesos).

ActiveRecord :: RecordInvalid se genera cuando falla la validación, no cuando se viola la restricción de índice única en la base de datos. (Hay varios niveles de excepciones de ActiveRecord que pueden activarse en diferentes puntos del ciclo de vida de la solicitud / respuesta)

RecordInvalid se genera en el primer nivel (nivel de aplicación), mientras que RecordNotUnique se puede generar después de intentar el envío y el servidor de la base de datos determina que la transacción no cumple con la restricción del índice. ( ActiveRecord :: StatementInvalid es el elemento principal de la excepción de recuperación tras publicación que se generará en esta instancia y debe rescatarla si en realidad está intentando obtener los comentarios de la base de datos y no la validación del nivel de la aplicación)

Si está en su controlador, “rescue_from” (como lo describe The Who) debería funcionar bien para recuperarse de estos diferentes tipos de errores y parece que la intención inicial fue manejarlos de manera diferente para que pueda hacerlo con múltiples “rescue_from” llamadas

Agregando a la respuesta de los chirantanes, con Rails 5 (o 3/4, con este Backport ) también puede usar los nuevos errors.details . errors.details :

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

Lo cual es muy útil para diferenciar entre los diferentes tipos de RecordInvalid y no requiere confiar en el mensaje de error de las excepciones.

Tenga en cuenta que incluye todos los errores informados por el proceso de validación, lo que hace que el manejo de múltiples errores de validación de unicidad sea mucho más fácil.

Por ejemplo, puede verificar si todos los errores de validación para un atributo de modelo son solo errores de exclusividad:

 exception.record.errors.details.all? do |hash_element| error_details = hash_element[1] error_details.all? { |detail| detail[:error] == :taken } end 

Esta gem rescata la falla de restricción en el nivel del modelo y agrega un error de modelo (model.errors) para que se comporte como otras fallas de validación. ¡Disfrutar! https://github.com/reverbdotcom/rescue-unique-constraint