À l'aide de factory_girl dans les Rails avec des associations qui ont des contraintes. Prise en double erreurs
Je travaille avec des Rails 2.2 projet de travail pour le mettre à jour. Je suis pour remplacer les luminaires avec des usines (à l'aide de factory_girl) et ont eu quelques problèmes. Le problème, c'est avec des modèles qui représentent des tables avec des données de recherche. Lorsque je créer un Panier avec deux produits qui ont le même type de produit, chaque produit créé est de recréer le même type de produit. Cette erreur à partir d'un unique de validation sur le type de produit modèle.
Problème De Démonstration
C'est à partir d'une unité de test où j'ai créer un Panier et le mettre en pièces. J'ai dû le faire pour contourner le problème. Cela montre encore le problème. Je vais vous expliquer.
cart = Factory(:cart)
cart.cart_items = [Factory(:cart_item,
:cart => cart,
:product => Factory(:added_users_product)),
Factory(:cart_item,
:cart => cart,
:product => Factory(:added_profiles_product))]
Les deux produits étant ajoutés sont de même type et de même lors de chaque produit est créé, il est re-créer le type de produit et la création de doublons.
L'erreur, qui est généré est:
"ActiveRecord::RecordInvalid: la Validation a échoué: le Nom a déjà été prises, le Code a déjà été prises"
Solution de contournement
La solution de contournement pour cet exemple est de remplacer le type de produit utilisé et de la passer dans une instance spécifique de sorte qu'une seule instance. Le "add_product_type" est récupérée au début et transmise pour chaque panier d'un article.
cart = Factory(:cart)
prod_type = Factory(:add_product_type) #New
cart.cart_items = [Factory(:cart_item,
:cart => cart,
:product => Factory(:added_users_product,
:product_type => prod_type)), #New
Factory(:cart_item,
:cart => cart,
:product => Factory(:added_profiles_product,
:product_type => prod_type))] #New
Question
Quelle est la meilleure façon d'utiliser factory_girl avec "pick-liste" types d'associations?
J'avais comme pour l'usine de définition pour tout contenir, au lieu d'avoir à monter dans le test, même si je peux vivre avec elle.
Arrière-plan et les Détails
usines/produit.rb
# Declare ProductTypes
Factory.define :product_type do |t|
t.name "None"
t.code "none"
end
Factory.define :sub_product_type, :parent => :product_type do |t|
t.name "Subscription"
t.code "sub"
end
Factory.define :add_product_type, :parent => :product_type do |t|
t.name "Additions"
t.code "add"
end
# Declare Products
Factory.define :product do |p|
p.association :product_type, :factory => :add_product_type
#...
end
Factory.define :added_profiles_product, :parent => :product do |p|
p.association :product_type, :factory => :add_product_type
#...
end
Factory.define :added_users_product, :parent => :product do |p|
p.association :product_type, :factory => :add_product_type
#...
end
Le but de ProductType du "code" est donc l'application peut donner une signification particulière pour eux. Le type de produit modèle ressemble à quelque chose comme ceci:
class ProductType < ActiveRecord::Base
has_many :products
validates_presence_of :name, :code
validates_uniqueness_of :name, :code
#...
end
usines/cart.rb
# Define Cart Items
Factory.define :cart_item do |i|
i.association :cart
i.association :product, :factory => :test_product
i.quantity 1
end
Factory.define :cart_item_sub, :parent => :cart_item do |i|
i.association :product, :factory => :year_sub_product
end
Factory.define :cart_item_add_profiles, :parent => :cart_item do |i|
i.association :product, :factory => :add_profiles_product
end
# Define Carts
# Define a basic cart class. No cart_items as it creates dups with lookup types.
Factory.define :cart do |c|
c.association :account, :factory => :trial_account
end
Factory.define :cart_with_two_different_items, :parent => :cart do |o|
o.after_build do |cart|
cart.cart_items = [Factory(:cart_item,
:cart => cart,
:product => Factory(:year_sub_product)),
Factory(:cart_item,
:cart => cart,
:product => Factory(:added_profiles_product))]
end
end
Quand j'essaie de définir le panier avec deux éléments du même type de produit, je reçois le même message d'erreur décrit ci-dessus.
Factory.define :cart_with_two_add_items, :parent => :cart do |o|
o.after_build do |cart|
cart.cart_items = [Factory(:cart_item,
:cart => cart,
:product => Factory(:added_users_product)),
Factory(:cart_item,
:cart => cart,
:product => Factory(:added_profiles_product))]
end
end
Vous devez vous connecter pour publier un commentaire.
J'ai rencontré le même problème et a ajouté une lambda au dessus de mes usines de fichier qui implémente le pattern singleton, qui régénère le modèle, si la db a été effacée depuis la dernière série de tests/specs:
Puis, à l'aide de votre exemple usines, vous pouvez utiliser un factory_girl attribut lazy pour exécuter le lambda
Voila!
single_instance
lambda à l'extérieur d'une usine de bloc? J'ai considéré le travail autour deFactory(:product).product_type
mais souhaitez avoir un plus direct chemin de la product_type.return saved_single_instances[factory_key]
nécessaire.Juste pour info, vous pouvez également utiliser le
initialize_with
macro à l'intérieur de votre usine et de vérifier pour voir si l'objet existe déjà, alors ne pas le créer encore une fois. La solution à l'aide d'un lambda (c'est génial, mais!) réplique de la logique déjà présents dans find_or_create_by. Cela fonctionne aussi pour les associations où l' :la ligue est créée par l'intermédiaire d'une usine.La réponse est "non", Factory girl n'ont pas le moyen le plus propre de le faire. Il me semblait vérifier cela sur le Factory girl forums.
Cependant, j'ai trouvé une autre réponse pour moi. Il s'agit d'une autre sorte de solution de contournement, mais rend tout beaucoup plus propre.
L'idée est de changer les modèles qui représentent les tables de recherche à la création de la saisie en cas de disparition. C'est OK parce que le code s'attend à des entrées spécifiques pour exister. Voici un exemple de la modification du modèle.
La méthode de classe statique de "id_for_addition" charge le modèle et le numéro d'identification si trouvé, si elle ne trouve pas, il va le créer.
L'inconvénient est le "id_for_addition" la méthode peut ne pas être clair quant à ce qu'il fait en son nom. Qui peut avoir besoin de changer. La seule autre code de l'impact de l'utilisation normale supplémentaire est un test pour voir si le modèle a été trouvé ou non.
Cela signifie que l'Usine de code pour créer le produit peut être changé comme ça...
Cela signifie que la modification de l'Usine de code peut ressembler à ceci...
C'est exactement ce que je voulais. Je peux maintenant proprement exprimer mon usine et le code de test.
Un autre avantage de cette approche est la recherche des données de la table n'a pas besoin d'être semées ou peuplées dans les migrations. Il gère lui-même pour tester les bases de données ainsi que de la production.
Ces problèmes pourraient être éliminées lorsque les singletons sont introduits dans les usines - actuellement, au -http://github.com/roderickvd/factory_girl/tree/singletons
Question - http://github.com/thoughtbot/factory_girl/issues#issue/16
EDIT:
Voir une meilleure solution au bas de cette réponse.
RÉPONSE ORIGINALE À CETTE QUESTION:
C'est ma solution pour la création de FactoryGirl singleton associations:
Vous l'appelez comme par exemple:
De cette façon, tous les 3 versions de plate-forme aura le même plate-forme 'Foo', c'est à dire singleton.
Si vous voulez enregistrer une base de données de requête vous pouvez faire au lieu de:
Et vous pouvez envisager de faire le singleton association d'un trait:
Si vous souhaitez configurer plus de 1 plate-forme, et ont différents ensembles de platform_versions, vous pouvez effectuer différents traits qui sont plus spécifiques, à savoir:
Espère que ce sera utile à certains.
EDIT:
Après la présentation de mon solution originale au-dessus, j'ai donné le code d'un autre regard, et trouve encore le moyen le plus propre pour ce faire: Vous ne définissez pas de traits dans les usines, à la place, vous spécifiez l'association lorsque vous appelez l'étape de test.
De faire régulièrement des usines:
Maintenant, vous appelez l'étape de test avec l'association spécifié:
Lorsque vous faites comme cela, la création de la plate-forme NewFoo' utilise 'find_or_create_by' de la fonctionnalité, de sorte que le premier appel crée la plate-forme, les 2 prochains appels trouve déjà créé la plate-forme.
De cette façon, tous les 3 versions de plate-forme aura même de la plate-forme NewFoo', et vous pouvez créer autant de groupes de versions de plate-forme que vous avez besoin.
Je pense que c'est un très propre solution, puisque vous gardez l'usine propre, et vous avez même le rendre visible pour le lecteur de test suit que ces 3 versions de plate-forme ont tous la même plate-forme.
J'ai eu une situation similaire. J'ai fini par utiliser mes graines.rb pour définir les singletons et en demandant alors les graines.rb dans le spec_helper.rb.rb pour créer les objets dans la base de données de test. Alors je peux juste la recherche de l'objet approprié dans les usines.
db/graines.rb
spec/spec_helper.rb.rb
spec/usine.rb
J'ai eu ce même problème, et je pense que c'est le même que celui référencé ici: http://groups.google.com/group/factory_girl/browse_frm/thread/68947290d1819952/ef22581f4cd05aa9?tvc=1&q=associations+validates_uniqueness_of#ef22581f4cd05aa9
Je pense que votre solution est peut-être la meilleure solution au problème.
Peut-être vous pouvez essayer d'utiliser factory_girl séquences de produit nom du type et le code de champs? Pour la plupart des tests je suppose que vous ne se soucient pas de savoir si le produit type de code est "code 1" ou "sous", et pour ceux où vous vous souciez, vous pouvez toujours spécifier explicitement.
Je pense que j'ai au moins trouvé une manière plus propre.
J'aime l'idée de contacter ThoughtBot sur l'obtention d'un recommandé "officiel" de la solution. Pour l'instant, cela fonctionne bien.
Je viens de combiné l'approche de le faire dans le test du code de avec de le faire dans l'usine de définition.
Je mettrai à jour si je trouve une meilleure solution.
Inspiré par les réponses que j'ai découvert ici la suggestion de @Jonas Bang le plus proche de mes besoins. Voici ce qui a fonctionné pour moi à la mi-2016 (FactoryGirl v4.7.0, Rails 5rc1):
Exemple d'utilisation pour la création de quatre platform_version avec la même plate-forme de référence:
Et si vous avez besoin de 'Dar' sur plate-forme:
Se sent comme le meilleur des deux mondes sans flexion factory_girl trop loin hors de forme.