web-dev-qa-db-fra.com

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

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.

131
Ethan

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!

72
efalcao

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
218
Vikrant Chaudhary

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

28

Rails 3:

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

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 )
17
Brad Werth

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.

16
Sarah Mei

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.

9
Luís Ramalho

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
6
rfunduk

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é.

5
chrisrbailey

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

4
fringd

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).

3
Dave Smylie

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? }
3
Aleks
# 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
Sasha Alexandrov

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.

1
Donald ball

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)
1
Steve Friedman

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

1

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.

1
Zinin Serge

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.

1
oivoodoo

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
1
tothemario

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

1
kares

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

0
Stephan Wehner

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)
0
BoosterStage

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

0
choonkeat

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.

0
nitecoder