Faker is producing duplicate data when used in factory_girl - ruby-on-rails

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: "user7#blow.com", 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: "user8#blow.com", 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?

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

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})"}

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.

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

Related

How to write RSpec to check if associated record exists?

I am beginner to RSpec. I am having a model teacher that has_many :lessons. Here is my FactoryGirls records:
spec/factories/lessons.rb
FactoryGirl.define do
factory :lesson do
title "Rspec test"
description "test description"
company_name "narola pvt"
association :teacher
location "Zwanenplein 34"
days_to_pay 2
end
end
spec/factories/teachers.rb
FactoryGirl.define do
factory :teacher do
first_name "Teacher's name"
last_name "Teacher's last name"
address "los angeles"
city "california"
zip_code "12345"
country "USA"
birthdate nil
phone nil
password "password"
email { "example#{SecureRandom.uuid}#email.dummy" }
end
end
Following is my try with models test:
spec/models/teacher_spec.rb
require 'rails_helper'
RSpec.describe Teacher, type: :model do
let(:teacher) { FactoryGirl.create(:teacher) }
it "should have at least one lesson" do
config.expect_with(Lesson.where(teacher_id: teacher)){|c| c.syntax = :should}
end
end
I am willing to write a rspec test case to find if lesson exists for particular lesson.
Please try this:
it "should have at least one lesson" do
expect(Lesson.where(teacher_id: teacher.id)).to exist
end
Let me know if it's work for you. I haven't try this.
it "should have at least one lesson" do
expect(Lesson.where(teacher_id: teacher.id).exists?).to be_truthy
end
This would be faster because of the use of 'exists?' method compared to
expect(Lesson.where(teacher_id: teacher.id)).to exist
Underlying query execution due to use of 'exists?' the method is fast. More details are here ->
https://www.ombulabs.com/blog/benchmark/performance/rails/present-vs-any-vs-exists.html

Rails Faker - How to generate a custom domain email?

Ideally I want to create a fake email based on the Faker generated email, but I want to achieve something like: faker_first_name#mydomain.com. The documentation shows you can do it for the first part but not the actual domain. Is there a way to achieve this?
20.times do
u = User.new(first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
email: Faker::Name.first_name"#THISPART.com",
)
u.save
end
Update Dec 2019:
Faker version v2.8.0 introduced the domain support - Release v2.8.0
Now, It is possible to pass the domain while creating the email address.
Following are the possible options:
Faker::Internet.email #=> "eliza#mann.net"
Faker::Internet.email(name: 'Nancy') #=> "nancy#terry.biz"
Faker::Internet.email(name: 'Janelle Santiago', separators: '+') #=> janelle+santiago#becker.com"
Faker::Internet.email(domain: 'example.com') #=> alice#example.com"
Note: Above code sample is from the faker documentation
Old Answer:
Well there is no such provision to pass domain name to the method
But, you can make use of Faker::Internet.user_name
User.new(
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
email: "#{Faker::Internet.user_name}#customdomain.com"
)
I think you just missed the string concat: +
:006 > Faker::Name.first_name+"#THISPART.com"
=> "Irving#THISPART.com"
And if you meant keeping the same name, save it before:
fn = Faker::Name.first_name
sn = Faker::Name.last_name
u = User.create(
:forename => fn,
:surname => sn,
:email => "#{fn}.#{sn}#yourdomain.net",
Faker::Name.first_name will always generate a new random value.
Recent versions of Faker has a built in support for custom email subdomains.
Faker::Internet.email(domain: 'customdomain.com')

RSpec - Testing errors for 2 attributes that share the same validation(s)

I have two attributes within my model that share the same validations
validates :first_name, :last_name, length: {minimum: 2}
Right now I have the :first_name attribute tested as follows:
RSpec.describe User, :type => :model do
it 'is invalid if the first name is less than two characters'
user = User.create(
first_name: 'a'
)
expect(user).to have(1).errors_on(:first_name)
end
For the sake of a developer who isn't familiar with how I've setup my model I wanted to explicitly state the two attributes' relationship with something like this:
it 'is invalid if the first name and/or last name has less than two characters'
user = User.create(
first_name: 'a',
last_name: 'b
)
expect(user).to have(1).errors_on(:first_name, :last_name)
Obviously this throws the error:
wrong number of arguments (2 for 0..1)
The same thing would apply if I had instituted 2 two validations:
validates :first_name, :last_name, length: {minimum: 2}, format: {with: /^([^\d\W]|[-])*$/}
And try to test for 2 errors:
it 'is invalid if the first name and/or last name has less than two characters and has special characters'
user = User.create(
first_name: '#',
last_name: '#
)
expect(user).to have(2).errors_on(:first_name, :last_name)
In RSpec 3.x, you can compound expectations with .and:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name).and have(1).errors_on(:last_name)
end
Check out the rspec-expectations documentation for more info.
For RSpec 2.x, you'll need to do one of these:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name) && have(1).errors_on(:last_name)
end
# or
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name)
expect(user).to have(1).errors_on(:last_name)
end
It's not as pretty, but it should work.
EDIT:
OP was using rspec-collection_matchers gem. That gem's custom matchers do not include RSpec 3 mixin module RSpec::Matchers::Composable, so the #and method goes unrecognized.
There are a few things to do to circumvent this issue. The easiest is to use the && technique above (in my RSpec 2.x suggestions). To use only RSpec 3 matchers, you need to use be_valid:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to_not be_valid
end
Of course, this does not distinguish between first_name errors and last_name errors as was originally intended. To do that with the be_valid matcher, you'd have to break the test into two tests:
it 'is invalid if the first name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'abc')
expect(user).to_not be_valid
end
it 'is invalid if the last name has less than two characters' do
user = User.create(first_name: 'abc', last_name: 'a')
expect(user).to_not be_valid
end
Your tests should look like this:
it 'invalid length' do
user = User.new(first_name: '#', last_name: '#')
user.valid?
expect(user.errors.count).to eq 2
expect(user.errors[:first_name]).to include "is too short (minimum is 2 characters)"
expect(user.errors[:last_name]).to include "is too short (minimum is 2 characters)"
end
The user.valid? call will run the new user against the validations which will populate the errors.
That's a very verbose test to do a unit test - I highly recommend shoulda matchers. You can test the above in just two lines:
it { is_expected.to ensure_length_of(:first_name).is_at_least(2) }
it { is_expected.to ensure_length_of(:last_name).is_at_least(2) }

Rails 3.2.13 + Faker + Devise + Rolify

I'm building a test app and came across a problem with Faker gem:
I've created Use model with Devise and used Rolify gem to create roles and then later on will use CanCan to limit user's usage permissions.
So, with Faker I've created a file for my rake task:
namespace :db do
desc "Fill dummy DB"
task :populate => :environment do
require "populator"
require "faker"
password = "password"
User.populate 198 do |user|
user.firstname = Faker::Name.first_name
user.lastname = Faker::Name.last_name
user.email = Faker::Internet.email
user.encrypted_password = User.new(:password => password).encrypted_password
user.phone = Faker::PhoneNumber.phone_number
user.address1 = Faker::Address.street_address
user.city = Faker::Address.city
user.state = Faker::Address.state_abbr
user.zip = Faker::Address.zip_code
user.latitude = Faker::Address.latitude
user.longitude = Faker::Address.longitude
end
end
end
This code works and it creates 198 dummy users...but when I looked into my users_roles table - nothing's there, users don't have roles assigned to them. I've been trying to figure out how to assign a role to user through Faker, but no luck.
I've tried adding user.role_id = User.add_role :user , but no luck.
Thank you in advance.
I discourage you from using populator gem. There are three reasons to that:
Populator is no longer maintained, last commit was over two years ago. The official repository still says "Rails 3 support is currently being worked on. Stay tuned." with Rails 4 released over a month ago.
It does not have data validation. It is up to you to ensure you’re adding proper data values. And this may end up really messy.
It's really easy to make it programatically giving you more control over custom cases and therefore making you a better developer =)
You can construct your database populator with user role adding with code below:
namespace :db do
desc "Fill dummy DB"
task :populate => :environment do
198.times do |n|
user = User.new(
firstname: Faker::Name.first_name,
lastname: Faker::Name.last_name,
email: Faker::Internet.email,
password: "password",
phone: Faker::PhoneNumber.phone_number,
address1: Faker::Address.street_address,
city: Faker::Address.city,
state: Faker::Address.state_abbr,
zip: Faker::Address.zip_code,
latitude: Faker::Address.latitude,
longitude: Faker::Address.longitude )
user.add_role :user
user.save
end
end
end
Please note as I:
deleted require statements (at least on my system aren't necessary)
moved password into a block
am adding a role using function, not assignment (=) BEFORE saving a user - not possible using populator
Using this construct, you can make more handy things with the User instance, ex. prevent sending a confirmation email when using Devise's :confirmable by invoking user.skip_confirmation! before saving - what you might find useful.
Consider as well generating emails basing on first_name and last_name already generated by Faker before to have user's name and email inline. To achieve that you may replace
Faker::Internet.email
by
"#{user.first_name}.#{user.last_name}#{n+1}#example.com"
taking advantage of block numerator and therefore creating an unique email.

FactoryGirl: attributes_for not giving me associated attributes

I have a Code model factory like this:
Factory.define :code do |f|
f.value "code"
f.association :code_type
f.association(:codeable, :factory => :portfolio)
end
But when I test my controller with a simple test_should_create_code like this:
test "should create code" do
assert_difference('Code.count') do
post :create, :code => Factory.attributes_for(:code)
end
assert_redirected_to code_path(assigns(:code))
end
... the test fails. The new record is not created.
In the console, it seems that attributes_for does not return all required attributes like the create does.
rob#compy:~/dev/my_rails_app$ rails console test
Loading test environment (Rails 3.0.3)
irb(main):001:0> Factory.create(:code)
=> #<Code id: 1, code_type_id: 1, value: "code", codeable_id: 1, codeable_type: "Portfolio", created_at: "2011-02-24 10:42:20", updated_at: "2011-02-24 10:42:20">
irb(main):002:0> Factory.attributes_for(:code)
=> {:value=>"code"}
Any ideas?
Thanks,
You can try something like this:
(Factory.build :code).attributes.symbolize_keys
Check this: http://groups.google.com/group/factory_girl/browse_thread/thread/a95071d66d97987e)
This one doesn't return timestamps etc., only attributes that are accessible for mass assignment:
(FactoryGirl.build :position).attributes.symbolize_keys.reject { |key, value| !Position.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Still, it's quite ugly. I think FactoryGirl should provide something like this out of the box.
I opened a request for this here.
I'd suggest yet an other approach, which I think is clearer:
attr = attributes_for(:code).merge(code_type: create(:code_type))
heres what I end up doing...
conf = FactoryGirl.build(:conference)
post :create, {:conference => conf.attributes.slice(*conf.class.accessible_attributes) }
I've synthesized what others have said, in case it helps anyone else. To be consistent with the version of FactoryGirl in question, I've used Factory.build() instead of FactoryGirl.build(). Update as necessary.
def build_attributes_for(*args)
build_object = Factory.build(*args)
build_object.attributes.slice(*build_object.class.accessible_attributes).symbolize_keys
end
Simply call this method in place of Factory.attributes_for:
post :create, :code => build_attributes_for(:code)
The full gist (within a helper module) is here: https://gist.github.com/jlberglund/5207078
In my APP/spec/controllers/pages_controllers_spec.rb I set:
let(:valid_attributes) { FactoryGirl.attributes_for(:page).merge(subject: FactoryGirl.create(:theme), user: FactoryGirl.create(:user)) }
Because I have two models associated. This works too:
FactoryGirl.define do
factory :page do
title { Faker::Lorem.characters 12 }
body { Faker::Lorem.characters 38 }
discution false
published true
tags "linux, education, elearning"
section { FactoryGirl.create(:section) }
user { FactoryGirl.create(:user) }
end
end
Here's another way. You probably want to omit the id, created_at and updated_at attributes.
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.

Resources