Je suis curieux de savoir ce qui nécessiterait une telle construction!
Je pense que vous allez avoir du mal à le faire sans un peu de hackery comme vous l'avez décrit. Par exemple, vous pouvez, dans mysql, définir le type de stockage de table pour Inner
sur un type qui ne supporte pas les transactions (MyIsam eg) tout en gardant le stockage des tables des autres classes avec quelque chose qui supporte les transactions (YUK!).
Si vous le pouvez, vous auriez presque certainement mieux retarder le Inner.create
qu'après la transaction. Vous pouvez commencer par vous assurer que la création se produit toujours. Quelque chose comme:
create_inner = false
begin
Outer.transaction.do
...
create_inner = true # instead of Inner.create(:blah)
...
end
ensure
if create_inner
Inner.create(:blah)
end
end
Cela deviendrait plus compliqué si le reste de votre bloc dépend de la création par exemple Inner
. Vous pouvez probablement créer l'instance dans le bloc et définir created_inner
sur false à la fin du bloc, de sorte que, si le code s'exécute sans exception, il aura été créé dans la transaction et vous ne le créerez plus dans le fichier.
Si vous voulez le faire dans le cas général, vous pouvez définir une méthode de classe sur Inner
pour exécuter un bloc mais toujours créer un objet Inner
. Vous devez également ajouter un after_create
à Inner
. Vous pouvez compter sur l'appel Inner.create
dans le bloc pour le créer lorsque la transaction réussit, mais si elle est annulée, vous devrez le créer par la suite. Par exemple:
class Inner < ActiveRecord::Base
def self.ensure_created(&block)
Thread.current[:created_inner] = false
begin
block.call
rescue => e
if Thread.current[:created_inner]
Inner.create(:blah)
end
raise e
end
end
def after_create
# Flag that an instance has been created in this thread so
# that if we rollback out of a transaction we can create again
Thread.current[:created_inner] = true
end
Vous souhaitez ensuite l'appeler comme:
Inner.ensure_created do
Outer.transaction do
...
Inner.create(:blah)
...
end
end
Cependant, il y a beaucoup de inconvénients à cette approche et je ne suis pas certaine que je Préconisez. C'est compliqué. Cela ne fonctionnera pas si ActiveRecord :: Rollback est déclenché car cette exception ne sortira pas du Outer.transaction
mais entraînera la création de l'instance Inner
. Cela ne fonctionnera pas correctement lorsque deux appels ou plus sont imbriqués. Et enfin je ne l'ai pas testé à fond - à utiliser avec prudence!
Merci pour les suggestions! Pour répondre à la question du scénario, c'est essentiellement pour le verrouillage des tâches par lots, où "Inner" est un modèle de verrouillage. Si 2 lots essayent de courir au moment où je veux que le 2ème lot échoue plutôt que d'attendre en ligne, ou il pourrait y avoir une pile. La transaction externe empêchait réellement cet échec, ce qui obligeait chaque verrouillage à attendre à son tour. –
Je n'ai pas suivi toutes les suggestions proposées, mais je considère cela comme une réponse acceptée parce que c'était la meilleure tentative. Merci les gars! –
@greg malcolm: si vous n'avez suivi aucune des suggestions ... qu'avez-vous fait? – marcgg