2010-10-07 10 views
5

Básicamente, lo que quiero hacer es registrar una acción en MyModel en la tabla de MyModelLog. Aquí hay un código de pseudo:¿Cómo guardar algo en la base de datos después de las validaciones fallidas de ActiveRecord?

class MyModel < ActiveRecord::Base 
    validate :something 

    def something 
    # test 
    errors.add(:data, "bug!!") 
    end 
end 

También he un modelo con este aspecto:

class MyModelLog < ActiveRecord::Base 

    def self.log_something 
    self.create(:log => "something happened") 
    end 

end 

Con el fin de iniciar la sesión intenté:

  • Añadir MyModelLog.log_something en el método de somethingMyModel

  • Llamada MyModelLog.log_something sobre la devolución de llamada de after_validationMyModel

En ambos casos, la creación se revierte cuando la validación falla porque está en la operación de validación. Por supuesto, también quiero iniciar sesión cuando las validaciones fallan. Realmente no quiero iniciar sesión en un archivo o en otro lugar que no sea la base de datos porque necesito las relaciones de las entradas de registro con otros modelos y la capacidad de hacer solicitudes.

¿Cuáles son mis opciones?

+0

Tipo similar de pregunta: http://stackoverflow.com/questions/3685912/how-do-i-exclude-a-model-from-a-transaction-in-activerecord/3686035#3686035 – Shadwell

Respuesta

0

Puede usar una transacción anidada. De esta forma, el código en su devolución de llamada se ejecuta en una transacción diferente a la validación fallida. La documentación de Rails para ActiveRecord::Transactions::ClassMethods explica cómo se hace esto.

+2

Puede que no entienda su punto, pero si hago una transacción anidada para log_something, cuando la transacción "principal" se retrotrae, el niño también se deshace – marcgg

1

No estoy seguro de si se aplica a usted, pero supongo que está tratando de guardar/crear un modelo desde su controlador. En el controlador es fácil verificar el resultado de esa acción, y lo más probable es que ya lo haga para brindar al usuario un flash útil; para que pueda registrar fácilmente un mensaje apropiado allí.

También estoy asumiendo que no utiliza ninguna transacción explícita, por lo que si lo maneja en el controlador, está fuera de la transacción (cada operación de salvar y destruir funciona en su propia transacción).

¿Qué opinas?

+0

gracias por la respuesta, pero podría terminar con muchos de esos registros en diferentes lugares así que preferiría mantenerlos en mi modelo – marcgg

+0

La ventaja de hacerlo en el controlador es que es explícito y pertenece al manejo de un error. Para evitar mucha duplicación de código, ¿podría mezclar el comportamiento, hacer algún método que registre el error y establecer el flash, por ejemplo en un comando? Por otro lado, me gusta la idea de los observadores también, aunque no estoy seguro si operan en una transacción diferente. – nathanvda

3

¿Sería esto una buena opción para un Observer? No estoy seguro, pero espero que exista fuera de la transacción ... Tengo una necesidad similar donde podría querer eliminar un registro en la actualización ...

+0

Buena idea, voy a pensar y ver si encajaría en lo que trato de hacer – marcgg

+1

Investigaciones adicionales muestran que esto puede no funcionar: incluso los observadores tienen lugar dentro de la transacción. Mira after_commit en rails 3 para ejecutar algo después del commit ... también hay un after_rollback – DGM

8

Las transacciones anidadas parecen funcionar en MySQL .

Esto es lo que he intentado una carriles recién generados (con MySQL) Proyecto:

./script/generate model Event title:string --skip-timestamps --skip-fixture 

./script/generate model EventLog error_message:text --skip-fixture 

class Event < ActiveRecord::Base                                  
    validates_presence_of :title                                   
    after_validation_on_create :log_errors                                

    def log_errors                                      
    EventLog.log_error(self) if errors.on(:title).present?                            
    end                                         
end 

class EventLog < ActiveRecord::Base                                  
    def self.log_error(event)                                    
    connection.execute('BEGIN') # If I do transaction do then it doesn't work. 
    create :error_message => event.errors.on(:title)                        
    connection.execute('COMMIT')                                  
    end                                         
end 

# And then in script/console: 
>> Event.new.save 
=> false 
>> EventLog.all 
=> [#<EventLog id: 1, error_message: "can't be blank", created_at: "2010-10-22 13:17:41", updated_at: "2010-10-22 13:17:41">] 
>> Event.all 
=> [] 

Tal vez tienen más simplificado que, o no algún momento.

+0

gracias por la respuesta. sin embargo, esto parece muy dependiente de mysql y no estoy seguro de si sería una solución viable. También quiero registrar errores dentro de los métodos de validación ya que no solo estaría registrando el contenido de los errores sino también los resultados de algunas llamadas a la API. Aún así, buen punto – marcgg

1

MyModelLog.log_algo se debe hacer usando una conexión diferente.

Puede hacer que el modelo MyModelLog use siempre una conexión diferente utilizando establish_connection.

class MyModelLog < ActiveRecord::Base 
    establish_connection Rails.env # Use different connection 

    def self.log_something 
    self.create(:log => "something happened") 
    end 
end 

¡No estoy seguro si esta es la manera correcta de hacer el registro!

2

He resuelto un problema como este aprovechando el alcance variable de Ruby. Básicamente, declare una variable error fuera de un bloque de transacción, luego capturo, almaceno el mensaje de registro y vuelvo a generar el error.

se ve algo como esto:

def something 
    error = nil 
    ActiveRecord::Base.transaction do 
     begin 
      # place codez here 
     rescue ActiveRecord::Rollback => e 
      error = e.message 
      raise ActiveRecord::Rollback 
     end 
    end 
    MyModelLog.log_something(error) unless error.nil? 
end 

Al declarar la error fuera variable del ámbito de transacción el contenido de la variable persisten incluso después de que la transacción haya salido.

Cuestiones relacionadas