FactoryGirl: set has_many association on creation - ruby-on-rails

This issue is happening in an upgrade that I'm working from Rails 3.2 to Rails 4.2. It might be related to the many things that Rails 4 broke.
I have the following factory:
factory :account do
sequence(:email) {|n| "email#{n}#example.org" }
sequence(:name) {|n| "Name #{n}" }
end
This model has an has_many association called ips. At a specific test, I need to set the account with an ip. In Rails 3.2 I was able to do this:
FactoryGirl.create(:account, :ips => [FactoryGirl.create(:ip)])
But in Rails 4 I get an exception:
ActiveRecord::RecordInvalid: Validation failed: Ips is invalid
I was able to verify that I'm not longer able to override an has_many association. Example:
account = FactoryGirl.build(:account)
account.ips = [FactoryGirl.create(:ip)]
account.save!
It will also throw an exception.
As a side note, using << works fine but that's not what I want since I want to delete any IPs that are assigned to the account and only set the new one.
What is the proper way of doing this in Rails 4?

For this I would recommend using a trait with an after create. So it would look something like this:
FactoryGirl.define do
factory :account do
sequence(:email) {|n| "email#{n}#example.org" }
sequence(:name) {|n| "Name #{n}" }
end
trait :with_ip do
transient do
ip_count 5
end
after(:create) do |account, evaluator|
create_list(:ip, evaluator.ip_count, account: account)
end
end
end
See this for further reference: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

Related

FactoryGirl—Add a has_many record to a factory

With FactoryGirl I know I can make singular attributes like this:
FactoryGirl.define do
factory :company do
trading_name { FFaker::Company.name }
legal_type { legal_types.keys.sample }
updated_by { FactoryGirl.create(:user) }
...
Now I'm trying to add a user, but not sure how to do it.
I tried:
users { FactoryGirl.create(:user) }
But got an error on an each loop because users wasn't the collection of users, it was changed to be that single user.
So how do you create has_many objects in a factory? How do you make sure they are linked to each other? Some people do an after(:create) but I'd like to make the users before saving the Company record.
Association section of Factory Girl documentation says to use after(:create). In your case code will be:
FactoryGirl.define do
factory :company do
...
after(:create) do |company|
create_list(:user, 3, company: company)
end
Update: In a case you have after_save validation in User model you may use after(:build) hook instead of after(:create):
after(:build) do |company|
company.users << build(:user)
end

has_many relationship using FactoryGirl

I'm trying to create instance with has_many relationship using FactoryGirl factories and fail.
I have two classes: Computers and NetworkCards. Each Computer can have many NetworkCards.
Defined two ActiveRecords:
1.
class Computer < ActiveRecord::Base
has_many :network_cards
end
2.
class Article < ActiveRecord::Base
belongs_to: computer
end
Defined the following factories:
1.
factory :computer do
sequence(:name) { |n| "PC_#{n}" }
transient do
network_cards_count 1
end
after(:create) do |computer|
create_list(:network_card, evaluator.network_cards_count, computer: computer)
done
end
2.
factory :network_card do
sequence(:name) { |n| "NC_#{n}" }
sequence(:type) { |n| "TYPE_#{n}" }
end
Now, when creating a computer in a RSpec test I get a weird behavior I can't explain.
#computer_1 = FactoryGirl.create(:computer)
#computer_1.network_cards.size #Expect 1. Got 0. Why?
#computer_1.reload
#computer_1.network_cards.size # Got 1
Any ideas what am I missing?
Try this:
after(:build) do |computer, evaluator|
computer.network_cards << build_list(:network_card, evaluator.network_cards_count, computer: computer)
end
factory :computer do
sequence(:name) { |n| "PC_#{n}" }
transient do
network_cards_count 1
end
after(:create) do |computer, evaluator|
create_list(:network_card, evaluator.network_cards_count, computer: computer)
computer.reload #<----- solves the issue
end
end

How should I set up complex Factories in Rails?

In my rails application I have three objects I would like to model for a particular Rspec test. These are Organizations, Users, and Sales Opportunities.
class Organization
has_many :users
end
class User
has_many :sales_opportunities
end
An Organization has to have a unique name, and for the authorisation/authentication aspects of my app to work, both users need to belong to the same Organization. For the purposes of this test I want one User to be admin and one to be non-admin.
This works successfully in other parts of my application (e.g. sign up - the first user is automatically the admin, subsequent users are not, and for viewing links etc on pages). However what I now need to do is create two users (both belong_to one organization), make one of them the admin, and then when I delete the sales_opportunity non-admin user any sales_opportunities that belonged to this non-admin user should transfer ownership to the admin (for re-allocation).
FWIW I am using associations for some of the other tests - but in this case I can't create two users associated with one organization, so I've used:
before(:create) do |organization|
organization.users << FactoryGirl.build(:user ...admin params)
organization.users << FactoryGirl.build(:user ...non_admin params)
end
in the factory for the organization to build 2 users who both belong to one organization and have different characteristics. This works well.
If I try to add another before(:create) to the factory to then build a sales_opportunity it fails (because the users aren't created yet I believe). After(:create) statements also fall flat. I've also tried defining the non_admin user in the tests themselves (e.g. by using a let(:non_admin) = Organization.users.last and then non_admin.sales_opportunities.create statement), but this fails to product any sales opportunities (not sure what it does).
Are there any good resources on how to build tests like this? I can write the code to solve the problems in no time - I seem to waste a huge amount of time writing the tests first. I'm sure these are not particularly DRY either looking through my factories.
Any help would be appreciated.
in rspec/factories/sales_opportunities.rb:
FactoryGirl.define do
sequence(:name) { |n| "Sales Oppotunity - #{n}" }
factory :sales_opportunity do
user
name { generate(:name) }
# add more attributes here
end
end
in rspec/factories/users.rb:
FactoryGirl.define do
factory :user do
organization
sequence(:name) { |n| "Test user #{n}" }
sequence(:email) { |n| "test.user#{n}#example.com" }
password "password"
admin false
# add more attributes here
end
factory :admin_user, parent: :user do
admin true
end
factory :user_with_sales_opportunities, parent: :user, traits: [:with_sales_opportunity]
factory :admin_user_with_sales_opportunities, parent: :admin_user, traits: [:with_sales_opportunity]
trait :with_sales_opportunity do
ignore do # instead of ignore use transient if you're using factory girl 4.3 or above
sales_opportunities_count 5
end
after(:create) do |user, evaluator|
sales_opportunities { FactoryGirl.create_list(:sales_opportunity, evaluator.sales_opportunities_count, user: user) }
end
end
end
in rspec/factories/organizations.rb:
FactoryGirl.define do
sequence(:name) { |n| "Organization - #{n}" }
factory :organization do
name { generate(:name) }
ignore do
users_count 5
admin_users_count 2
sales_opportunities_count 5
end
after(:create) do |organization, evaluator|
users { FactoryGirl.create_list(:user_with_sales_opportunities, evaluator.users_count, organization: organization, sales_opportunities_count: sales_opportunities_count) + FactoryGirl.create_list(:admin_user_with_sales_opportunities, evaluator.users_count, organization: organization, sales_opportunities_count: sales_opportunities_count) }
end
# add more attributes here
end
end
Now you can:
let(:organization) { FactoryGirl.create(:organization, users_count: 4, admin_users_count: 3, sales_opportunities_count: 4) }
Im not sure exactly what you're trying to do, as you seem to be deleting the sales opportunity that you want to transfer?
If you think its an rspec you could instead try using after(:build)
Something like:
factory :user, class: User do
sequence(:email) { |n| "user#{n}#example.org" }
password "foobar"
# Standard user stuff etc
trait :admin do
after(:build) do |user|
user.set_admin
end
end
end
factory :organisation, class: Organisation do
name "Super Duper"
after(:build) do |organization|
organization.users << FactoryGirl.build(:user)
organization.users << FactoryGirl.build(:user, :admin)
end
end
I feel like you may have a modeling issue though. Shouldn't your sales opportunities be standalone?
ie:
class SalesOpportunity
field :name, type: String
field :phone, type: Integer
belongs_to :user
end
class User
field :name, type: String
field :phone, type: Integer
has_many :salesopportunities
end
This way you can delete a user object and persist the sale opportunity?

Avoid duplicating records in loop

I have two factories for my Company and Employee models respectively. Employee is in a belongs_to relationship with Company. Here's my two factories:
factory :company do
name "foo company"
end
factory :employee do
company
name 'Willy Bytes'
end
There are some occasions where I need to traverse an array of data and populate new Employee records accordingly to test against different conditions in my specs. To illustrate, I'm using the following specs to test one of my inclusion validations:
it "should be valid if they like red blue or green" do
["red","blue","green"].each do |c|
FactoryGirl.build(:employee, :favourite_colour => c).should be_valid
end
end
it { FactoryGirl.build(:upload, :favourite_colour => "other").should_not be_valid }
However, I have a uniqueness constraint on the parent companies name field which yields an error when I attempt to build the record. Is there an intelligent way to resolve/avoid this problem? I run into these types of specs a lot and typically what I would do is define a single Company factory and assign that to each record in the loop, but it doesn't feel intuitive and results in a lot of repetition. Is a sequence the only other way around this?
You can use factory_girl's sequence generator. Your factory will look like this.
factory :company do
sequence(:name) {|n| "company #{n}"}
end
One line:
sequence(:name) { |n| "Company #{n}" }
I found exactly what I was looking for here: Find or create record through factory_girl association
Overriding the default build / new behaviour using the initialize_with method seems to be the way to go:
factory :company do
initialize_with { Company.find_or_create_by_name("Foo Company") }
end

FactoryGirl association model trouble: "SystemStackError: stack level too deep"

I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to state a Factory association model but I am in trouble.
I have a factories/user.rb file like the following:
FactoryGirl.define do
factory :user, :class => User do
attribute_1
attribute_2
...
association :account, :factory => :users_account, :method => :build, :email => 'foo#bar.com'
end
end
and a factories/users/account.rb file like the following:
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
end
end
The above example works as expected in my spec files, but if in the factory :users_account statement I add the association :user code so to have
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
association :user
end
end
I get the following error:
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
How can I solve that problem so to access associated models from both sides\factories (that is, in my spec files I would like to use RoR association model methods like user.account and account.user)?
P.S.: I read the Factory Girl and has_one question and my case is very close to the case explained in the linked question. That is, I have an has_one association too (between User and Users::Account classes).
According to the docs, you can't just put both sides of the associations into the factories. You'll need to use their after callback to set an object(s) to return.
For instance, in the factories/users/account.rb file, you put something like
after(:build) do |user_account, evaluator|
user_account.user = FactoryGirl.build(:user, :account=>user_account)
end
For has_many associations, you'll need to use their *_list functions.
after(:build) do |user_account, evaluator|
user_account.users = FactoryGirl.build_list(:user, 5, :account=>user_account)
end
Note: I believe the example in the docs is a bit misleading it doesn't assign anything to the object. I believe it should be something like (note the assignment).
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
user.posts = FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
Spyle's excellent answer (still working with Rails 5.2 and RSpec 3.8) will work for most associations. I had a use case where a factory needed to use 2 different factories (or different traits) for a single has_many association (ie. for a scope type method).
What I ended up coming up with was:
# To build user with posts of category == 'Special' and category == 'Regular'
after(:create) do |user, evaluator|
array = []
array.push(FactoryBot.create_list(:post, 1, category: 'Regular')
array.push(FactoryBot.create_list(:post, 1, category: 'Special')
user.posts = array.flatten
end
This allowed the user to have 1 post of category 'Regular' and 1 post of category 'Special.'

Resources