Wie kann ich vermeiden, ActiveRecord-Rückrufe auszuführen?


140

Ich habe einige Modelle mit After_Save-Rückrufen. Normalerweise ist das in Ordnung, aber in einigen Situationen, z. B. beim Erstellen von Entwicklungsdaten, möchte ich die Modelle speichern, ohne dass die Rückrufe ausgeführt werden. Gibt es eine einfache Möglichkeit, das zu tun? So etwas wie ...

Person#save( :run_callbacks => false )

oder

Person#save_without_callbacks

Ich habe in den Rails-Dokumenten nachgesehen und nichts gefunden. Nach meiner Erfahrung erzählen die Rails-Dokumente jedoch nicht immer die ganze Geschichte.

AKTUALISIEREN

Ich habe einen Blog-Beitrag gefunden , in dem erklärt wird, wie Sie Rückrufe aus einem Modell wie diesem entfernen können:

Foo.after_save.clear

Ich konnte nicht finden, wo diese Methode dokumentiert ist, aber sie scheint zu funktionieren.


8
Wenn Sie in einem Rückruf etwas Destruktives oder Teueres tun (wie das Senden von E-Mails), empfehle ich, dies zu verschieben und getrennt vom Controller oder anderswo auszulösen. Auf diese Weise werden Sie es nicht "versehentlich" in der Entwicklung usw. auslösen
Ryanb

2
Die von Ihnen akzeptierte Lösung funktioniert bei mir nicht. Ich benutze Rails 3. Ich erhalte eine Fehlermeldung wie die folgende: - undefinierte Methode `update_without_callbacks 'für # <User: 0x10ae9b848>
Mohit Jain

yaa, dass Blog-Beitrag funktioniert ....
Mohit Jain


Würden Sie Foo.after_save.clearRückrufe nicht für das gesamte Modell entfernen? Und wie schlagen Sie dann vor, sie wiederherzustellen?
Joshua Pinter

Antworten:


72

Diese Lösung ist nur Rails 2.

Ich habe das gerade untersucht und denke, ich habe eine Lösung. Es gibt zwei private ActiveRecord-Methoden, die Sie verwenden können:

update_without_callbacks
create_without_callbacks

Sie müssen send verwenden, um diese Methoden aufzurufen. Beispiele:

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

p = Person.find(1)
p.send(:update_without_callbacks)

Dies ist definitiv etwas, das Sie wirklich nur in der Konsole oder bei zufälligen Tests verwenden möchten. Hoffe das hilft!


7
Es funktioniert nicht für mich. Ich benutze Rails 3. Ich erhalte eine Fehlermeldung wie die folgende: - undefinierte Methode `update_without_callbacks 'für # <User: 0x10ae9b848>
Mohit Jain

Ihr Vorschlag funktioniert nicht, aber der im Update-Teil erwähnte Blog-Beitrag funktioniert.
Mohit Jain

Dadurch werden auch Validierungen übersprungen.
Daniel Pietzsch

Ich habe eine andere Lösung für jede Version von Rails. Es funktioniert gut für uns. Überprüfen Sie es in meinem Blog-Beitrag: railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725

224

Verwenden Sie update_column(Rails> = v3.1) oder update_columns(Rails> = 4.0), um Rückrufe und Validierungen zu überspringen. Auch mit diesen Methoden updated_atwird nicht aktualisiert.

#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: Rückrufe überspringen, die auch beim Erstellen eines Objekts funktionieren

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

2
Es sieht so aus, als ob es auch mit 2.x funktioniert, und es gibt eine Vielzahl anderer Methoden, die ähnlich funktionieren : guides.rubyonrails.org/…
rogerdpack

15
Dies spricht nicht an :create_without_callbacks:( Wie kann ich etwas Ähnliches ausführen? (Arbeitete in Rails2, entfernt in Rails3).
nzifnab

Angenommen, es @personhandelt sich irgendwo um eine Variable in einem Controller, bedeutet diese Lösung, dass Personen, die Ihre Modellklasse lesen, die Rückrufe nicht verstehen können. Sie werden sehen after_create :something_coolund denken "großartig, etwas Cooles passiert nach dem Erstellen!". Um Ihre Modellklasse tatsächlich zu verstehen, müssen sie alle Ihre Controller durchsuchen und nach all den kleinen Stellen suchen, an denen Sie sich entschieden haben, Logik einzufügen. Ich mag es nicht> o <;;
Ziggy

1
Ersetzen skip_callback ..., if: :skip_some_callbacksdurch after_create ..., unless: :skip_some_callbacks, um dies ordnungsgemäß mit after_create auszuführen.
Sakurashinken

28

Aktualisiert:

@ Vikrant Chaudharys Lösung scheint besser zu sein:

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

Meine ursprüngliche Antwort:

siehe diesen Link: Wie überspringe ActiveRecord-Rückrufe?

in Rails3,

Angenommen, wir haben eine Klassendefinition:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Ansatz1:

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

Ansatz 2: Wenn Sie sie in Ihren rspec-Dateien oder was auch immer überspringen möchten, versuchen Sie Folgendes:

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

HINWEIS: Wenn Sie sich nicht in einer rspec-Umgebung befinden, sollten Sie die Rückrufe zurücksetzen:

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

funktioniert gut für mich auf Schienen 3.0.5



19

Wenn das Ziel darin besteht, einfach einen Datensatz ohne Rückrufe oder Validierungen einzufügen, und Sie dies tun möchten, ohne auf zusätzliche Edelsteine ​​zurückzugreifen, bedingte Überprüfungen hinzuzufügen, RAW SQL zu verwenden oder in irgendeiner Weise mit Ihrem vorhandenen Code zu fummeln, sollten Sie die Verwendung eines "Schattens" in Betracht ziehen Objekt "zeigt auf Ihre vorhandene DB-Tabelle. Wie so:

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

Dies funktioniert mit jeder Version von Rails, ist threadsicher und eliminiert alle Validierungen und Rückrufe vollständig, ohne Änderungen an Ihrem vorhandenen Code vorzunehmen. Sie können diese Klassendeklaration einfach direkt vor Ihrem eigentlichen Import einwerfen, und Sie sollten bereit sein, loszulegen. Denken Sie daran, Ihre neue Klasse zum Einfügen des Objekts zu verwenden, z.

ImportedPerson.new( person_attributes )

4
Beste Lösung aller Zeiten. Elegant und einfach!
Rafael Oliveira

1
Dies funktionierte sehr gut für mich, da ich dies nur im Test tun wollte, um den Status "vor" der Datenbank zu simulieren, ohne mein Produktionsmodellobjekt mit Maschinen zu verschmutzen, um optional Rückrufe zu überspringen.
Douglas Lovell

1
Mit Abstand die beste Antwort
Robomc

1
Upvoted, weil es zeigt, wie vorhandene Schienenbeschränkungen umgangen werden, und mir geholfen hat zu verstehen, wie das gesamte Objekt MVC wirklich funktioniert. So einfach und sauber.
Michael Schmitz

17

Sie könnten so etwas in Ihrem Personenmodell ausprobieren:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save ist kein Symbol, aber das ist mindestens das 1000. Mal, dass ich versucht habe, es zu einem zu machen.


1
Ich denke wirklich, dass dies hier die beste Antwort ist. Auf diese Weise ist die Logik, die bestimmt, wann der Rückruf übersprungen wird, im Modell verfügbar, und Sie haben nicht überall verrückte Codefragmente, die die Geschäftslogik aufheben oder die Kapselung mit umgehen send. KOODOS
Ziggy

10

Sie können verwenden update_columns:

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

Aktualisiert die angegebenen Attribute eines Objekts, ohne save aufzurufen, und überspringt daher Überprüfungen und Rückrufe.


7

Die einzige Möglichkeit, alle after_save-Rückrufe zu verhindern, besteht darin, dass der erste false zurückgibt.

Vielleicht könnten Sie etwas versuchen wie (ungetestet):

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

1
Ich liebe es zu versuchen (ungetestet). Nervenkitzel fahren.
Adamantish

Getestet und es funktioniert. Ich denke, das ist eine sehr gute und saubere Lösung, danke!
Kernifizierung

5

Eine Möglichkeit, dies in Rails 2.3 zu handhaben (da update_without_callbacks weg ist usw.), besteht darin, update_all zu verwenden. Dies ist eine der Methoden, mit denen Rückrufe gemäß Abschnitt 12 des Rails-Handbuchs zu Validierungen und Rückrufen übersprungen werden .

Beachten Sie außerdem, dass Sie, wenn Sie in Ihrem after_-Rückruf etwas tun, das eine Berechnung basierend auf vielen Zuordnungen durchführt (dh eine has_many-Zuordnung, in der Sie auch_nested_attributes_for akzeptieren), die Zuordnung neu laden müssen, falls dies Teil des Speichervorgangs ist wurde eines seiner Mitglieder gelöscht.


4

https://gist.github.com/576546

Speichern Sie diesen Affen-Patch einfach in config / initializers / skip_callbacks.rb

dann

Project.skip_callbacks { @project.save }

oder so ähnlich.

Alle Gutschrift an den Autor


4

Am meisten up-voted Antworten scheinen in einigen Fällen verwirrend.

Sie können nur eine einfache ifÜberprüfung verwenden, wenn Sie einen Rückruf wie folgt überspringen möchten:

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

3

Eine Lösung, die für alle Versionen von Rails ohne Verwendung eines Gems oder Plugins funktionieren sollte, besteht einfach darin, Update-Anweisungen direkt auszugeben. z.B

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

Dies kann eine Option sein (oder auch nicht), je nachdem, wie komplex Ihr Update ist. Dies funktioniert gut, um beispielsweise Flags in einem Datensatz innerhalb eines after_save-Rückrufs zu aktualisieren (ohne den Rückruf erneut auszulösen).


Ich bin mir nicht sicher, warum die Ablehnung, aber ich denke immer noch, dass die obige Antwort legitim ist. Manchmal ist es am besten, ActiveRecord zu vermeiden, um Probleme mit dem ActiveRecord-Verhalten zu vermeiden.
Dave Smylie

Prinzipiell positiv bewertet, um dem -1 entgegenzuwirken. Wir hatten gerade ein Produktionsproblem (mit einer langen Geschichte dahinter), bei dem wir einen neuen Datensatz erstellen mussten (kein Update), und das Auslösen von Rückrufen wäre katastrophal gewesen. Alle oben genannten Antworten sind Hacks, ob sie es zugeben oder nicht, und es war die beste Lösung, zur DB zu gehen. Hierfür gibt es legitime Bedingungen. Allerdings sollte man sich vor der SQL-Injection mit dem hüten #{...}.
sinisterchipmunk

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

Keiner dieser Punkte für ein without_callbacksPlugin, das genau das tut, was Sie brauchen ...

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 funktioniert mit Rails 2.x.


1

Ich habe ein Plugin geschrieben, das update_without_callbacks in Rails 3 implementiert:

http://github.com/dball/skip_activerecord_callbacks

Ich denke, die richtige Lösung besteht darin, Ihre Modelle neu zu schreiben, um Rückrufe zu vermeiden. Wenn dies jedoch kurzfristig unpraktisch ist, kann dieses Plugin hilfreich sein.


1

Wenn Sie Rails 2 verwenden. Sie können die SQL-Abfrage zum Aktualisieren Ihrer Spalte verwenden, ohne Rückrufe und Überprüfungen auszuführen.

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

Ich denke, es sollte in allen Rails-Versionen funktionieren.


1

Wenn ich die volle Kontrolle über den Rückruf benötige, erstelle ich ein weiteres Attribut, das als Switch verwendet wird. Einfach und effektiv:

Modell:

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

Prüfung:

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

Sie können Sneaky-Save Gem verwenden: https://rubygems.org/gems/sneaky-save .

Beachten Sie, dass dies beim Speichern von Assoziationen ohne Validierungen nicht hilfreich sein kann. Es wird der Fehler 'created_at darf nicht null sein' ausgelöst, da die SQL-Abfrage im Gegensatz zu einem Modell direkt eingefügt wird. Um dies zu implementieren, müssen wir alle automatisch generierten Spalten von db aktualisieren.


1

Ich brauchte eine Lösung für Rails 4, also habe ich mir Folgendes ausgedacht:

App / Modelle / Bedenken / 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

in jedem Modell:

include SaveWithoutCallbacks

dann kannst du:

record.save_without_callbacks

oder

Model::WithoutCallbacks.create(attributes)

0

Warum möchten Sie dies in der Entwicklung tun können? Dies bedeutet sicherlich, dass Sie Ihre Anwendung mit ungültigen Daten erstellen und sich als solche seltsam verhalten und nicht so, wie Sie es in der Produktion erwarten.

Wenn Sie Ihre Entwickler-Datenbank mit Daten füllen möchten, besteht ein besserer Ansatz darin, eine Rechenaufgabe zu erstellen, bei der mithilfe des Fälscher-Edelsteins gültige Daten erstellt und in die Datenbank importiert werden, um so viele oder wenige Datensätze zu erstellen, wie Sie möchten Ich denke, Update_without_callbacks und create_without_callbacks funktionieren einwandfrei. Wenn Sie jedoch versuchen, die Schienen nach Ihrem Willen zu biegen, fragen Sie sich, ob Sie einen guten Grund haben und ob das, was Sie tun, wirklich eine gute Idee ist.


Ich versuche nicht ohne Validierungen zu speichern, nur ohne Rückrufe. Meine App verwendet Rückrufe, um statisches HTML in das Dateisystem zu schreiben (ähnlich wie bei einem CMS). Ich möchte das nicht tun, während ich Entwicklungsdaten lade.
Ethan

War nur ein Gedanke, ich denke, wann immer ich in der Vergangenheit diese Art von Frage gesehen habe, versucht es aus schlechten Gründen, Dinge zu umgehen.
Nitecoder

0

Eine Möglichkeit besteht darin, ein separates Modell für solche Manipulationen unter Verwendung derselben Tabelle zu haben:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Der gleiche Ansatz könnte das Umgehen von Validierungen erleichtern.)

Stephan


0

Eine andere Möglichkeit wäre die Verwendung von Validierungs-Hooks anstelle von Rückrufen. Beispielsweise:

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

Auf diese Weise können Sie das do_something standardmäßig erhalten, aber Sie können es leicht überschreiben mit:

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

3
Dies scheint eine schlechte Idee zu sein - Sie sollten die Dinge für den beabsichtigten Zweck verwenden. Das Letzte, was Sie wollen, ist, dass Ihre Validierungen Nebenwirkungen haben.
Chug2k

0

Etwas, das mit allen Versionen von ActiveRecordohne funktionieren sollte, abhängig von Optionen oder Aktivaufzeichnungsmethoden, die möglicherweise vorhanden sind oder nicht.

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: Verwenden Sie ein "anderes Aktivaufzeichnungsmodell" für dieselbe Tabelle


0

Verwenden Sie für benutzerdefinierte Rückrufe ein attr_accessorund einunless im Rückruf.

Definieren Sie Ihr Modell wie folgt:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

Wenn Sie den Datensatz speichern müssen, ohne die von after_saveIhnen definierten Rückrufe auszuführen, setzen Sie das skip_after_save_callbacksvirtuelle Attribut auf true.

person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.

person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.

person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.

-5

Nicht der sauberste Weg, aber Sie könnten den Rückrufcode in einen Zustand einschließen, der die Rails-Umgebung überprüft.

if Rails.env == 'production'
  ...
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.