Come posso evitare di eseguire callback ActiveRecord?


140

Ho alcuni modelli che hanno richiamate after_save. Di solito va bene, ma in alcune situazioni, come quando si creano dati di sviluppo, voglio salvare i modelli senza far funzionare i callback. C'è un modo semplice per farlo? Qualcosa di simile a ...

Person#save( :run_callbacks => false )

o

Person#save_without_callbacks

Ho cercato nei documenti di Rails e non ho trovato nulla. Tuttavia, nella mia esperienza, i documenti di Rails non raccontano sempre l'intera storia.

AGGIORNARE

Ho trovato un post sul blog che spiega come rimuovere i callback da un modello come questo:

Foo.after_save.clear

Non sono riuscito a trovare dove sia documentato quel metodo ma sembra funzionare.


8
Se stai facendo qualcosa di distruttivo o costoso (come l'invio di e-mail) in una richiamata, ti consiglio di spostarlo e attivarlo separatamente dal controller o altrove. In questo modo non lo "
innescerai

2
la soluzione che hai accettato non funziona per me. Sto usando le rotaie 3. Ricevo un errore come questo: - metodo indefinito `update_without_callbacks 'per # <Utente: 0x10ae9b848>
Mohit Jain,

quel post sul blog ha funzionato ....
Mohit Jain,


Non Foo.after_save.clearrimuoveresti i callback per l'intero modello? E poi come proponete di ripristinarli?
Joshua Pinter,

Risposte:


72

Questa soluzione è solo Rails 2.

Ho appena indagato su questo e penso di avere una soluzione. Esistono due metodi privati ​​di ActiveRecord che è possibile utilizzare:

update_without_callbacks
create_without_callbacks

Dovrai utilizzare send per chiamare questi metodi. esempi:

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

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

Questo è sicuramente qualcosa che vorresti davvero usare solo nella console o durante alcuni test casuali. Spero che questo ti aiuti!


7
non funziona per me. Sto usando le rotaie 3. Ricevo un errore come questo: - metodo indefinito `update_without_callbacks 'per # <Utente: 0x10ae9b848>
Mohit Jain,

Il tuo suggerimento non funziona ma il post del blog menzionato nella parte di aggiornamento funziona ..
Mohit Jain,

Questo salterà anche le convalide.
Daniel Pietzsch,

Ho un'altra soluzione per qualsiasi versione di Rails. Funziona bene per noi. Dai
ka8725

224

Utilizzare update_column(Rails> = v3.1) o update_columns(Rails> = 4.0) per saltare callback e validazioni. Anche con questi metodi, nonupdated_at viene aggiornato.

#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: saltare i callback che funzionano anche durante la creazione di un oggetto

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
sembra funzionare anche con 2.xe ci sono molti altri metodi che funzionano in modo simile: guide.rubyonrails.org/…
rogerdpack

15
Questo non :create_without_callbacksrisolve :( Come posso eseguire qualcosa di simile? (Ha funzionato in Rails2, rimosso in Rails3).
nzifnab

Supponendo che @personda qualche parte sia presente una variabile in un controller, questa soluzione significa che le persone che leggono la classe del modello non saranno in grado di comprendere i callback. Vedranno after_create :something_coole penseranno "fantastico, dopo la creazione succede qualcosa di bello!". Per capire davvero la tua classe di modello, dovranno accedere a tutti i tuoi controller, cercando tutti i piccoli posti in cui hai deciso di iniettare la logica. Non mi piace> o <;;
Ziggy

1
sostituirlo skip_callback ..., if: :skip_some_callbackscon after_create ..., unless: :skip_some_callbacksper eseguirlo correttamente con after_create.
sakurashinken,

28

aggiornato:

La soluzione di @Vikrant Chaudhary sembra migliore:

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

La mia risposta originale:

vedi questo link: Come saltare i callback di ActiveRecord?

in Rails3,

supponiamo di avere una definizione di classe:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Approach1:

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

Approach2: quando vuoi saltarli nei tuoi file rspec o altro, prova questo:

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

NOTA: una volta fatto ciò, se non ci si trova nell'ambiente rspec, è necessario ripristinare i callback:

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

funziona bene per me su rotaie 3.0.5


20

rotaie 3:

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

11
Bello. Anche MyModel.skip_callback (: create,: after,: my_callback) per un controllo preciso .. vedi ActiveSupport :: Callbacks :: ClassMethods documenti per tutti i lobang
tardate

4
Informazioni utili: il 'simbolo' in reset_callbacksnon lo è :after_save, ma piuttosto :save. apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/…
nessur

19

Se l'obiettivo è semplicemente inserire un record senza richiamate o convalide e si desidera farlo senza ricorrere a gemme aggiuntive, aggiungere controlli condizionali, utilizzare RAW SQL o inavvertirsi con il codice in uscita in alcun modo, considerare l'utilizzo di un "ombra oggetto "che punta alla tabella db esistente. Così:

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

Funziona con ogni versione di Rails, è thread-safe ed elimina completamente tutte le convalide e le richiamate senza alcuna modifica al codice esistente. Puoi semplicemente lanciare quella dichiarazione di classe proprio prima dell'importazione effettiva e dovresti essere pronto per partire. Ricorda solo di usare la tua nuova classe per inserire l'oggetto, come:

ImportedPerson.new( person_attributes )

4
La migliore soluzione MAI. Elegante e semplice!
Rafael Oliveira,

1
Questo ha funzionato davvero bene per me perché era qualcosa che volevo fare solo in fase di test, per simulare lo stato "prima" del database, senza inquinare l'oggetto del mio modello di produzione con macchinari per saltare facoltativamente i callback.
Douglas Lovell,

1
Di gran lunga la migliore risposta
robomc

1
Eseguito l'upgrade perché mostra come aggirare i vincoli di binari esistenti e mi ha aiutato a capire come funziona realmente l'intero oggetto MVC. Così semplice e pulito.
Michael Schmitz,

17

Potresti provare qualcosa del genere nel tuo modello Person:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save non è un simbolo, ma questa è almeno la millesima volta che ho provato a farne uno.


1
Penso davvero che questa sia la migliore risposta qui. In questo modo la logica che determina quando il callback viene ignorato è disponibile nel modello e non ci sono frammenti di codice pazzi ovunque che rintracciano la logica aziendale o aggirano l'incapsulamento send. KOODOS
Ziggy

10

Puoi usare update_columns:

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

Aggiorna gli attributi dati di un oggetto, senza chiamare save, saltando quindi convalide e callback.


7

L'unico modo per impedire tutti i callback after_save è avere il primo a restituire false.

Forse potresti provare qualcosa del genere (non testato):

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
Adoro provare (non testato). Brivido.
Adamantish

Testato e funziona. Penso che questa sia una soluzione molto buona e pulita, grazie!
kernificazione

5

Sembra un modo per gestirlo in Rails 2.3 (dato che update_without_callbacks è sparito, ecc.), Sarebbe usare update_all, che è uno dei metodi che salta i callback secondo la sezione 12 della Guida di Rails per convalide e callback .

Inoltre, tieni presente che se stai facendo qualcosa nel tuo callback after_, che fa un calcolo basato su molte associazioni (cioè un has_many assoc, dove fai anche accettare_nested_attributes_for), dovrai ricaricare l'associazione, nel caso facesse parte del salvataggio , uno dei suoi membri è stato eliminato.



4

La maggior parte delle up-votedrisposte potrebbe sembrare confusa in alcuni casi.

È possibile utilizzare solo un semplice ifcontrollo se si desidera saltare una richiamata, in questo modo:

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

3

Una soluzione che dovrebbe funzionare su tutte le versioni di Rails senza l'uso di una gemma o di un plugin è semplicemente quella di rilasciare direttamente le dichiarazioni di aggiornamento. per esempio

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

Questa potrebbe (o meno) essere un'opzione in base alla complessità dell'aggiornamento. Questo funziona bene per esempio bandiere di aggiornamento su un record da all'interno di un callback after_save (senza riarmo il callback).


Non so perché il downvote, ma penso ancora che la risposta di cui sopra sia legittima. A volte il modo migliore per evitare problemi con il comportamento di ActiveRecord è evitare di usare ActiveRecord.
Dave Smylie,

Votato in linea di principio per contrastare il -1. Abbiamo appena avuto un problema di produzione (con una lunga storia alle spalle) che ci ha richiesto di creare un nuovo record (non un aggiornamento) e licenziare i callback sarebbe stato catastrofico. Tutte le risposte di cui sopra sono degli hack se lo ammettono o no e andare al DB è stata la soluzione migliore. Ci sono condizioni legittime per questo. Anche se si dovrebbe fare attenzione all'iniezione SQL con il #{...}.
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

Nessuno di questi punti indica un without_callbacksplug-in che fa esattamente ciò di cui hai bisogno ...

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 funziona con Rails 2.x



1

Se si utilizza Rails 2. È possibile utilizzare la query SQL per aggiornare la colonna senza eseguire callback e convalide.

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

Penso che dovrebbe funzionare in tutte le versioni di binari.


1

Quando ho bisogno del controllo completo sul callback, creo un altro attributo che viene utilizzato come switch. Semplice ed efficace:

Modello:

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

Test:

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

Puoi usare la gemma subdolo: https://rubygems.org/gems/sneaky-save .

Nota che questo non può aiutare a salvare le associazioni senza convalide. Genera errore "create_at non può essere nullo" poiché inserisce direttamente la query sql a differenza di un modello. Per implementare questo, dobbiamo aggiornare tutte le colonne generate automaticamente di db.


1

Avevo bisogno di una soluzione per Rails 4, quindi ho pensato a questo:

app / modelli / preoccupazioni / 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 qualsiasi modello:

include SaveWithoutCallbacks

allora puoi:

record.save_without_callbacks

o

Model::WithoutCallbacks.create(attributes)

0

Perché vorresti essere in grado di farlo nello sviluppo? Sicuramente questo significherà che stai costruendo la tua applicazione con dati non validi e come tale si comporterà in modo strano e non come ti aspetti dalla produzione.

Se vuoi popolare il tuo db di sviluppo con i dati, un approccio migliore sarebbe quello di costruire un'attività di rake che usasse la gemma falsa per costruire dati validi e importarli nel db creando tutti i record che desideri, ma se sei il tallone piegati su di esso e hai una buona ragione immagino che update_without_callbacks e create_without_callbacks funzioneranno bene, ma quando stai cercando di piegare le rotaie alla tua volontà, chiediti di avere una buona ragione e se quello che stai facendo è davvero una buona idea.


Non sto cercando di salvare senza convalide, solo senza richiamate. La mia app utilizza i callback per scrivere un codice HTML statico nel filesystem (una specie di CMS). Non voglio farlo durante il caricamento dei dati di sviluppo.
Ethan,

Era solo un pensiero, immagino che in passato ho visto questo tipo di domanda che sta cercando di aggirare roba per cattive ragioni.
nitecoder

0

Un'opzione è quella di avere un modello separato per tali manipolazioni, usando la stessa tabella:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Lo stesso approccio potrebbe semplificare le cose per bypassare le convalide)

Stephan


0

Un altro modo sarebbe quello di utilizzare gli hook di validazione anziché i callback. Per esempio:

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

In questo modo puoi ottenere do_something per impostazione predefinita, ma puoi facilmente sostituirlo con:

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

3
Questa sembra una cattiva idea: dovresti usare le cose per lo scopo previsto. L'ultima cosa che vuoi è che le tue convalide abbiano effetti collaterali.
chug2k,

0

Qualcosa che dovrebbe funzionare con tutte le versioni di ActiveRecordsenza dipendere da opzioni o metodi activerecord che potrebbero esistere o meno.

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: utilizzare un "modello activerecord diverso" sulla stessa tabella


0

Per richiamate personalizzate, utilizzare un attr_accessore ununless nel callback.

Definisci il tuo modello come segue:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

Quindi, se è necessario salvare il record senza colpire i after_savecallback definiti, impostare l' skip_after_save_callbacksattributo virtuale su 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

Non è il modo più pulito, ma è possibile racchiudere il codice di richiamata in una condizione che controlla l'ambiente Rails.

if Rails.env == 'production'
  ...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.