Carlos Brando

Nome do Jogo

Rails 2.3: Nested Transactions

Outro recurso que também foi muito solicitado e estará disponível com a versão 2.3 do Rails é uma melhoria nas nested transactions (transações aninhadas).

Atualmente no Rails já podemos aninhar transações, ou em um português mais simples podemos colocar uma transação dentro de outra. Veja um exemplo:

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

User.find(:all) # => empty

Resumindo o exemplo acima: Criei uma transação e inclui um novo usuário no banco, então criei uma segunda transação dentro da primeira e inclui um segundo usuário, e no fim disparei uma exceção que faz com que a primeira transação e todas as suas filhas sejam desfeitas como você pode ver no resultado do método find.

A novidade é a opção :requires_new. Quando usada em uma sub-transação, caso alguma coisa dê errado somente as alterações feitas dai em diante é que serão desfeitas. Veja um novo exemplo, agora com este recurso habilitado:

User.transaction do
  User.create(:username => 'Kotori')
  User.transaction(:requires_new => true) do
    User.create(:username => 'Nemu')
    raise ActiveRecord::Rollback
  end
end

User.find(:all) # => Returns only Kotori

Como você pode ver no retorno do método find, ao realizar o rollback somente a inclusão do segundo usuário foi desfeita, já que estou usando a opção :requires_new habilitada na segunda transação.

Nem todo banco de dados tem suporte real a nested transactions. Na verdade, até o momento somente o MS-SQL tem esta funcionalidade. Mas não se preocupe se você estiver usando outro banco de dados, o Active Record consegue emular nested transactions usando savepoints.

ATENÇÃO: Caso você esteja usando o MySQL, então não faça operações DDL em nested transactions que estiverem emulando savepoints. Ou seja, não tente executar algo como ‘CREATE TABLE’ nestes blocos. Isto acontece porque o MySQL automaticamente libera todos os savepoints após executar uma operação DDL. Então quando a transação terminar e tentar liberar os savepoints criados, ocorrerá um erro no banco já que todos os savepoints foram liberados antes da hora. Para entender melhor, veja o exemplo abaixo:

# INICIO
Model.connection.transaction do
  # CRIA O SAVEPOINT active_record_1
  Model.connection.transaction(:requires_new => true) do
    # active_record_1 é automaticamente liberado
    Model.connection.create_table(...)
  end # LIBERA o savepoint active_record_1

  # ^^^^ BOOM! database error!
end

Apesar deste pequeno detalhe, em todos os outros casos este recurso tem funcionado perfeitamente.


Todos os exemplos dados aqui funcionarão somente no Ruby on Rails 2.3 ou superior. Você pode encontrar mais detalhes sobre esta e outras novidades acompanhando a série Rails 2.3.

Comments