J'ai des modèles qui ont des rappels after_save. Généralement, cela va, mais dans certaines situations, comme lors de la création de données de développement, je souhaite enregistrer les modèles sans que les rappels ne soient exécutés. Y a-t-il un moyen simple de faire ça? Quelque chose qui ressemble à ...
Person#save( :run_callbacks => false )
ou
Person#save_without_callbacks
J'ai regardé dans la documentation de Rails et je n'ai rien trouvé. Cependant, d'après mon expérience, la documentation de Rails ne raconte pas toujours toute l'histoire.
MISE À JOUR
J'ai trouvé n article de blog qui explique comment supprimer les rappels d'un modèle comme celui-ci:
Foo.after_save.clear
Je ne pouvais pas trouver où cette méthode est documentée, mais cela semble fonctionner.
Cette solution est Rails 2 uniquement.
Je viens d'enquêter sur cela 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 définitivement quelque chose que vous ne voudrez vraiment utiliser que dans la console ou lors de tests aléatoires. J'espère que cela t'aides!
Utilisation 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
est non mis à jour.
#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: Ignorer les rappels qui fonctionnent également lors de la création d'un objet
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
Mis à jour:
La solution de @Vikrant Chaudhary semble meilleure:
#Rails >= v3.1 only
@person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
@person.update_columns(attributes)
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)
Approche2: Lorsque vous voulez les ignorer dans vos fichiers rspec ou autre, essayez ceci:
User.skip_callback(:save, :after, :generate_nick_name)
User.create!()
NOTE: une fois ceci fait, si vous n'êtes pas dans l'environnement rspec, vous devez réinitialiser les callbacks:
User.set_callback(:save, :after, :generate_nick_name)
fonctionne bien pour moi sur Rails 3.0.5
Rails 3:
MyModel.send("_#{symbol}_callbacks") # list
MyModel.reset_callbacks symbol # reset
Si l'objectif est simplement d'insérer un enregistrement sans rappel ni validation, et que vous souhaitiez le faire sans recourir à des gemmes supplémentaires, en ajoutant des contrôles conditionnels, en utilisant RAW SQL ou en utilisant votre code existant de quelque manière que ce soit, envisagez d'utiliser une "ombre". objet "pointant vers votre table de base de données existante. Ainsi:
class ImportedPerson < ActiveRecord::Base
self.table_name = 'people'
end
Cela fonctionne avec toutes les versions de Rails, est threadsafe et élimine complètement toutes les validations et les rappels sans modification de votre code existant. Vous pouvez simplement jeter cette déclaration de classe juste avant votre importation réelle, et vous devriez être prêt à partir. N'oubliez pas d'utiliser votre nouvelle classe pour insérer l'objet, par exemple:
ImportedPerson.new( person_attributes )
Vous pouvez 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 1 000e fois que j'essaye d'en faire un.
Vous pouvez utiliser update_columns
:
User.first.update_columns({:name => "sebastian", :age => 25})
Met à jour les attributs donnés d'un objet, sans appeler de sauvegarde, ignorant ainsi les validations et les rappels.
Le seul moyen d'éviter tout rappel après_save est de renvoyer le premier à false.
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
Il semblerait qu’un moyen de gérer cela dans Rails 2.3 (puisque update_without_callbacks a disparu, etc.), consisterait à utiliser update_all, qui est l’une des méthodes permettant d’ignorer les rappels conformément à section 12 du Rails Guide des validations et des rappels .
De plus, notez que si vous faites quelque chose dans votre callback after_, cela fait un calcul basé sur plusieurs associations (ie un assoc has_many, où vous effectuez également accept_nested_attributes_for), vous devrez recharger l'association, au cas où vous en feriez sauvegarde , un de ses membres a été supprimé.
https://Gist.github.com/576546
il suffit de déposer ce patch-singe dans config/initializers/skip_callbacks.rb
puis
Project.skip_callbacks { @project.save }
ou similaire.
tout crédit à l'auteur
Une solution qui devrait fonctionner avec toutes les versions de Rails sans l'utilisation d'une gemme ou d'un plugin) consiste simplement à émettre directement des instructions de mise à jour.
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, par exemple, pour mettre à jour les indicateurs d'un enregistrement à partir de dans un rappel after_save (sans redéclencher le rappel).
Le plus up-voted
_ réponse peut paraître déroutant dans certains cas.
Vous pouvez utiliser un simple contrôle if
si vous souhaitez ignorer un rappel, comme ceci:
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
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 plugin peut vous aider.
J'avais besoin d'une solution pour Rails 4, alors je suis venu avec ceci:
app/models/concerne/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
dans n'importe quel modèle:
include SaveWithoutCallbacks
ensuite vous pouvez:
record.save_without_callbacks
ou
Model::WithoutCallbacks.create(attributes)
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
Vous pouvez utiliser un bijou de sauvegarde: https://rubygems.org/gems/sneaky-save .
Notez que cela ne peut pas aider à enregistrer des associations sans validations. Il crée l'erreur 'created_at ne peut pas être null' car il insère directement la requête SQL contrairement à un modèle. Pour implémenter cela, nous devons mettre à jour toutes les colonnes de base de données générées automatiquement.
Si vous utilisez Rails 2.), vous pouvez utiliser une requête SQL pour mettre à jour votre colonne sans exécuter de rappels ni de 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 attribut 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
Aucun de ces points à without_callbacks
plugin qui fait juste 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
Une option consiste à avoir un modèle séparé pour de telles manipulations, en utilisant le même tableau:
class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'
include CommonModelMethods # if there are
:
:
end
(Une même approche pourrait rendre les choses plus faciles pour contourner les validations)
Stephan
Une autre solution consisterait à utiliser des points d'ancrage 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 de ActiveRecord
sans dépendre d'options ou de méthodes d'activerecord qui pourraient 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: utiliser un "modèle d'activation différent" sur la même table
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, en tant que telle, elle se comportera étrangement et non comme prévu en production.
Si vous souhaitez peupler votre base de données dev avec des données, une meilleure approche serait de créer une tâche rake utilisant le joyau faker pour générer des données valides et de les importer dans la base de données en créant autant ou que peu d'enregistrements que vous le souhaitez, mais si vous êtes à la hauteur. Je pense que update_without_callbacks et create_without_callbacks fonctionneront bien, mais lorsque vous essayez de plier Rails à votre testament, demandez-vous si vous avez une bonne raison et si faire est vraiment une bonne idée.