2011-08-16 45 views
32

假設我有一個模型'用戶',它在'email'字段上有唯一性約束Rails:避免工廠女孩的重複錯誤...我做錯了嗎?

如果我一旦一切正常就調用Factory(:user),但如果我第二次調用它'入口已存在'錯誤失敗。

我目前使用一個簡單的輔助搜索創建出廠前在數據庫中的現有條目......,並呼籲任何工廠我通過助手作出。

它的工作原理,但它不是完全優雅,並考慮我想是多麼常見。這個問題應該,我猜有一個更好的解決方案。 ......工廠女孩有沒有內在的方式來返回或創建一個工廠,而不是僅僅使用create()來計費?如果不是,大多數人如何避免與工廠重複輸入?

+0

我也有這個問題。您是否在電子郵件字段中添加了序列,因此理論上每次調用Factory(:user)時都會更改。我有這個問題,並且仍然遇到你有問題。 – someoneinomaha

+1

我有同樣的問題。我注意到FactoryGirl在我的測試數據庫中留下了一些不好的數據,這些數據來自於一個早期的測試失敗,可能會引發異常(可能會避免清理)。我通過這樣做來修復它: RAILS_ENV =測試bin/rake db:drop RAILS_ENV =測試bin/rake db:希望這可以幫助@someoneinomaha – Joel

回答

69

答案很簡單:使用factory.sequence

如果您有必須是唯一的,你可以在factory_girl添加順序,以確保它是絕不相同的字段:

Factory.define :user do |user| 
    sequence(:email){|n| "user#{n}@factory.com" } 
    user.password{ "secret" } 
end 

這將每次增加n,以便生成一個唯一的電子郵件地址,例如`[email protected]。 (更多信息請參見https://github.com/thoughtbot/factory_girl/wiki/Usage

然而,這並不總是Rails.env.development偉大的...

隨着時間的推移,我發現,這是不實際,創造出獨特的最有效的方法電子郵件地址。原因在於,雖然工廠對於您的測試環境而言始終是獨一無二的,但它並不總是與您的開發環境相獨立,並且在您向上和向下啓動環境時重置自身。在:test這不是問題,因爲數據庫已被擦除,但在:development中,您保持趨向於相同的數據一段時間。

然後你得到衝突,並發現自己不得不手動覆蓋電子郵件給您知道的東西是獨特的這是煩人。

經常更有用:使用隨機數

因爲我定期去我用,而不是產生一個隨機數調用u = Factory :user從控制檯。你不能保證避免衝突,但在實踐中幾乎從未發生:

Factory.define :user do |user| 
    user.email {"user_#{Random.rand(1000).to_s}@factory.com" } 
    user.password{ "secret" } 
end 

注:您必須使用Random.rand而不是rand(),因爲FactoryGirl中的碰撞(錯誤?)[https://github.com/thoughtbot/factory_girl/issues/219](see here]。

這使您可以從命令行隨意創建用戶,無論數據庫中是否存在工廠生成的用戶。

製作電子郵件測試可選的額外容易

當你進入電子郵件測試你經常要驗證特定用戶的操作觸發的電子郵件給其他用戶。

您以Robin Hood的身份登錄,發送電子郵件至Maid Marion,然後轉至您的收件箱進行驗證。您在收件箱中看到的內容是[email protected]。這到底是誰?

您需要返回數據庫以檢查電子郵件是否由您期望的發送/接收。這又是一個痛苦。

我喜歡做的是使用Factory用戶名和一個隨機數生成電子郵件。這使得更容易檢查來自哪些事物(並且也使得碰撞不可能)。使用法克爾寶石(http://faker.rubyforge.org/)來創建我們得到的名字:

Factory.define :user do |user| 
    user.first_name { Faker::Name::first_name } 
    user.last_name { Faker::Name::last_name } 
    user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}@factory.com" } 
end 

最後,由於法克爾有時產生未電子郵件友好(邁克·奧唐奈),我們需要接受白名單字符的名稱:.gsub(/[^a-zA-Z1-10]/, '')

Factory.define :user do |user| 
    user.first_name { Faker::Name::first_name } 
    user.last_name { Faker::Name::last_name } 
    user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '')}_#{Random.rand(1000).to_s}@factory.com" } 
end 

這給了我們風度翩翩,但獨特的電郵,例如[email protected][email protected]

+0

...或者使用'Faker :: Internet.email'作爲電子郵件地址。 –

+2

但是,這有一個缺點,即電子郵件地址可能不像名稱。我現在看到你想在這裏做什麼。此外,ffaker比Faker,FWIW更快,性能更好。 –

+1

然後使用Faker :: Internet.email(「#{first_name}#{last_name}」)'使電子郵件名稱匹配。 –

10

這就是我做的,強制的「否」我的工廠女孩​​順序是一樣的物體」的ID,從而避免碰撞:

首先,我定義找到下一個ID應該是在應用程序/模型/ user.rb什麼方法:

def self.next_id 
    self.last.nil? ? 1 : self.last.id + 1 
end 

然後我打電話User.next_id從規範/factories.rb啓動序列:

factory :user do 
    association(:demo) 
    association(:location) 
    password "password" 
    sequence(:email, User.next_id) {|n| "darth_#{n}@sunni.ru" } 
end 
1

我發現這是確保測試總是通過的好方法。 否則,您無法確定您將創建唯一電子郵件的時間百分之百。

FactoryGirl.define do 
    factory :user do 
    name { Faker::Company.name } 
    email { generate(:email) } 
    end 
    sequence(:email) do 
    gen = "user_#{rand(1000).to_s}@factory.com" 
    while User.where(email: gen).exists? 
     gen = "user_#{rand(1000).to_s}@factory.com" 
    end 
    gen 
    end 
end 
0

如果你只需要生成屬性幾個值,還可以添加一個方法字符串,它記錄用於一個屬性之前字符串。然後你可以這樣做:

factory :user do 
    fullname { Faker::Name.name.unique('user_fullname') } 
end 

我使用這種方法進行播種。我想避免序列號,因爲它們看起來不現實。

這裏字符串擴展,使這種情況發生:

class String 
    # Makes sure that the current string instance is unique for the given id. 
    # If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number. 
    # Example: 
    #  puts "abc".unique("some_attribute") #=> "abc" 
    #  puts "abc".unique("some_attribute") #=> "abc-1" 
    #  puts "abc".unique("some_attribute") #=> "abc-2" 
    #  puts "abc".unique("other") #=> "abc" 
    # 
    # Internal: 
    # We keep a data structure of the following format: 
    #  @@unique_values = { 
    #  "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item 
    #  } 
    def unique(for_id) 
    @@unique_values ||= {} # initialize structure in case this method was never called before 
    @@unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet 
    counter = @@unique_values[for_id][self] || 0 
    result = (counter == 0) ? self : "#{self}-#{counter}" 
    counter += 1 
    @@unique_values[for_id][self] = counter 
    return result 
    end 

end 

注意:這不應該被用於大量的屬性,因爲我們跟蹤所有之前的字符串(優化可能)。