Writing Factories around values that already exist - ruby-on-rails

I've been using Factory Girl to create some basic objects in development when I want to test out an idea, and I commonly run into this:
ActiveRecord::RecordInvalid: Validation failed: Email has already been taken, Login has already been taken
If I've run FactoryGirl.create :user in development mode once and left that user in the db, I'll have to run that command twice if I try to do this again after closing the console. Basically, sequences are getting reset between console instances.
Has anyone come up with a way to write factories such that they'll generate unique results each time? I'm aware that I can use random generators to pick a value from a large domain, minimizing the chance of a collision. I'd like to find a cleaner method, if available, though.

You can write a sequence for this.
Factory.sequence(:email) do |n|
"tester#{n}#example.com"
end
Factory.define :user do |f|
f.name "Tester"
f.email {Factory.next :email}
f.password "tester"
end
Source: about half way down the page.
EDIT
Upon re-reading, it seems that you are trying to create the data in development mode.
You should use the seeds.rb file for this and maintain a counter.
index = User.count || 1
User.create([
{:email => "user#{index++}#example.com",
:password => "password"},
{:email => "user#{index++}#example.com",
:password => "password"}
])

Related

How to sequence correctly with factory_girl in rails development environment?

I want to integrate factory_girl in rails development environment for quickly creating records. However, every time I open rails console and do some actions, the sequence state always begins from zero, which leading to error of violating uniqueness. Such as:
FactoryGirl.define do
factory :user do
name 'Jim'
email
end
sequence :email do |n| # every time n begins at 0
"#{n}#exmaple.com"
end
end
Do you have some simple solutions to solve this problem? Thank you!
You can set the initial value of n if you wish. you can achieve this by:
FactoryGirl.define do
factory :user do
name 'Jim'
sequence :email, 100 do |n|
"#{n}#exmaple.com"
end
end
end
or
FactoryGirl.define do
factory :user do
name 'Jim'
sequence(:email, 100) { |n| "person#{n}#example.com" }
end
end
note: n = 100 in this case for more info check out the documentation here
I think the original question was how to deal with the fact that sequences reset each time rails is restarted, not how to use sequences. The easiest thing I've done for strings that need to be unique is to append a timestamp to the end of the string.
FactoryBot.define do
factory :user do
name 'Jim'
email { "jim_#{Time.now.to_f}#example.com" }
end
end
This can also be combined with Faker, since Faker's unique call can time out or run out of options:
FactoryBot.define do
name { Faker::Name.name + " #{Time.now.to_f}" }
email { "#{name}#example.com" }
end
It makes for ugly strings, but you can truncate the timestamp a bit if you like, and it just works. As long as your test objects don't need to look nice, you can just slap the timestamp on the string and get on with your day.
You need the sequence statement inside of the :user factory definition:
FactoryGirl.define do
factory :user do
name 'Jim'
sequence :email do |n|
"#{n}#exmaple.com"
end
end
end
You can reset the sequence with FactoryBot.rewind_sequences.
I have this in my spec helper:
config.append_after(:each) do
DatabaseCleaner.clean
FactoryBot.rewind_sequences
end
Sadly, there isn't a way to keep the sequence as you start the environment up and down (which you probably do in development), nor are you starting with a clean db each time (as you probably do in the test environment).
From this answer talking about using factory.sequence:
However this isn't always great in Rails.env.development...
Over time I have found that this is not actually the most useful way to create unique email addresses. The reason is that while the factory is always unique for your test environment it's not always unique for your development environment and n resets itself as you start the environment up and down. In :test this isn't a problem because the database is wiped but in :development you tend to keep the same data for a while.
You then get collisions and find yourself having to manually override the email to something you know is unique which is annoying.
Often more useful: use a random number
Since I call u = Factory :user from the console on a regular basis I go instead with generating a random number. You're not guaranteed to avoid collisions but in practice it hardly ever happens:
Factory.define :user do |user|
user.email {"user_#{Random.rand(1000).to_s}#factory.com" }
user.password{ "secret" }
end
N.B. You have to use Random.rand rather than rand() because of a collision (bug?) in FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see here).
This frees you to create users at will from the command line regardless of whether there are already factory generated users in the database.

Railstutorial.org Validating presence tests

I've been working through the tutorials at railstutorial.org, and I was a little stumped by the author's code for section -- 6.2.1 Validating presence.
In the user model, the tutorial adds validates :name, :presence => true. Simple enough.
When the author chooses to write the rspec test, he does something that I thought was a little strange.
describe User do
before(:each) do
#attr = { :name => "Example User", :email => "user#example.com" }
end
.
.
.
it "should require a name" do
no_name_user = User.new(#attr.merge(:name => ""))
no_name_user.should_not be_valid
end
end
Why go through the trouble to merge a blank string to #attr when one could get rid of the :each block statement and simply write:
it "should require a name" do
no_name_user = User.new(:name => "", :email => "user#example.com")
no_name_user.should_not be_valid
end
I know that the author uses the #attr variable to validate the presence of the email address as well, which is one indication as to why he used the block statement -- to me it makes more sense to follow the structure of the second block quote. Still, I have a feeling there is something that I'm missing here.
Another explanation that crossed my mind is that it helps to use the #attr structure when there are lots of keys to be entered, as opposed to this rather simplistic case of only name and email.
Anyone have any input?
It's so there's one standard attributes map that can be used across all the tests. When a test requires that a value isn't there, it's removed.
Personally, I wasn't convinced it was worth it as it sort of obfuscates things (as you discovered), but there it is.
The point is to only have code relevant for the test case in the test. The only relevant attribute when testing that the user isn't valid without a name is the name attribute. That test should not have to know anything about the email attribute.
Let's say you add validation for the presence of a new field – you'd have to update every test where you build up a User without that field. With the attr hash at the top, you just pop the new field in there, and all your tests are fine.
Creating objects for testing is a common enough problem that there are many solutions, and plenty of discussion about which way is best. I'd suggest you look into factories. Machinist and FactoryGirl are two alternatives that work great with Rails.

Rails 3 Factories vs Simple Instantiation

Could someone please explain why Factories are more useful than a simple instantiation during test? More clearly, I do not see the difference between:
before(:each) do
#attr = {
:name => "Example User",
:email => "user#example.com",
:password => "foobar",
:password_confirmation => "foobar"
}
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
and this
before(:each) do
#user = Factory(:user)
end
which has the following Factory:
Factory.define :user do |user|
user.name "Michael Hartl"
user.email "mhartl#example.com"
user.password "foobar"
user.password_confirmation "foobar"
end
The bigger your app gets, the more benefits you gain from factories.
Your solution is great for 2-3 models. But lets say you have an article model where you need valid users to test stuff. Now you have 2 files where you define #attr for users. Now imagine there are even more models that need users, like comments, roles, etc. It gets messy.
It is more convenient to use factories. The benefits are you can define multiple default prototypes. Like an admin user, a normal user, an unregistered user etc.
Furthermore the code is DRY, so if you add a new mandatory field you can add it once to your factory and you are done.
So the answer is: Basically they are the same, but the bigger your app gets the more you need a way to manage all your prototypes.
Because it lets you have in a single place all your required variables plus associations.
Moreover, you can easily create stubs or simply extract attributes without additional code.
The interest is clearer once you have several test files as you want to keep your code DRY.
Sidenote:
You should use 'let' instead of creating each time an instance variable

Rails Seed confusion

I'm having trouble seeding my database using seed.rb, specifically where table relationships are concerned.
Here's a sample of the code:
# seed.rb
user = User.find_or_create_by_login(
:login => "myname",
:email => "myname#gmail.com",
:user_type => "Admin",
:password => "admin",
:password_confirmation => "admin")
project = Project.find_or_create_by_user_id(
:user_id => user.id,
:name => "Test Project")
When project is created (along with other unrelated parameters I've left out from above), user_id is empty. How can I get this to work?
This is the strangest behavior I've seen in something so simple. In my seed file, I have about eight tables being created and some are nested 3-4 levels deep (i.e. user has_many projects; projects has_many tasks, etc.).
When I call user user as above and reference user.id multiple times after that, it only works once! I tried adding [user.reload] before each new record is created but to no avail. I don't imagine this will make sense to anyone, but are there any possibilities here? Thanks all.
I figured out what the problem was. The fields that weren't populating were not listed explicitly in attr_accessible in their respective models. The fields listed were being saved correctly.
Thank you very much for your help everyone.
The code is fine and is the correct syntax for find_or_create. As others have said the most likely problem is that the user is invalid. Trying to call user.reload would make it blow up if the user is invalid and so will kind of make the problem more apparent, but the error you'll get from it will be useless (it'll moan about not being able to find a user without an id).
Unfortunately find_or_create doesn't work as a bang method to raise exceptions if it's invalid, so the best thing to do is probably raising an error and outputting the error after attempting to create the user:
user = User.find_or_create_by_login(:login => "myname")
raise "User is invalid: #{user.errors.full_messages}" unless user.valid?
User created with success? if so..try user.reload if not. that is probably the error
Are you sure your user is saved? I think the right syntax for find_or_create_by_XX is Blog.find_or_create_by_title("Some Blog"). If you need to pass more data you need to use find_or_initialize first and set other data after that separately.
Loosely related thread: Rails find_or_create by more than one attribute?
--edit
Passing data as hash to find_or_create_by_XX seems to work too. Docs are under "Dynamic attribute-based finders" here http://apidock.com/rails/v3.0.0/ActiveRecord/Base
try this
use User.find_or_create instead of User.find_or_create_by_login.
It seems like your user object is not saved.
Or before you assign user.id do user.reload
user = User.find_or_create(
:login => "myname",
:email => "myname#gmail.com",
:user_type => "Admin",
:password => "admin",
:password_confirmation => "admin")
[user.reload]
project = Project.find_or_create_by_user_id( :user_id => user.id,
:name => "Test Project")

How do I test that my table design implements my associations properly?

I have written my basic models and defined their associations as well as the migrations to create the associated tables.
EDIT - Adding emphasis to what I specifically want to test.
I want to be able to test:
The associations are configured as intended
The table structures support the associations properly
I've written FG factories for all of my models in anticipation of having a complete set of test data but I can't grasp how to write a spec to test both belongs_to and has_many associations.
For example, given an Organization that has_many Users I want to be able to test that my sample Organization has a reference to my sample User.
Organization_Factory.rb:
Factory.define :boardofrec, :class => 'Organization' do |o|
o.name 'Board of Recreation'
o.address '115 Main Street'
o.city 'Smallville'
o.state 'New Jersey'
o.zip '01929'
end
Factory.define :boardofrec_with_users, :parent => :boardofrec do |o|
o.after_create do |org|
org.users = [Factory.create(:johnny, :organization => org)]
end
end
User_Factory.rb:
Factory.define :johnny, :class => 'User' do |u|
u.name 'Johnny B. Badd'
u.email 'jbadd#gmail.com'
u.password 'password'
u.org_admin true
u.site_admin false
u.association :organization, :factory => :boardofrec
end
Organization_spec.rb:
...
it "should have the user Johnny B. Badd" do
boardofrec_with_users = Factory.create(:boardofrec_with_users)
boardofrec_with_users.users.should include(Factory.create(:johnny))
end
...
This example fails because the Organization.users list and the comparison User :johnny are separate instances of the same Factory.
I realize this doesn't follow the BDD ideas behind what these plugins (FG, rspec) seemed to be geared for but seeing as this is my first rails application I'm uncomfortable moving forward without knowing that I've configured my associations and table structures properly.
Your user factory already creates an organization by virtue of the Factory Girl association method:
it "should associate a user with an organization" do
user = Factory.create(:johnny)
user.organization.name.should == 'Board of Recreation'
organization = user.organization
organization.users.count.should == 1
end
Take a look at 'log/test.log' after running your spec -- you should see an INSERT for both the organization and the user.
If you wanted to test this without the Factory Girl association, make a factory that just creates the user and make the association in the spec:
it "should associate a user with an organization" do
user = Factory.create(:johnny_no_org)
org = Factory.create(:boardofrec)
org.users.should be_empty
org.users << user
org.users.should include(user)
end
Of course all this is doing is testing whether ActiveRecord is doing its job. Since ActiveRecord is already thoroughly tested, you'll want to concentrate on testing the functionality of your application, once you've convinced yourself that the framework actually does what it's supposed to do.

Resources