Rails e transazioni
In questi giorni ho toccato con mano la gestione delle transazioni in Rails.
Il mio problema consisteva nel gestire in un’unica transazione la crezione di due modelli diversi ed indipendenti l’uno dall’altro.
Ogni oggetto ActiveRecord e’ provvisto di un metodo
, in grado di gestire in blocco operazioni di scrittura sullo stesso o su oggetti collegati da relazioni tipo
ecc. Quindi, nel mio caso, creare due oggetti non relazionati significava aprire due transazioni separate.
Questo aspetto di Rails mi ha molto stupito e sinceramente non capisco la scelta di THH di gestire le transazioni in questo modo.
A mio parere, una scelta piu’ felice e’ stata presa dal team di Django, dando la possibilita’ di gestire le transazioni alla Rails o relative all’intera richiesta HTTP, attraverso la libreria
. Questa libreria permette infatti di aprire una transazione con l’inizio di una richiesta HTTP e di committare o di eseguire il rollback in base al successo o no della
Tornando al mio problema, a nulla sono valsi i tentativi di risolvere la transazione usando:
ActiveRecord::Base.transaction do
@first_model = FirstModel.new(params[:first_model])
@first_model.save!
@second_model = SecondModel.new(:user => current_user, :first_model => @first_model)
@second_model.save!
end
rescue Exception => @ex
# handle the exception
end
oppure usando transazioni innestate:
FirstModel.transaction do
SecondModel.transaction do
@first_model = FirstModel.new(params[:first_model])
@first_model.save!
@second_model = SecondModel.new(:user => current_user, :first_model => @first_model)
@second_model.save!
end
end
rescue Exception => @ex
# handle the exception
end
In entrambi i casi, se la creazione di
falliva, per la prima transazione relativa al
non veniva eseguito il rollback, e i dati venivano salvati comunque sul database.
La soluzione al problema e’ stata incapsulare l’intero motodo del controller in un
, aprendo una transazione prima dell’esecuzione del metodo e chiudendola all’uscita dello stesso.
Questo il codice:
prepend_around_filter :transaction_filter
def transaction_filter
ActiveRecord::Base.transaction { |*block_args| yield(*block_args) if block_given? }
end
Un’alternativa potrebbe essere l’utilizzo del plugin Super Transaction