I have a question in Rails 4.
On my factories.rb i have something like:
factory :user do
sequence(:name) { |n| "User #{n}" }
sequence(:email) { |n| "user_#{n}#example.org" }
password 'foobar'
password_confirmation 'foobar'
end
The problem, i don't know to add a relation after created user.
When i create a user, and he is a moderator i assign to user the role like that:
u.add_role_id!(11)
I have different table for relationships, with user_id and role_id and on user model i have the method.
def add_role_id!(role)
role_user_relationship.create!(role_id: Role.find(role).id)
end
How i do that on factories.rb?
Now what i now to do:
u = FactoryGirl.create(:moderator)
u.add_role_id!(11)
I want to assign by default on factories.rb
How I do that?!
Thanks everybody!
Manu
Solution:
I have to add these lines:
after(:create) do |u|
u.add_roles_ids!([10,20,30])
end
With the entire code:
factory :user do
sequence(:name) { |n| "User #{n}" }
sequence(:email) { |n| "user_#{n}#mudev.org" }
password 'foobar'
password_confirmation 'foobar'
after(:create) do |u|
u.add_roles_ids!([10,20,30])
end
end
Thanks to Peter Alfvin
You can use FactoryGirl's after callback mechanism described at https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#callbacks to get the comparable behavior to what you're doing now by including the following within your factory definition:
after(:create) {|u| u.add_role_id!(11)
Depending on your validation, you can alternatively do this after(:build) or before(:create).
Related
I'm trying to instanciate with FactoryGirl multiple object (guests who have unique email)
guests = create_list(:guest, 3)
But I get error :
Failure/Error: guests = create_list(:guest, 3)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
Here my factory :
FactoryGirl.define do
factory :guest do
email Faker::Internet.unique.email
firstname 'John'
lastname 'Doe'
password 'test'
password_confirmation 'test'
factory :guest_with_bookings do
email "#{SecureRandom.hex}#test.com"
transient do
bookings_count 2
end
after(:create) do |guest, evaluator|
create_list(:booking, evaluator.bookings_count, guests: [guest])
end
end
end
end
Even with Faker::Internet.unique.email, it's don't working when I try to create more than 1 guest
Use sequence in your factory
FactoryGirl.define do
factory :guest do
sequence(:email) { |n| "name_#{n}#example.com" }
Or in combination with Faker
FactoryGirl.define do
factory :guest do
sequence(:email) { |n| Faker::Internet.unique.email("Nancy_#{n}") }
I have a user factory that be default creates an association for user_document
FactoryGirl.define do
factory :user do |u|
sequence(:first_name) {|n| "firstname-#{n}" }
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
end
I'd like to define a new factory for which the association user_document is null. How do I do this ?
[UPDATE]
When I do this
factory :user_with_no_doc_buyer do
user_document nil
end
I get an error saying :
NoMethodError: undefined method `user_document=' for #<User:0x007f97329c08f8>
and When I do
user_documents nil I get
NoMethodError: undefined method `each' for nil:NilClass
thanks
I'd create two traits.
FactoryGirl.define do
factory :user do
sequence(:first_name) {|n| "firstname-#{n}" }
trait :with_document do
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
trait :without_document do
user_documents []
end
end
end
Then you just call whichever factory you need.
FactoryGirl.create(:user, :with_document) for example
Edit: Have seen you want the with document to be the default. That's easy enough. You can just use the code you have and take my suggestion of a trait without document which you can call when you need it.
Use traits
FactoryGirl.define do
factory :user do |u|
sequence(:first_name) {|n| "firstname-#{n}" }
trait :with_user_document
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
end
end
To create user with document
FactoryGirl.create(:user, :with_user_document)
To create user with null document
FactoryGirl.create(:user)
I have a User model with two roles: member and admin and defaults to member. This model has two required attributes payout_bank and payout_account and they are only required to be present if the role is member.
My FactoryGirl for User has two traits that corresponds to the roles. I then add the two required attributes to the member trait, but when I create an User with member trait with linting turned on FactoryGirl will complain that the required attributes are failing validation because they're blank.
If I turn off linting it will correctly generate a member User with payout_bank and payout_account filled out. What's going on?
Model User.rb
class User < ActiveRecord::Base
validates_presence_of :payout_bank, :payout_account, if: :member?
def set_default_role
self.role ||= :member
end
end
Factory User.rb
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password "password"
trait :member do
sequence(:email) { |n| "member#{n}#email.com" }
role "member"
payout_bank { Faker::Company.name }
payout_account { Faker::Number.number(10) }
end
trait :admin do
sequence(:email) { |n| "admin#{n}#email.com" }
role "admin"
status "active"
end
end
end
Creating user from Factory
FactoryGirl.lint
FactoryGirl.create(:member_user, :member)
Output
FactoryGirl::InvalidFactoryError: The following factories are invalid:
* member_user - Validation Failed: payout_bank cannot be blank, payout_account cannot be blank (ActiveRecord::RecordInvalid)/usr/local/lib/ruby/gems/2.3.0/gems/factory_girl-4.7.0/lib/factory_girl/linter.rb:12:in `lint!'
You have to write the validation tests in spec/models directrory. Something like.
require 'rails_helper'
require "validates_email_format_of/rspec_matcher"
RSpec.describe BankAccount, type: :model do
it "should have a account number" do
bank_a = build(:bank_account, account_no: '')
expect(bank_a).to_not be_valid
end
end
I found out that FactoryGirl.lint is the line that blew up and caught the validation failure. The issue here is that linting will try to create generic User model with no traits. Since my User model defaults to member role, and this role requires payout_bank and payout_account to be present, the validation thus failed.
The solution is to move these two attributes out of the member trait and into the base in the factory.
Something like this
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password "password"
payout_bank { Faker::Company.name } # Move here
payout_account { Faker::Number.number(10) } # Move here
trait :member do
sequence(:email) { |n| "member#{n}#email.com" }
role "member"
end
trait :admin do
sequence(:email) { |n| "admin#{n}#email.com" }
role "admin"
status "active"
payout_bank nil
payout_account nil
end
end
end
My factories.rb file became too big to maintain over time and I'm now trying to split it across many files in factories directory. The problem is that I don't know how to deal with dependencies.
To make a long story short, I'm trying to split my factories in following way. All sequences go to sequences.rb file and each factory definition goes to separate file like so:
factories/sequences.rb
FactoryGirl.define do
sequence :name {|n| "Name #{n}" }
sequence :email {|n| "person#{n}#example.com" }
end
factories/user.rb
FactoryGirl.define do
factory :user do
name
email
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
name
content "Post Content"
user
end
end
When I run tests I get name is not defined error. I can deal with this by passing a block to each association (e.g. name, email, user and so on) mention but it seems to be ugly and not DRY.
Is there way to let factory_girl know sequence in which files should be loaded?
to deal with complex dependencies when it's not possible to fix this issue with changing load sequence of files?
You can simply achieve the result with generate method:
# factories/sequences.rb
FactoryGirl.define do
sequence(:email) { |n| "person#{n}#example.com" }
end
# factories/user.rb
FactoryGirl.define do
factory :user do
email { generate(:email) }
password '12345678'
end
end
Then try it:
FactoryGirl.create :user
=> #<User:0x007fa99d2ace40
id: 1,
email: "person1#example.com",
. . .>
Sequences Documentation for more details.
I do this in this way:
Create separate folder for shared factories. It should in same level as 'factories' folder.
factories
shared_factories
Create shared file, ex. shared_factories/sequences.rb
Import sequences.rb to every factory file.
require_relative '../shared_factories/sequences'
The full example:
https://gist.github.com/alexkojin/6a2d70f84ff91c37315d1d3edb0d8e6b
You can do this:
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "Name #{n}" }
sequence(:email) {|n| "person#{n}#example.com" }
end
end
FactoryGirl.define do
factory :post do
content "Post Content"
user { create :user }
end
end
There are a variety of ways to structure your factory girls factories such as traits, inheritance, callbacks, etc. I just recently learned about traits and it is really wonderful:
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "Name #{n}" }
sequence(:email) {|n| "person#{n}#example.com" }
end
trait :with_post do
after(:create) { |user| create :post, user: user }
end
end
FactoryGirl.define do
factory :post do
content "Post Content"
user { create :user }
end
end
# and in your specs you will do this:
let!(:blogger) { create :user, :with_post }
It is very worthwhile to invest some time reading through the entire documentation (a 30 mins read perhaps?). Thereafter, you will have much better ideas to refactor your factories.
Reference link: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
Using FactoryBot, I'm having trouble creating a admin Factory in my specs because every user is assigned a default role of user in a before_create callback. This means that any role I assign a factory will be changed to user when the callback happens.
What I really want to do is something like this:
Inside my spec
admin = FactoryBot.create(:user)
admin.role = 'admin'
The second line, admin.role = 'admin' doesn't do anything. Any ideas?
I'm open to better ways of doing this as well.
There might be a way of reassigning the value to a FactoryBot (formerly FactoryGirl) instantiation, but RSpec negates the need:
describe User do
let(:user) { FactoryBot.create(:user) }
context 'when admin' do
let(:user) { FactoryBot.create(:user, admin: true) }
# ...
end
end
Try using a trait:
factory :user do
sequence(:username) { |n| "User ##{n}"}
role 'user'
trait :is_admin do
role 'admin'
end
end
Usage:
FactoryBot.create(:user, :is_admin)
Or eventually an after(:create):
factory :user do
sequence(:username) { |n| "user ##{n}"}
role 'user'
end
factory :user_admin, class: User do
after(:create) { |user| user.role = 'admin'; user.save } # don't know if the .save is necessary here
sequence(:username) { |n| "User Admin ##{n}"}
end
Just another way
# Steal some code from MrYoshiji at first.
factory :user do
sequence(:username) { |n| "User ##{n}"}
role 'user'
# Then a separate factory inside
factory :admin do
role 'admin'
end
end
# Use
FactoryBot.create(:admin)