¿Cómo puedo evitar ejecutar devoluciones de llamada ActiveRecord?
-
08-07-2019 - |
Pregunta
Tengo algunos modelos que tienen devoluciones de llamada after_save. Por lo general, está bien, pero en algunas situaciones, como al crear datos de desarrollo, quiero guardar los modelos sin que se ejecuten las devoluciones de llamada. ¿Hay una manera simple de hacer eso? Algo parecido a ...
Person#save( :run_callbacks => false )
o
Person#save_without_callbacks
Miré en los documentos de Rails y no encontré nada. Sin embargo, en mi experiencia, los documentos de Rails no siempre cuentan toda la historia.
ACTUALIZACIÓN
Encontré una publicación de blog que explica cómo puede eliminar devoluciones de llamada de un modelo como este:
Foo.after_save.clear
No pude encontrar dónde está documentado ese método, pero parece funcionar.
Solución
Esta solución es solo Rails 2.
Acabo de investigar esto y creo que tengo una solución. Hay dos métodos privados de ActiveRecord que puede usar:
update_without_callbacks
create_without_callbacks
Tendrá que usar enviar para llamar a estos métodos. ejemplos:
p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)
p = Person.find(1)
p.send(:update_without_callbacks)
Esto es definitivamente algo que realmente solo querrás usar en la consola o mientras haces algunas pruebas aleatorias. ¡Espero que esto ayude!
Otros consejos
Utilice update_column
(Rails > = v3.1) o update_columns
(Rails > = 4.0) para omitir devoluciones de llamada y validaciones. También con estos métodos, updated_at
es no actualizado.
#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)
http://api.rubyonrails.org/classes/ ActiveRecord / Persistence.html # method-i-update_column
# 2: omitiendo las devoluciones de llamada que también funcionan al crear un objeto
class Person < ActiveRecord::Base
attr_accessor :skip_some_callbacks
before_validation :do_something
after_validation :do_something_else
skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
end
person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
Actualizado:
La solución de @Vikrant Chaudhary parece mejor:
#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)
Mi respuesta original:
vea este enlace: ¿Cómo omitir las devoluciones de llamada de ActiveRecord?
en Rails3,
supongamos que tenemos una definición de clase:
class User < ActiveRecord::Base
after_save :generate_nick_name
end
Enfoque1:
User.send(:create_without_callbacks)
User.send(:update_without_callbacks)
Enfoque2: Cuando desee omitirlos en sus archivos rspec o lo que sea, intente esto:
User.skip_callback(:save, :after, :generate_nick_name)
User.create!()
NOTA: una vez hecho esto, si no está en el entorno rspec, debe restablecer las devoluciones de llamada:
User.set_callback(:save, :after, :generate_nick_name)
funciona bien para mí en rails 3.0.5
rieles 3:
MyModel.send("_#{symbol}_callbacks") # list
MyModel.reset_callbacks symbol # reset
Puede intentar algo como esto en su modelo de Persona:
after_save :something_cool, :unless => :skip_callbacks
def skip_callbacks
ENV[RAILS_ENV] == 'development' # or something more complicated
end
EDITAR: after_save no es un símbolo, pero esa es al menos la milésima vez que he intentado hacerlo uno.
Si el objetivo es simplemente insertar un registro sin devoluciones de llamada o validaciones, y le gustaría hacerlo sin recurrir a gemas adicionales, agregar comprobaciones condicionales, usar SQL RAW o inutilizar su código de salida de alguna manera, considere usar un " objeto de sombra " apuntando a su tabla db existente. Me gusta así:
class ImportedPerson < ActiveRecord::Base
self.table_name = 'people'
end
Esto funciona con todas las versiones de Rails, es seguro para subprocesos y elimina por completo todas las validaciones y devoluciones de llamadas sin modificaciones en su código existente. Simplemente puede incluir esa declaración de clase justo antes de su importación real, y debería estar listo para comenzar. Solo recuerde usar su nueva clase para insertar el objeto, como:
ImportedPerson.new( person_attributes )
Puede usar update_columns
:
User.first.update_columns({:name => "sebastian", :age => 25})
Actualiza los atributos dados de un objeto, sin llamar a guardar, por lo tanto, omitiendo validaciones y devoluciones de llamada.
La única forma de evitar todas las devoluciones de llamada after_save es hacer que la primera devuelva false.
Quizás podría intentar algo como (no probado):
class MyModel < ActiveRecord::Base
attr_accessor :skip_after_save
def after_save
return false if @skip_after_save
... blah blah ...
end
end
...
m = MyModel.new # ... etc etc
m.skip_after_save = true
m.save
Parece una forma de manejar esto en Rails 2.3 (ya que update_without_callbacks ha desaparecido, etc.), sería usar update_all, que es uno de los métodos que omite las devoluciones de llamada según sección 12 de la Guía de validaciones y devoluciones de llamada de Rails .
Además, tenga en cuenta que si está haciendo algo en su devolución de llamada after_, eso hace un cálculo basado en muchas asociaciones (es decir, un has_many assoc, donde también acepta "atributos_denominados"), deberá volver a cargar la asociación, en caso de que sea parte del guardado, uno de sus miembros fue eliminado.
https://gist.github.com/576546
simplemente descargue este parche de mono en config / initializers / skip_callbacks.rb
luego
o similar.
todo crédito al autor
Una solución que debería funcionar en todas las versiones de Rails sin el uso de una gema o complemento es simplemente emitir declaraciones de actualización directamente. por ejemplo
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
Esto puede (o no) ser una opción dependiendo de cuán compleja sea su actualización. Esto funciona bien, por ejemplo, para actualizar banderas en un registro desde within una devolución de llamada after_save (sin volver a activar la devolución de llamada).
La respuesta más más votada
puede parecer confusa en algunos casos.
Puede usar solo un simple if
para verificar si desea omitir una devolución de llamada, como esta:
after_save :set_title, if: -> { !new_record? && self.name_changed? }
# for rails 3
if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
def update_without_callbacks
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return false if attributes_with_values.empty?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
end
end
Ninguno de estos puntos al complemento without_callbacks
que simplemente hace lo que necesita ...
class MyModel < ActiveRecord::Base
before_save :do_something_before_save
def after_save
raise RuntimeError, "after_save called"
end
def do_something_before_save
raise RuntimeError, "do_something_before_save called"
end
end
o = MyModel.new
MyModel.without_callbacks(:before_save, :after_save) do
o.save # no exceptions raised
end
http://github.com/cjbottaro/without_callbacks funciona con Rails 2.x
Escribí un complemento que implementa update_without_callbacks en Rails 3:
http://github.com/dball/skip_activerecord_callbacks
La solución correcta, creo, es reescribir sus modelos para evitar devoluciones de llamada en primer lugar, pero si eso no es práctico en el corto plazo, este complemento puede ayudar.
Si está utilizando Rails 2. Puede usar la consulta SQL para actualizar su columna sin ejecutar devoluciones de llamada y validaciones.
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
Creo que debería funcionar en cualquier versión de rails.
Cuando necesito un control total sobre la devolución de llamada, creo otro atributo que se usa como un interruptor. Simple y efectivo:
Modelo:
class MyModel < ActiveRecord::Base
before_save :do_stuff, unless: :skip_do_stuff_callback
attr_accessor :skip_do_stuff_callback
def do_stuff
puts 'do stuff callback'
end
end
Prueba:
m = MyModel.new()
# Fire callbacks
m.save
# Without firing callbacks
m.skip_do_stuff_callback = true
m.save
# Fire callbacks again
m.skip_do_stuff_callback = false
m.save
Para crear datos de prueba en Rails, usa este truco:
record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
Puede usar la gema sneaky-save: https://rubygems.org/gems/sneaky-save .
Tenga en cuenta que esto no puede ayudar a guardar asociaciones sin validaciones. Lanza el error 'created_at no puede ser nulo' ya que inserta directamente la consulta sql a diferencia de un modelo. Para implementar esto, necesitamos actualizar todas las columnas de db generadas automáticamente.
Necesitaba una solución para Rails 4, así que se me ocurrió esto:
aplicación / modelos / preocupaciones / save_without_callbacks.rb
module SaveWithoutCallbacks
def self.included(base)
base.const_set(:WithoutCallbacks,
Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
)
end
def save_without_callbacks
new_record? ? create_without_callbacks : update_without_callbacks
end
def create_without_callbacks
plain_model = self.class.const_get(:WithoutCallbacks)
plain_record = plain_model.create(self.attributes)
self.id = plain_record.id
self.created_at = Time.zone.now
self.updated_at = Time.zone.now
@new_record = false
true
end
def update_without_callbacks
update_attributes = attributes.except(self.class.primary_key)
update_attributes['created_at'] = Time.zone.now
update_attributes['updated_at'] = Time.zone.now
update_columns update_attributes
end
end
en cualquier modelo:
include SaveWithoutCallbacks
entonces puedes:
record.save_without_callbacks
o
Model::WithoutCallbacks.create(attributes)
¿Por qué querrías poder hacer esto en desarrollo? Seguramente esto significará que está creando su aplicación con datos no válidos y, como tal, se comportará de manera extraña y no como espera en la producción.
Si desea llenar su dev db con datos, un mejor enfoque sería construir una tarea de rastrillo que utilizara la gema falsa para construir datos válidos e importarlos en el db creando tantos o pocos registros como desee, pero si está decidido a hacerlo y tiene una buena razón, supongo que update_without_callbacks y create_without_callbacks funcionarán bien, pero cuando intente doblar rieles a su voluntad, pregúntese que tiene una buena razón y si lo que está haciendo es realmente bueno idea.
Una opción es tener un modelo separado para tales manipulaciones, usando la misma tabla:
class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'
include CommonModelMethods # if there are
:
:
end
(El mismo enfoque podría facilitar las cosas para evitar las validaciones)
Stephan
Otra forma sería usar ganchos de validación en lugar de devoluciones de llamada. Por ejemplo:
class Person < ActiveRecord::Base
validate_on_create :do_something
def do_something
"something clever goes here"
end
end
De esa manera puede obtener do_something de forma predeterminada, pero puede anularlo fácilmente con:
@person = Person.new
@person.save(false)
Algo que debería funcionar con todas las versiones de ActiveRecord
sin depender de las opciones o métodos de registro activo que puedan existir o no.
module PlainModel
def self.included(base)
plainclass = Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
base.const_set(:Plain, plainclass)
end
end
# usage
class User < ActiveRecord::Base
include PlainModel
validates_presence_of :email
end
User.create(email: "") # fail due to validation
User::Plain.create(email: "") # success. no validation, no callbacks
user = User::Plain.find(1)
user.email = ""
user.save
TLDR: use un " modelo de registro activo diferente " sobre la misma mesa
No es la forma más limpia, pero podría ajustar el código de devolución de llamada en una condición que verifique el entorno Rails.
if Rails.env == 'production'
...