Come fai a evitare modifiche al database all'interno di un filtro before_create Rails ActiveRecord dall'ottenere il rollback quando ritorna falso?
-
22-09-2019 - |
Domanda
Ho aggiunto un filtro before_create ad uno dei miei modelli Rails ActiveRecord e dentro quel filtro che sto facendo alcuni aggiornamenti del database.
A volte mi return false dal filtro per impedire la creazione del modello di destinazione, ma che sta causando tutti i altri modifiche al database che ho fatto (mentre all'interno del filtro) per ottenere il rollback.
Come posso evitare questo?
Aggiornamento # 1 : Ecco alcuni pseudo codice che spiega il mio problema:
class Widget < ActiveRecord::Base
before_create :update_instead
def update_instead
if some_condition?
update_some_record_in_same_model # this is getting rolled back
return false # don't create a new record
else
return true # let it continue
end
end
end
Aggiornamento # 2 : Alcune buone risposte qui sotto, ma ognuno aveva i suoi difetti. Ho finito per overridding il metodo create in questo modo:
def create
super unless update_instead? # yes I reversed the return values from above
end
Soluzione
Ho appena avuto a fare questo di recente. È necessario richiedere specificamente un altro collegamento da AR. Quindi eseguire le modifiche a questa connessione. In questo modo, se la creazione fallisce e ripristina la transazione, i cambiamenti del vostro callback sono stati già impegnati in una transazione diversa.
Ignora la mia risposta sopra. Il codice di esempio hai appena dato davvero chiarito le cose.
class Foo < ActiveRecord::Base
before_create :update_instead
def update_instead
dbconn = self.class.connection_pool.checkout
dbconn.transaction do
dbconn.execute("update foos set name = 'updated'")
end
self.class.connection_pool.checkin(dbconn)
false
end
end
>> Foo.create(:name => 'sam')
=> #<Foo id: nil, name: "sam", created_at: nil, updated_at: nil>
>> Foo.all
=> [#<Foo id: 2, name: "updated", created_at: "2009-10-21 15:12:55", updated_at: "2009-10-21 15:12:55">]
Altri suggerimenti
Con una transazione nel filtro.
Hai provato di sovrascrivere creare / salvare e le loro versioni distruttivi? ActiveRecord :: Base.create, ActiveRecord :: Base.save e le loro versioni distruttivi sono avvolti in una transazione, sono anche quello che grilletto callback e validazioni. Se stai ignorando che, solo la roba fatta da super sarà parte di una transazione. Se avete bisogno di anni convalide eseguire prima allora si può chiamare in modo esplicito valida per eseguirli tutti.
Esempio:
before_create :before_create_actions_that_can_be_rolled_back
def create
if valid? && before_create_actions_that_wont_be_rolled_back
super
end
end
def before_create_actions_that_wont_be_rolled_back
# exactly what it sounds like
end
def before_create_actions_that_can_be_rolled_back
# exactly what it sounds like
end
Avvertimento: Con queste modifiche i metodi saranno chiamati in questo ordine:
- prima della convalida (on_create)
- validate
- dopo la convalida (on_create)
- before_create_actions_that_wont_be_rolled_back
- prima della convalida (on_create)
- validate
- dopo la convalida (on_create)
- prima di salvare i callback
- prima di creare i callback
- si crea record
- dopo creare i callback
- dopo salvare callback
Se qualsiasi convalide falliscono o se uno di callback restituisce false nei passaggi 5-12 il database verrà ripristinati alla stato in cui era prima del punto 5.
Se valida? fallisce, o before_create_actions_that_wont_be_rolled_back fallisce rispetto verrà interrotta l'intera catena.
Potrebbe tali modifiche essere fatto in un after_create
invece?
Questa utilizza lo stesso concetto come la risposta di Rich Cavanaugh. Ho aggiunto un modello di genitore in modo che sia più chiaro ciò che il filtro sta facendo. La chiave è quella di utilizzare un filo + un'automatica partenza / check-in di una connessione separata. Nota: è necessario assicurarsi che il proprio: il valore piscina si trova ad almeno 2 nella vostra specifica connessione, a seconda di quanti thread simultanei si incorrerà. Penso che il valore predefinito è 5.
class Gadget < ActiveRecord::Base
has_many :widgets
end
class Widget < ActiveRecord::Base
belongs_to :gadget
before_create :update_instead
def update_some_record_in_same_model
# the thread forces a new connection to be checked out
Thread.new do
ActiveRecord::Base.connection_pool.with_connection do |conn|
# try this part without the 2 surrounding blocks and it will be rolled back
gadget.touch_count += 1
gadget.save!
end
end.join
end
def some_condition?
true
end
def update_instead
if some_condition?
update_some_record_in_same_model # this is getting rolled back
p [:touch_count_in_filter, gadget.reload.touch_count]
return false # don't create a new record
else
return true # let it continue
end
end
end
Prova:
g = Gadget.create(:name => 'g1')
puts "before:"
p [:touch_count, g.reload.touch_count]
p [:widget_count, Widget.count]
g.widgets.create(:name => 'w1')
puts "after:"
# Success means the count stays incremented
p [:touch_count, g.reload.touch_count]
p [:widget_count, Widget.count]
Per ulteriori approfondimenti: http://bibwild.wordpress.com/2011/11/14/multi-threading-in-rails-activerecord-3-0-3-1/