Ruby on Rails
Transacciones ActiveRecord
Buscar..
Observaciones
Las transacciones son bloques protectores donde las sentencias de SQL solo son permanentes si todas pueden tener éxito como una sola acción atómica. El ejemplo clásico es una transferencia entre dos cuentas donde solo puede tener un depósito si el retiro fue exitoso y viceversa. Las transacciones imponen la integridad de la base de datos y protegen los datos contra los errores del programa o las fallas de la base de datos. Básicamente, debería usar bloques de transacciones cuando tenga una serie de sentencias que deben ejecutarse juntas o no deben ejecutarse.
Ejemplo basico
Por ejemplo:
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
Este ejemplo solo tomará dinero de David y se lo dará a Mary si ni el retiro ni el depósito generan una excepción. Las excepciones forzarán un ROLLBACK que devuelve la base de datos al estado antes de que comience la transacción. Tenga en cuenta, sin embargo, que a los objetos no se les devolverán los datos de instancia a su estado previo a la transacción.
Diferentes clases de ActiveRecord en una sola transacción
Aunque se llama al método de clase de transacción en alguna clase de ActiveRecord, no es necesario que todos los objetos dentro del bloque de transacción sean instancias de esa clase. Esto se debe a que las transacciones son por conexión de base de datos, no por modelo.
En este ejemplo, un registro de saldo se guarda de manera transaccional aunque la transacción se llame en la clase de Cuenta:
Account.transaction do
balance.save!
account.save!
end
El método de transacción también está disponible como un método de instancia de modelo. Por ejemplo, también puedes hacer esto:
balance.transaction do
balance.save!
account.save!
end
Conexiones de base de datos múltiples
Una transacción actúa en una sola conexión de base de datos. Si tiene varias bases de datos específicas de clase, la transacción no protegerá la interacción entre ellas. Una solución es comenzar una transacción en cada clase cuyos modelos modifique:
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
Esta es una solución deficiente, pero las transacciones totalmente distribuidas están fuera del alcance de ActiveRecord.
guardar y destruir se envuelven automáticamente en una transacción
Tanto #save como #destroy vienen envueltos en una transacción que garantiza que todo lo que haga en las validaciones o devoluciones de llamada sucederá bajo su cobertura protegida. Por lo tanto, puede usar las validaciones para verificar los valores de los que depende la transacción o puede generar excepciones en las devoluciones de llamada para revertir, incluidas las devoluciones de llamada after_*
.
Como consecuencia, los cambios en la base de datos no se ven fuera de su conexión hasta que se completa la operación. Por ejemplo, si intenta actualizar el índice de un motor de búsqueda en after_save
el indexador no verá el registro actualizado. La after_commit
llamada after_commit
es la única que se activa una vez que se confirma la actualización.
Devoluciones de llamada
Hay dos tipos de devoluciones de llamada asociadas con las transacciones de after_commit
y after_rollback
: after_commit
y after_rollback
.
after_commit
devoluciones de llamada after_commit
se llaman en cada registro guardado o destruido dentro de una transacción inmediatamente después de que se confirma la transacción. after_rollback
devoluciones de llamada after_rollback
realizan en cada registro guardado o destruido dentro de una transacción inmediatamente después de que la transacción o el punto de salvaguarda se revierte.
Estas devoluciones de llamada son útiles para interactuar con otros sistemas, ya que se le garantizará que la devolución de llamada solo se ejecute cuando la base de datos se encuentre en un estado permanente. Por ejemplo, after_commit
es un buen lugar para poner un gancho para borrar un caché, ya que borrarlo de una transacción podría hacer que el caché se regenere antes de actualizar la base de datos.
Deshacer una transacción
ActiveRecord::Base.transaction
utiliza la excepción ActiveRecord::Rollback
para distinguir una reversión deliberada de otras situaciones excepcionales. Normalmente, al generar una excepción, el método .transaction
revertirá la transacción de la base de datos y pasará la excepción. Pero si ActiveRecord::Rollback
una excepción ActiveRecord::Rollback
, la transacción de la base de datos se retrotraerá, sin pasar la excepción.
Por ejemplo, podría hacer esto en su controlador para deshacer una transacción:
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