web-dev-qa-db-fra.com

Rechercher ou créer un enregistrement via l'association factory_girl

J'ai un modèle d'utilisateur qui appartient à un groupe. Le groupe doit avoir un attribut de nom unique. La fabrique d'utilisateurs et la fabrique de groupes sont définies comme suit:

Factory.define :user do |f|
  f.association :group, :factory => :group
  # ...
end

Factory.define :group do |f|
  f.name "default"
end

Lorsque le premier utilisateur est créé, un nouveau groupe est également créé. Lorsque j'essaie de créer un deuxième utilisateur, il échoue car il souhaite à nouveau créer le même groupe.

Existe-t-il un moyen de dire à la méthode d'association factory_girl de rechercher d'abord un enregistrement existant?

Remarque: J'ai essayé de définir une méthode pour gérer cela, mais je ne peux pas utiliser f.association. Je voudrais pouvoir l'utiliser dans des scénarios de concombre comme celui-ci:

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |

et cela ne peut fonctionner que si l'association est utilisée dans la définition d'usine.

60
Slobodan Kovacevic

J'ai fini par utiliser un mélange de méthodes trouvées sur le net, l'une d'entre elles étant des usines héritées comme suggéré par duckyfuzz dans une autre réponse.

J'ai fait ce qui suit:

# in groups.rb factory

def get_group_named(name)
  # get existing group or create new one
  Group.where(:name => name).first || Factory(:group, :name => name)
end

Factory.define :group do |f|
  f.name "default"
end

# in users.rb factory

Factory.define :user_in_whatever do |f|
  f.group { |user| get_group_named("whatever") }
end
16
Slobodan Kovacevic

Vous pouvez utiliser initialize_with Avec la méthode find_or_create

FactoryGirl.define do
  factory :group do
    name "name"
    initialize_with { Group.find_or_create_by_name(name)}
  end

  factory :user do
    association :group
  end
end

Il peut également être utilisé avec id

FactoryGirl.define do
  factory :group do
    id     1
    attr_1 "default"
    attr_2 "default"
    ...
    attr_n "default"
    initialize_with { Group.find_or_create_by_id(id)}
  end

  factory :user do
    association :group
  end
end

Pour Rails 4

La manière correcte dans Rails 4 est Group.find_or_create_by(name: name), donc vous utiliseriez

initialize_with { Group.find_or_create_by(name: name) } 

au lieu.

103
Fernando Almeida

Vous pouvez également utiliser une stratégie FactoryGirl pour y parvenir

module FactoryGirl
  module Strategy
    class Find
      def association(runner)
        runner.run
      end

      def result(evaluation)
        build_class(evaluation).where(get_overrides(evaluation)).first
      end

      private

      def build_class(evaluation)
        evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@build_class)
      end

      def get_overrides(evaluation = nil)
        return @overrides unless @overrides.nil?
        evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@evaluator).instance_variable_get(:@overrides).clone
      end
    end

    class FindOrCreate
      def initialize
        @strategy = FactoryGirl.strategy_by_name(:find).new
      end

      delegate :association, to: :@strategy

      def result(evaluation)
        found_object = @strategy.result(evaluation)

        if found_object.nil?
          @strategy = FactoryGirl.strategy_by_name(:create).new
          @strategy.result(evaluation)
        else
          found_object
        end
      end
    end
  end

  register_strategy(:find, Strategy::Find)
  register_strategy(:find_or_create, Strategy::FindOrCreate)
end

Vous pouvez utiliser ce Gist . Et puis procédez comme suit

FactoryGirl.define do
  factory :group do
    name "name"
  end

  factory :user do
    association :group, factory: :group, strategy: :find_or_create, name: "name"
  end
end

Cela fonctionne pour moi, cependant.

6
Hiasinho

Habituellement, je fais simplement plusieurs définitions d'usine. Un pour un utilisateur avec un groupe et un pour un utilisateur sans couplage:

Factory.define :user do |u|
  u.email "email"
  # other attributes
end

Factory.define :grouped_user, :parent => :user do |u|
  u.association :group
  # this will inherit the attributes of :user
end

Ensuite, vous pouvez les utiliser dans vos définitions d'étape pour créer des utilisateurs et des groupes séparément et les réunir à volonté. Par exemple, vous pouvez créer un utilisateur groupé et un seul utilisateur et joindre l'utilisateur seul à l'équipe d'utilisateurs groupés.

Quoi qu'il en soit, vous devriez jeter un œil au pickle gem qui vous permettra d'écrire des étapes comme:

Given a user exists with email: "[email protected]"
And a group exists with name: "default"
And the user: "[email protected]" has joined that group
When somethings happens....
2
David Tuite

J'ai rencontré un problème similaire récemment, et voici ce que j'ai essayé.

Pour garantir que build et create de FactoryBot se comportent toujours comme il se doit, nous ne devons remplacer la logique de create qu'en procédant comme suit:

factory :user do
  association :group, factory: :group
  # ...
end

factory :group do
  to_create do |instance|
    instance.attributes = Group.find_or_create_by(name: instance.name).attributes
    instance.reload
  end

  name { "default" }
end

Cela garantit que build conserve son comportement par défaut de "construction/initialisation de l'objet" et n'effectue aucune lecture ou écriture de base de données, donc c'est toujours rapide. Seule la logique de create est remplacée pour récupérer un enregistrement existant s'il existe, au lieu d'essayer de toujours créer un nouvel enregistrement.

J'ai écrit n article expliquant cela.

0
Joey Cheng

J'utilise exactement le scénario Concombre que vous avez décrit dans votre question:

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |

Vous pouvez l'étendre comme:

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |
  | [email protected]  | Name: mygroup |
  | [email protected]  | Name: mygroup |

Cela créera 3 utilisateurs avec le groupe "mygroup". Comme il utilise ainsi la fonctionnalité 'find_or_create_by', le premier appel crée le groupe, les deux appels suivants trouvent le groupe déjà créé.

0