Ruby on Rails
Transazioni ActiveRecord
Ricerca…
Osservazioni
Le transazioni sono blocchi protettivi in cui le istruzioni SQL sono permanenti solo se riescono tutte come una sola azione atomica. L'esempio classico è un trasferimento tra due account in cui è possibile avere un deposito solo se il ritiro è riuscito e viceversa. Le transazioni rafforzano l'integrità del database e proteggono i dati dagli errori del programma o dalle interruzioni del database. Quindi in pratica dovresti usare i blocchi di transazione ogni volta che hai un numero di istruzioni che devono essere eseguite insieme o non del tutto.
Esempio di base
Per esempio:
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
Questo esempio prenderà solo denaro da David e lo consegnerà a Mary se né il prelievo né il deposito sollevano un'eccezione. Le eccezioni costringono un ROLLBACK che restituisce il database allo stato prima dell'inizio della transazione. Si noti, tuttavia, che gli oggetti non avranno i loro dati di istanza restituiti allo stato pre-transazione.
Classi ActiveRecord differenti in una singola transazione
Sebbene il metodo della classe di transazione sia chiamato su alcune classi ActiveRecord, gli oggetti all'interno del blocco di transazione non devono necessariamente essere tutte istanze di quella classe. Questo perché le transazioni sono per connessione al database, non per modello.
In questo esempio, un record di saldo viene salvato a livello di transazione anche se la transazione viene richiamata nella classe Account:
Account.transaction do
balance.save!
account.save!
end
Il metodo di transazione è anche disponibile come metodo di istanza del modello. Ad esempio, puoi anche fare questo:
balance.transaction do
balance.save!
account.save!
end
Più connessioni al database
Una transazione agisce su una singola connessione al database. Se si dispone di più database specifici della classe, la transazione non proteggerà l'interazione tra di loro. Una soluzione alternativa è iniziare una transazione su ogni classe di cui modifichiamo i modelli:
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
Questa è una soluzione scadente, ma le transazioni completamente distribuite vanno oltre lo scopo di ActiveRecord.
salva e distruggi automaticamente in una transazione
Sia #save che #destroy vengono racchiusi in una transazione che garantisce che qualunque cosa tu faccia in convalide o callback avverrà sotto la sua copertina protetta. Pertanto, è possibile utilizzare le convalide per verificare i valori da cui dipende la transazione oppure è possibile generare eccezioni nei callback per il rollback, inclusi after_*
callbacks.
Di conseguenza, le modifiche al database non vengono visualizzate al di fuori della connessione fino al completamento dell'operazione. Ad esempio, se si tenta di aggiornare l'indice di un motore di ricerca in after_save
l'indicizzatore non vedrà il record aggiornato. Il callback after_commit
è l'unico che viene attivato una volta eseguito il commit dell'aggiornamento.
callback
Esistono due tipi di callback associati alle transazioni di after_commit
e after_rollback
: after_commit
e after_rollback
.
after_commit
callback after_commit
vengono richiamati su ogni record salvato o distrutto all'interno di una transazione immediatamente dopo il commit della transazione. after_rollback
callback after_rollback
vengono richiamati su ogni record salvato o after_rollback
all'interno di una transazione immediatamente dopo il rollback della transazione o del punto di salvataggio.
Questi callback sono utili per l'interazione con altri sistemi poiché è garantito che la richiamata viene eseguita solo quando il database si trova in uno stato permanente. Ad esempio, after_commit
è un buon punto per mettere un aggancio per svuotare una cache poiché la sua eliminazione da una transazione potrebbe innescare la rigenerazione della cache prima che il database venga aggiornato.
Rollback di una transazione
ActiveRecord::Base.transaction
utilizza l'eccezione ActiveRecord::Rollback
per distinguere un rollback deliberato da altre situazioni eccezionali. Solitamente, sollevando un'eccezione, il metodo .transaction
ripristina la transazione del database e trasmette l'eccezione. Ma se si solleva un'eccezione ActiveRecord::Rollback
, la transazione del database verrà sottoposta a rollback, senza passare l'eccezione.
Ad esempio, è possibile farlo nel controller per eseguire il rollback di una transazione:
class BooksController < ActionController::Base
def create
Book.transaction do
book = Book.new(params[:book])
book.save!
if today_is_friday?
# The system must fail on Friday so that our support department
# won't be out of job. We silently rollback this transaction
# without telling the user.
raise ActiveRecord::Rollback, "Call tech support!"
end
end
# ActiveRecord::Rollback is the only exception that won't be passed on
# by ActiveRecord::Base.transaction, so this line will still be reached
# even on Friday.
redirect_to root_url
end
end