Faker is producing duplicate data when used in factory_girl
Asked Answered
B

4

87

I'm trying to populate some fake data into a factory using the Faker gem:

Factory.define :user do |user|
  user.first_name Faker::Name::first_name
  user.last_name Faker::Name::last_name
  user.sequence(:email) {|n| "user#{n}@blow.com" }
end

However while I expect this to produce users who have different first_name and last_names, each one is the same:

>> Factory(:user)
=> #<User id: 16, email: "[email protected]", created_at: "2011-03-18 18:29:33",     
updated_at: "2011-03-18 18:29:33", first_name: "Bailey", last_name: "Durgan">
>> Factory(:user)
=> #<User id: 17, email: "[email protected]", created_at: "2011-03-18 18:29:39", 
updated_at: "2011-03-18 18:29:39", first_name: "Bailey", last_name: "Durgan">

How can I get the Faker gem to generate new names for each users and not just reuse the original ones?

Bermejo answered 18/3, 2011 at 18:37 Comment(1)
Just a shot in the dark, but have you tried using something like user.sequence(:first_name} {|n| Faker::Name::first_name}? FactoryGirl is likely just evaluating your Faker call when it loads your "fixtures". Using the sequence param,&block method should prevent that.Bots
A
159
Factory.define :user do |user|
  user.first_name { Faker::Name::first_name }
  user.last_name { Faker::Name::last_name }
  user.sequence(:email) {|n| "user#{n}@blow.com" }
end

Try putting brackets around the fakers. see this link

Aborn answered 18/3, 2011 at 18:46 Comment(6)
I love stackoverflow so much - thank you Will, you saved my baconBermejo
Why, why, why? What is going on here?Idzik
because of "lazy attribute", see: github.com/thoughtbot/factory_girl/blob/master/…Wakerly
Sadly this doesn't always work. This basically just gets a new random faker object, however because of RNG, there is still a chance this fails.Hardecanute
Aaaaaaand this is why writing Rails apps is only possible with Stack Overflow. Thanks.Elsieelsinore
Is there any way to fix RNG?Jowl
I
46

Note that Faker may still be providing duplicate data due to the limited amount of fake data available.

For simple testing purposes and to get by uniqueness validations, I've used the following:

sequence(:first_name) {|n| Faker::Name::first_name + " (#{n})"}
sequence(:last_name) {|n| Faker::Name::last_name + " (#{n})"}
Ironware answered 2/7, 2014 at 22:54 Comment(3)
This answer deserves more upvotes. It's likely to happen when your test creates lot of instances.Agro
Yup, I agree with Enrico. +1Firebrick
Nice idea, but adding parenthesis can break the rest if you use first name and last name to generate emails, or you have validations on the format (don't know of any name that has parenthesis :P).Sextans
O
18

For the sake of preserving the correct answer, here it is translocated from the blog, I take no credit for the answer.

If you use the code below, faker will not churn out unique names

Factory.define :user do |u|
  u.first_name Faker::Name.first_name
  u.last_name Faker::Name.last_name
end

However putting curly braces around faker makes it work!

Factory.define :user do |u|
  u.first_name { Faker::Name.first_name }
  u.last_name { Faker::Name.last_name }
end

To explain why, the first example is producing the same names. It's only evaluating once. The second example evaluates every time the factory is used.

This is due to the {} providing lazy evaluation. Essentially they are providing a proc/lambda with the Faker call as their return value.

Olnee answered 14/4, 2014 at 3:1 Comment(1)
Thank you for posting this. I couldn't figure out why Faker wasn't able to generate random data and it seemed like every example I came across showed how to use sequencing which seemed odd to me. I wanted to use Faker so each record is random, not sequenced. Just adding braces around my Faker calls solved the problem. Simple and elegant!Callous
M
5

A (less efficient) alternative to using sequences when you have a uniqueness validation on an attribute is to check whether a proposed value already exists and keep trying new ones until it's unique:

FactoryGirl.define do
  factory :company do
    name do
      loop do
        possible_name = Faker::Company.name
        break possible_name unless Company.exists?(name: possible_name)
      end
    end
  end
end
Muzhik answered 6/11, 2015 at 21:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.