sintaxis recursivos recursividad recursivas programacion informatica funciones estructura ejemplos datos casos arbol ruby-on-rails

ruby on rails - recursivos - Usar la devolución de llamada after_save para modificar el mismo objeto sin activar de nuevo la devolución de llamada(recursión)



recursividad programacion (13)

¿Podría usar la devolución de llamada before_save en su lugar?

Si agrego una devolución de llamada after_save a un modelo ActiveRecord, y en esa devolución de llamada utilizo update_attribute para cambiar el objeto, se llama de nuevo a la devolución de llamada, y entonces ocurre un ''desbordamiento de pila'' (jeje, no pude resistir).

¿Es posible evitar este comportamiento, tal vez deshabilitar la devolución de llamada durante su ejecución? ¿O hay otro enfoque?

¡Gracias!


Este código ni siquiera intenta resolver problemas de subprocesamiento o concurrencia, al igual que Rails propiamente dicho. Si necesita esa característica, ¡preste atención!

Básicamente, la idea es mantener un conteo en el nivel de las llamadas recursivas de "guardar", y solo permitir after_save cuando salga del nivel superior. También querrá agregar el manejo de excepciones.

def before_save @attempted_save_level ||= 0 @attempted_save_level += 1 end def after_save if (@attempted_save_level == 1) #fill in logic here save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action #fill in logic here end @attempted_save_level -= 1 # reset the "prevent infinite recursion" flag end


Gracias chicos, el problema es que actualizo otros objetos también (hermanos si se quiere) ... se olvidó de mencionar esa parte ...

Entonces before_save está fuera de la cuestión, porque si el guardado falla todas las modificaciones a los otros objetos tendrían que ser revertidas y eso podría volverse desordenado :)


Si usa before_save, puede modificar cualquier parámetro adicional antes de que se complete el guardado, lo que significa que no tendrá que llamar explícitamente a guardar.


Una solución consiste en establecer una variable en la clase y verificar su valor en after_save.

  1. Verifíquelo primero. (si var)
  2. Asignarlo a un valor ''falso'' antes de llamar a update_attribute.
  3. llamar a update_attribute.
  4. Asignarlo a un valor "verdadero".
  5. fin

De esta forma, solo intentará guardar dos veces. Es probable que esto afecte a su base de datos dos veces, lo que puede o no ser deseable.

Tengo la vaga sensación de que hay algo integrado, pero esta es una forma bastante infalible de evitar un punto específico de recursión en casi cualquier aplicación. También recomendaría mirar el código nuevamente, ya que es probable que lo que esté haciendo en after_save se haga antes de save. Hay momentos en que esto no es cierto, pero son bastante raros.


Vea cómo se implementa update_attribute . Use el método de envío en su lugar:

send(name.to_s + ''='', value)


También puedes mirar el plugin Without_callbacks . Agrega un método a AR que le permite saltarse ciertas devoluciones de llamada para un bloque determinado. Ejemplo:

def your_after_save_func YourModel.without_callbacks(:your_after_save_func) do Your updates/changes end end


También tuve este problema. Necesito guardar un atributo que depende de la identificación del objeto. Lo resolví mediante el uso de invocación condicional para la devolución de llamada ...

Class Foo << ActiveRecord::Base after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs def init_bar_attr self.bar_attr = "my id is: #{self.id}" # careful now, let''s save only if we''re sure the triggering condition will fail self.save if bar_attr end


A veces esto se debe a que no se especifica attr_accessible en los modelos. Cuando update_attribute desea editar los atributos, si descubre que no son accesibles y crea nuevos objetos en su lugar. Al guardar los nuevos objetos, entrará en un ciclo interminable.


Tuve la necesidad de gsub los nombres de ruta en un bloque de texto cuando su registro se copió en un contexto diferente:

attr_accessor :original_public_path after_save :replace_public_path, :if => :original_public_path private def replace_public_path self.overview = overview.gsub(original_public_path, public_path) self.original_public_path = nil save end

La clave para detener la recursión fue asignar el valor del atributo y luego establecer el atributo en nil para que la condición :if no se cumpla en el siguiente guardado.



Puede usar after_save en asociación con if siguiente manera:

after_save :after_save_callback, if: Proc.new { //your logic when to call the callback }

o

after_save :after_save_callback, if: :call_if_condition def call_if_condition //condition for when to call the :after_save_callback method end

call_if_condition es un método. Defina el escenario en el que se llama al after_save_callback en ese método


No vi esta respuesta, así que pensé en agregarla en caso de que ayudara a cualquiera que busque este tema. (La sugerencia de ScottD''s sin_callbacks está cerca).

ActiveRecord proporciona update_without_callbacks para esta situación, pero es un método privado. Use enviar para tener acceso a él de todos modos. Estar en una devolución de llamada para el objeto que está guardando es exactamente la razón para usar esto.

También hay otro hilo SO aquí que cubre esto bastante bien: ¿Cómo puedo evitar ejecutar callbacks de ActiveRecord?