2011-08-22 54 views
50

我有一個用戶模型屬於一個組。組必須具有唯一的名稱屬性。用戶工廠和組工廠定義爲:通過factory_girl協會查找或創建記錄

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

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

當創建第一個用戶時,也會創建一個新組。當我嘗試創建第二個用戶時,它失敗了,因爲它想再次創建相同的組。

有沒有辦法告訴factory_girl關聯方法先查找現有記錄?

注意:我確實試圖定義一個方法來處理這個問題,但後來我無法使用f.association。我希望能夠用它在黃瓜的場景是這樣的:

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

,如果關聯在工廠定義中才能做到這一點。

回答

16

我結束了使用四處撒網發現的搭配方法,其中一個被繼承的工廠作爲由duckyfuzz在另一個答案中提出。

我以下:

# 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

通常我只是做出多個工廠定義。一個用於與一組用戶,另一個是無組的用戶:

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 

那麼你可以使用這些在你的步驟定義來seperatly創建用戶和組,並隨意將它們連接在一起。例如,您可以創建一個分組用戶和一個單獨用戶,並將單個用戶加入分組的用戶組。

無論如何,你應該看看pickle gem這將使你寫這樣的步驟:

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

我使用的正是你在你的問題中描述的情景黃瓜:

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

你可以擴展它想:

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

這將創建3個用戶與組「mygroup」。像這樣使用'find_or_create_by'功能,第一次調用創建組,接下來的兩次調用找到已經創建的組。

89

您可以使用initialize_withfind_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 

它也可以用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 

用於軌道4

在軌道4,5的正確方法是Group.find_or_create_by(name: name) ,所以你會用

initialize_with { Group.find_or_create_by(name: name) } 

改爲。

+6

工作得很好,謝謝。在Rails 4中,首選的方法是:'Group.find_or_create_by(name:name)' – migu

+19

Rails 4中的首選方法實際上是'Group.where(name:name).first_or_create'。 – Laurens

+0

這應該可能是被接受的答案。開車去......這是你的解決方案。 – ocodo

4

你也可以使用一個FactoryGirl策略來實現這一

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 

您可以使用this gist。 然後執行以下操作:

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

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

雖然這對我有效。