2011-02-13 13 views
9

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.Manejo de Excepciones únicas de registro en un controlador

En mi modelo:

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

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

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 

Respuesta

16

No creo que haya una forma de hacer una excepción solo por un solo tipo de falla de validación. O bien puede hacer un save! que levantaría excepciones para todos los errores de guardado (incluidos todos los errores de validación) y los gestionará por separado.

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

Algo así 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 este embargo.

+0

El 'save!' Es lo que me estaba perdiendo. Ambos funcionan, sin embargo, su solución es más completa. También hice una pequeña edición en la línea de rescate que SO dice que necesita ser revisada por pares. – Dex

+0

Cool. ¿Cuál es la edición? Actualizaré la respuesta con eso. – Chirantan

+0

debe ser 'rescue ActiveRecord :: RecordInvalid, Exception => e' – Dex

8

Usted tendrá que usar rescue_from

En su controlador

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

.... 

protected 

def my_rescue_method 
    ... 
end 

Sin embargo, no le gustaría para invalidar su registro en lugar de lanzar una excepción?

+0

Esto es si ya se encuentra en la base de datos. No es necesario invalidarlo, pero estoy abierto a mejores sugerencias. Además, parece que el error que se arroja es en realidad 'ActiveRecord :: RecordInvalid Exception: Validation failed: Email ya se ha tomado', no RecordNotUnique. – Dex

+0

Sin embargo, el código aún no parece funcionar. – Dex

+0

Como señala Dex, la excepción correcta es ActiveRecord :: RecordInvalid. –

9

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

  1. hacer en la presencia, la singularidad, y validaciones de formato en validaciones separadas. (Su clave de exclusividad en el hash de atributos que está pasando a "valida" se sobrescribe en su validación). Me gustaría hacer que se vea más como:

    validates_uniqueness_of: correo electrónico,: Ámbito =>: ubicación

    validates_presence_of: correo electrónico

    validates_format_of: correo electrónico,: con => RFC_822 # Usamos la validación global de expresiones regulares

  2. Las validaciones son Nivel de aplicación, una de las razones por las que debe separarlas es porque las validaciones de presencia y formato se pueden realizar sin tocar la base de datos. La validación de exclusividad tocará la base de datos, pero no usará el índice único que configure. Las validaciones de nivel de aplicación no interactúan con las bases de datos internas que generan SQL y, en función de los resultados de la consulta, determinan la validez. Puede dejar validates_uniqueness_of, pero esté preparado para las condiciones de carrera en su aplicación.

Desde la validación es el nivel de aplicación solicitará la fila (algo así como "SELECT * FROM suscripciones DONDE email = 'email_address' LIMIT 1"), si una fila se devuelve luego falla la validación. Si no se devuelve una fila, entonces se considera válida.

Sin embargo, si al mismo tiempo otra persona 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 "guardar" activará la restricción del índice de la base de datos sin activar el validación en la aplicación. (Dado que es muy probable que se ejecuten en diferentes servidores de aplicaciones o al menos diferentes máquinas virtuales o procesos).

ActiveRecord :: RecordInvalid se genera cuando la validación falla, no cuando se infringe la restricción de índice única en la base de datos. (Hay múltiples niveles de Excepciones ActiveRecord que se pueden activar en diferentes puntos de la solicitud de ciclo de vida/respuesta)

RecordInvalid se eleva en el (nivel de aplicación) primer nivel mientras que RecordNotUnique se puede elevar después de la presentación se intenta y el servidor de base de datos determina que la transacción no cumple con la restricción de índice. (ActiveRecord :: StatementInvalid es el padre del post buscar a excepción de que se crió en este caso y se debe rescatar que si en realidad se está tratando de obtener la información de base de datos y no la validación a nivel de aplicación)

Si están en su controlador "rescue_from" (según lo descrito por The Who) debería funcionar bien para recuperarse de estos diferentes tipos de errores y parece que el intento inicial fue manejarlos de manera diferente para que pueda hacerlo con múltiples "rescue_from " llamadas.

+0

Quiere agregar que 'ActiveRecord :: StatementInvalid' puede ocurrir también en' save'. 'ActiveModel :: RangeError' también puede suceder. Apuesto a que hay más clases de excepción. – lulalala

3

adición a la respuesta Chirantans, con los carriles 5 (o 3/4, con este Backport) también se puede utilizar el nuevo errors.details:

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

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

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

Por ejemplo, se puede comprobar si todos validación en los errores para un modelo de atributo son sólo unicidad-errors:

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