¿Cómo puedo evitar ejecutar devoluciones de llamada de ActiveRecord?


Tengo algunos modelos que tienen retrollamadas after_save. Por lo general, eso está bien, pero en algunas situaciones, como cuando se crean datos de desarrollo, quiero guardar los modelos sin tener que ejecutar las devoluciones de llamada. ¿Hay una forma sencilla de hacerlo? Algo parecido...

Person#save( :run_callbacks => false )

O

Person#save_without_callbacks

Busqué en los documentos de Rails y no encontré nada. Sin embargo, en mi experiencia los documentos de Rails no siempre cuentan toda la historia.

ACTUALIZAR

Encontré una entrada de blog que explica cómo puede eliminar las 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.

Author: user2262149, 2009-03-11

25 answers

Esta solución es Rails 2 solamente.

Acabo de investigar esto y creo que tengo una solución. Hay dos métodos privados de ActiveRecord que puedes usar:

update_without_callbacks
create_without_callbacks

Vas a tener que usar send 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 solo querrás usar en la consola o mientras haces algunas pruebas aleatorias. Espero que esto ayude!

 69
Author: efalcao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-03 04:01:55

Use 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: Omitir devoluciones de llamada que también funciona mientras se crea 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
 213
Author: Vikrant Chaudhary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-20 04:22:56

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 

Enfoque 1:

User.send(:create_without_callbacks)
User.send(:update_without_callbacks)

Bookshare 2: Cuando desee omitirlos en sus archivos rspec o lo que sea, pruebe esto:

User.skip_callback(:save, :after, :generate_nick_name)
User.create!()

NOTA: una vez hecho esto, si no está en el entorno rspec, debe reiniciar las devoluciones de llamada:

User.set_callback(:save, :after, :generate_nick_name)

Funciona bien para mí en rails 3.0.5

 26
Author: Siwei Shen申思维,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:15

Carriles 3:

MyModel.send("_#{symbol}_callbacks") # list  
MyModel.reset_callbacks symbol # reset
 19
Author: guai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-26 08:03:40

Puedes probar algo como esto en tu modelo de Persona:

after_save :something_cool, :unless => :skip_callbacks

def skip_callbacks
  ENV[RAILS_ENV] == 'development' # or something more complicated
end

EDIT: after_save no es un símbolo, pero es al menos la 1.000 veces que he intentado hacerlo.

 15
Author: Sarah Mei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-27 14:32:24

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 SIN PROCESAR o cambiar el código de salida de cualquier manera, considere usar un "objeto de sombra" que apunte a su tabla de bd existente. Así:

class ImportedPerson < ActiveRecord::Base
  self.table_name = 'people'
end

Esto funciona con todas las versiones de Rails, es threadsafe, y elimina completamente todas las validaciones y devoluciones de llamada sin modificaciones a su código existente. Puedes solo tienes que tirar esa declaración de clase justo antes de su importación real, y usted debe ser bueno para ir. Solo recuerda usar tu nueva clase para insertar el objeto, como:

ImportedPerson.new( person_attributes )
 12
Author: Brad Werth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-09 22:52:14

Puedes usar update_columns:

User.first.update_columns({:name => "sebastian", :age => 25})

Actualiza los atributos dados de un objeto, sin llamar a save, por lo tanto omitiendo validaciones y devoluciones de llamada.

 8
Author: Luís Ramalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-01 18:51:05

La única manera de evitar todas las devoluciones de llamada after_save es hacer que la primera devuelva false.

Tal vez usted 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
 5
Author: rfunduk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-21 13:41:53

Parece una forma de manejar esto en Rails 2.3 (ya que update_without_callbacks se ha ido, etc.), sería utilizar update_all, que es uno de los métodos que omite callbacks según sección 12 de la Guía Rails para validaciones y callbacks.

También, tenga en cuenta que si está haciendo algo en su devolución de llamada after_, que hace un cálculo basado en muchas asociaciones (es decir, una has_many assoc, donde también accepts_nested_attributes_for), tendrá que volver a cargar el asociación, en caso de que como parte de la guardar, uno de sus miembros fue eliminado.

 5
Author: chrisrbailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-24 07:39:29

Https://gist.github.com/576546

Simplemente vuelca este monkey-patch en config/initializers/skip_callbacks.rb

Entonces

Project.skip_callbacks { @project.save }

O similares.

Todo el crédito al autor

 4
Author: fringd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-24 23:26:42

La respuesta más up-voted puede parecer confusa en algunos casos.

Puede usar una simple if verificación si desea omitir una devolución de llamada, como esta:

after_save :set_title, if: -> { !new_record? && self.name_changed? }
 3
Author: Aleks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-23 09:57:23

Una solución que debería funcionar en todas las versiones de Rails sin el uso de una gema o plugin es simplemente emitir instrucciones de actualización directamente. eg

ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"

Esto puede (o no) ser una opción dependiendo de lo compleja que sea su actualización. Esto funciona bien para, por ejemplo, actualizar banderas en un registro de dentro una devolución de llamada after_save (sin volver a activar la devolución de llamada).

 2
Author: Dave Smylie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-08 18:02:40
# 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
 1
Author: Sasha Alexandrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-17 07:21:17

Ninguno de estos puntos a without_callbacks plugin que solo 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

 1
Author: kares,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-01 19:27:15

Escribí un plugin 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 plugin puede ayudar.

 1
Author: Donald ball,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-13 14:59:38

Si está utilizando Rails 2. Puede usar SQL query 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.

 1
Author: oivoodoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-31 11:31:13

Cuando necesito control total sobre la devolución de llamada, creo otro atributo que se usa como un conmutador. Simple y eficaz:

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
 1
Author: tothemario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-14 08:39:12

Para crear datos de prueba en Rails utiliza este hack:

record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call

Https://coderwall.com/p/y3yp2q/edit

 1
Author: Wojtek Kruszewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-29 16:15:54

Puedes usar la gema sneaky-save: https://rubygems.org/gems/sneaky-save .

Tenga en cuenta que esto no puede ayudar a guardar asociaciones sin validaciones. Arroja el error 'created_at cannot be null' ya que inserta directamente la consulta sql a diferencia de un modelo. Para implementar esto, necesitamos actualizar todas las columnas generadas automáticamente de la base de datos.

 1
Author: Zinin Serge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-14 13:16:17

Necesitaba una solución para Rails 4, así que se me ocurrió esto:

App/models/concerns/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)
 1
Author: Steve Friedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-12 15:05:02

¿Por qué querrías ser capaz de hacer esto en desarrollo? Seguramente esto significará que está construyendo 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 base de datos de desarrollo con datos, un mejor enfoque sería construir una tarea de rake que use la gema faker para construir datos válidos e importarlos a la base de datos creando tantos o pocos registros como desee, pero si está inclinado hacia ella y tiene una buena razón, supongo ese update_without_callbacks y create_without_callbacks funcionarán bien, pero cuando estés tratando de doblar los rieles a tu voluntad, pregúntate que tienes una buena razón y si lo que estás haciendo es realmente una buena idea.

 0
Author: nitecoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-11 09:18:13

Una opción es tener un modelo separado para tales manipulaciones, utilizando 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

 0
Author: Stephan Wehner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-06 19:00:06

Otra forma sería usar hooks de validación en lugar de callbacks. 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 el do_something por defecto, pero puede reemplazarlo fácilmente con:

@person = Person.new
@person.save(false)
 0
Author: BoosterStage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-05 14:50:24

Algo que debería funcionar con todas las versiones de ActiveRecord sin depender de opciones o métodos activerecord que puedan o no existir.

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: utilice un "modelo de activerecord diferente" sobre la misma tabla

 0
Author: choonkeat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-07 05:17:11

No es la forma más limpia, pero podría envolver el código de devolución de llamada en una condición que verifique el entorno Rails.

if Rails.env == 'production'
  ...
 -3
Author: james,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-10 23:59:26