2011-08-22 15 views
50

Possiedo un modello utente appartenente a un gruppo. Il gruppo deve avere un attributo nome univoco. Fabbrica utente e fabbrica di gruppo definite come:Trova o crea record tramite l'associazione factory_girl

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

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

Quando viene creato il primo utente, viene creato anche un nuovo gruppo. Quando provo a creare un secondo utente, fallisce perché vuole creare di nuovo lo stesso gruppo.

C'è un modo per dire al metodo di associazione factory_girl di cercare prima un record esistente?

Nota: ho provato a definire un metodo per gestirlo, ma non posso utilizzare f.association. Mi piacerebbe essere in grado di utilizzarlo in scenari di cetriolo in questo modo:

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

e questo può funzionare solo se l'associazione è utilizzato nella definizione di fabbrica.

risposta

16

ho finito per usare un mix di metodi trovati giro per la rete, uno dei quali è ereditata come suggerito da duckyfuzz in un'altra risposta.

ho seguente:

# 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 
1

Generalmente faccio solo più definizioni di fabbrica. Uno per un utente con un gruppo e uno per un utente groupless:

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 

quindi è possibile utilizzare questi nelle definizioni step per creare utenti e gruppi in modo esclusivo e unirsi a loro insieme a volontà. Ad esempio, è possibile creare un utente raggruppato e un utente singolo e unire l'utente singolo al gruppo di utenti raggruppati.

In ogni caso, si dovrebbe dare uno sguardo alla pickle gem che vi permetterà di scrivere passi come:

fabbriche
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.... 
0

sto usando esattamente lo scenario cetriolo hai descritto nella sua domanda:

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

qui può allungare piace:

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

Questo creerà 3 utenti con il gruppo "mygroup". Poiché viene utilizzato in questo modo utilizza la funzionalità 'find_or_create_by', la prima chiamata crea il gruppo, le successive due chiamate trovano il gruppo già creato.

89

È possibile utilizzare initialize_with con find_or_create metodo

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 

Può essere utilizzato anche con 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 

Per Rails 4

Il modo corretto in Rails 4 è Group.find_or_create_by(name: name) , quindi useresti

initialize_with { Group.find_or_create_by(name: name) } 

invece.

+6

Funziona molto bene, grazie. In Rails 4, il modo preferito sarebbe: 'Group.find_or_create_by (name: name)' – migu

+19

Il modo preferito in Rails 4 sarebbe in realtà 'Group.where (nome: nome) .first_or_create'. – Laurens

+0

Probabilmente questa dovrebbe essere la risposta accettata. Drive-by-ers ... ecco la tua soluzione. – ocodo

4

È inoltre possibile utilizzare una strategia factorygirl per raggiungere questo

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 

È possibile utilizzare this gist. E poi effettuare le seguenti operazioni

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

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

Questo sta lavorando per me, però.

Problemi correlati