2010-09-10 6 views
1

J'ai un modèle de cas particulier qui ne doit pas faire partie d'une transaction externe:Comment exclure un modèle d'une transaction dans ActiveRecord?

Outer.Transaction do 
    ... 

    Inner.create(:blah) 

    ... 
end 

Comment puis-je arrêter intérieure de faire partie de la transaction, en supposant intérieure ne devrait rien savoir quelle transaction spécifique, il est se tiré dans ?

Par exemple, la création d'une transaction intérieure est un pas aller, parce que feront également partie de la transaction extérieure.

Je veux le faire parce que le modèle interne doit écrire immédiatement et ne pas attendre que la transaction externe à commettre.

Répondre

1

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!

+0

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. –

+0

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! –

+0

@greg malcolm: si vous n'avez suivi aucune des suggestions ... qu'avez-vous fait? – marcgg

0

Vous pouvez définir une connexion de base de données distincte pour Inner, puis la transaction s'appliquera uniquement sur la connexion d'Outer.

Questions connexes