Comment puis-je éviter d’exécuter des rappels ActiveRecord?

J’ai des modèles qui ont des call_save après. Habituellement, c’est bien, mais dans certaines situations, comme lors de la création de données de développement, je veux enregistrer les modèles sans avoir à exécuter les rappels. Y a-t-il un moyen simple de le faire? Quelque chose qui ressemble à …

Person#save( :run_callbacks => false ) 

ou

 Person#save_without_callbacks 

J’ai regardé dans les documents de Rails et n’ai rien trouvé. Cependant, d’après mon expérience, les documents de Rails ne racontent pas toujours toute l’histoire.

METTRE À JOUR

J’ai trouvé un article de blog qui explique comment supprimer les rappels d’un modèle comme celui-ci:

 Foo.after_save.clear 

Je n’ai pas pu trouver où cette méthode est documentée mais cela semble fonctionner.

Cette solution est Rails 2 uniquement.

Je viens d’enquêter là-dessus et je pense avoir une solution. Il existe deux méthodes privées ActiveRecord que vous pouvez utiliser:

 update_without_callbacks create_without_callbacks 

Vous allez devoir utiliser send pour appeler ces méthodes. exemples:

 p = Person.new(:name => 'foo') p.send(:create_without_callbacks) p = Person.find(1) p.send(:update_without_callbacks) 

C’est certainement quelque chose que vous ne voulez vraiment utiliser que dans la console ou lors de tests aléatoires. J’espère que cela t’aides!

Utilisez update_column (Rails> = v3.1) ou update_columns (Rails> = 4.0) pour ignorer les rappels et les validations. Aussi avec ces méthodes, updated_at n’est pas mis à jour.

 #Rails >= v3.1 only @person.update_column(:some_atsortingbute, 'value') #Rails >= v4.0 only @person.update_columns(atsortingbutes) 

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

# 2: Ignorer les rappels qui fonctionnent également lors de la création d’un object

 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 

Actualisé:

La solution de @Vikrant Chaudhary semble meilleure:

 #Rails >= v3.1 only @person.update_column(:some_atsortingbute, 'value') #Rails >= v4.0 only @person.update_columns(atsortingbutes) 

Ma réponse originale:

voir ce lien: Comment ignorer les rappels ActiveRecord?

dans Rails3,

supposons que nous ayons une définition de classe:

 class User < ActiveRecord::Base after_save :generate_nick_name end 

Approche1:

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

Approach2: Lorsque vous voulez les ignorer dans vos fichiers rspec ou autres, essayez ceci:

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

REMARQUE: une fois cela fait, si vous n'êtes pas dans l'environnement rspec, vous devez réinitialiser les rappels:

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

fonctionne bien pour moi sur les rails 3.0.5

rails 3:

 MyModel.send("_#{symbol}_callbacks") # list MyModel.reset_callbacks symbol # reset 

Vous pourriez essayer quelque chose comme ça dans votre modèle Person:

 after_save :something_cool, :unless => :skip_callbacks def skip_callbacks ENV[RAILS_ENV] == 'development' # or something more complicated end 

EDIT: after_save n’est pas un symbole, mais c’est au moins la 1000ème fois que j’ai essayé d’en faire un.

Si le but est d’insérer simplement un enregistrement sans callbacks ou validations, et que vous souhaitez le faire sans avoir recours à des gems supplémentaires, en ajoutant des vérifications conditionnelles, en utilisant RAW SQL ou en fouillant avec votre code existant, envisagez d’utiliser un “shadow” object “pointant vers votre table de firebase database existante. Ainsi:

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

Cela fonctionne avec chaque version de Rails, est threadsafe, et élimine complètement toutes les validations et rappels sans modification de votre code existant. Vous pouvez simplement lancer cette déclaration de classe juste avant votre importation réelle, et vous devriez être prêt à partir. Rappelez-vous juste d'utiliser votre nouvelle classe pour insérer l'object, comme:

 ImportedPerson.new( person_atsortingbutes ) 

Vous pouvez utiliser update_columns :

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

Met à jour les atsortingbuts donnés d’un object, sans appeler save, ignorant ainsi les validations et les rappels.

La seule façon d’empêcher tous les rappels après_save est de faire en sorte que le premier retourne faux.

Peut-être que vous pourriez essayer quelque chose comme (non testé):

 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 

On dirait qu’une façon de gérer cela dans Rails 2.3 (puisque la méthode update_without_callbacks a disparu, etc.) serait d’utiliser update_all, une des méthodes qui ignore les rappels conformément à la section 12 du Guide Rails .

De plus, notez que si vous faites quelque chose dans votre callback after_, qui effectue un calcul basé sur de nombreuses associations (c.-à-d. Une association has_many, ou accept_nested_atsortingbutes_for), vous devrez recharger l’association, dans le cadre de la sauvegarde. , un de ses membres a été supprimé.

https://gist.github.com/576546

il suffit de transférer ce patch de singe dans config / initializers / skip_callbacks.rb

puis

Project.skip_callbacks { @project.save }

ou semblable.

tout crédit à l’auteur

La réponse la plus up-voted pourrait sembler déroutante dans certains cas.

Vous pouvez utiliser une simple vérification si vous souhaitez ignorer un rappel, comme ceci:

 after_save :set_title, if: -> { !new_record? && self.name_changed? } 

Une solution qui devrait fonctionner sur toutes les versions de Rails sans utiliser de gem ou de plug-in consiste simplement à émettre directement des instructions de mise à jour. par exemple

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

Cela peut (ou non) être une option en fonction de la complexité de votre mise à jour. Cela fonctionne bien pour, par exemple, la mise à jour des indicateurs sur un enregistrement depuis un rappel after_save (sans que le rappel ne soit déclenché).

 # for rails 3 if !ActiveRecord::Base.private_method_defined? :update_without_callbacks def update_without_callbacks atsortingbutes_with_values = arel_atsortingbutes_values(false, false, atsortingbute_names) return false if atsortingbutes_with_values.empty? self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(atsortingbutes_with_values) end end 

Aucun de ces points ne signifie que le plug-in without_callbacks fait exactement ce dont vous avez besoin …

 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 fonctionne avec Rails 2.x

J’ai écrit un plugin qui implémente update_without_callbacks dans Rails 3:

http://github.com/dball/skip_activerecord_callbacks

Je pense que la bonne solution consiste à réécrire vos modèles pour éviter les rappels, mais si cela n’est pas pratique à court terme, ce plug-in peut vous aider.

Si vous utilisez Rails 2. Vous pouvez utiliser la requête SQL pour mettre à jour votre colonne sans exécuter les rappels et les validations.

 YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}") 

Je pense que cela devrait fonctionner dans toutes les versions de rails.

Lorsque j’ai besoin d’un contrôle total sur le rappel, je crée un autre atsortingbut utilisé comme commutateur. Simple et efficace:

Modèle:

 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 

Tester:

 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 

Pour créer des données de test dans Rails, vous utilisez ce hack:

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

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

Vous pouvez utiliser sneaky-save gem: https://rubygems.org/gems/sneaky-save .

Notez que cela ne peut pas aider à enregistrer des associations sans validations. Il génère l’erreur “created_at ne peut pas être nulle” car il insère directement la requête sql contrairement à un modèle. Pour implémenter cela, nous devons mettre à jour toutes les colonnes générées automatiquement de db.

J’avais besoin d’une solution pour Rails 4, j’ai donc trouvé ceci:

app / modèles / soucis / 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.atsortingbutes) 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_atsortingbutes = atsortingbutes.except(self.class.primary_key) update_atsortingbutes['created_at'] = Time.zone.now update_atsortingbutes['updated_at'] = Time.zone.now update_columns update_atsortingbutes end end 

dans n’importe quel modèle:

 include SaveWithoutCallbacks 

Ensuite vous pouvez:

 record.save_without_callbacks 

ou

 Model::WithoutCallbacks.create(atsortingbutes) 

Pourquoi voudriez-vous pouvoir faire cela en développement? Cela signifie sûrement que vous construisez votre application avec des données non valides et que, de ce fait, elle se comportera étrangement et pas comme vous vous y attendez dans la production.

Si vous voulez remplir votre firebase database de développement avec des données, une meilleure approche consisterait à créer une tâche de rake utilisant le bijou faker pour créer des données valides et les importer dans la firebase database en créant autant d’enregistrements que vous le souhaitez. Je me suis penché dessus et j’ai une bonne raison de penser que update_without_callbacks et create_without_callbacks fonctionneraient bien, mais lorsque vous essayez de plier les rails à votre gré, demandez-vous si vous avez une bonne raison et si vous faites une bonne idée.

Une option consiste à avoir un modèle distinct pour de telles manipulations, en utilisant la même table:

 class NoCallbacksModel < ActiveRecord::Base set_table_name 'table_name_of_model_that_has_callbacks' include CommonModelMethods # if there are : : end 

(Même approche pourrait rendre les choses plus faciles pour contourner les validations)

Stephan

Une autre façon serait d’utiliser des crochets de validation au lieu de rappels. Par exemple:

 class Person < ActiveRecord::Base validate_on_create :do_something def do_something "something clever goes here" end end 

De cette façon, vous pouvez obtenir le do_something par défaut, mais vous pouvez facilement le remplacer par:

 @person = Person.new @person.save(false) 

Quelque chose qui devrait fonctionner avec toutes les versions d’ ActiveRecord sans dépendre des options ou des méthodes d’activerecord qui peuvent ou non exister.

 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: utilise un "modèle d'activationecord différent" sur la même table

Pas le moyen le plus propre, mais vous pouvez envelopper le code de rappel dans une condition qui vérifie l’environnement Rails.

 if Rails.env == 'production' ...