I have three factories that i want to DRY up. They look like this:
factory :sequenced_stamps_by_years, class: Stamp do
...
sequence(:day_date) { |n| n.years.ago }
end
factory :sequenced_stamps_by_months, class: Stamp do
...
sequence(:day_date) { |n| n.months.ago }
end
factory :sequenced_stamps_by_weeks, class: Stamp do
...
sequence(:day_date) { |n| n.weeks.ago }
end
How can i dry this up? I want to be able to create them something like this:
FactoryGirl.create_list(:sequenced_stamps_by_x, 4, x: "weeks") ## <- So that i can decide whether I want weeks, days, years, or months ago.
Is this possible?
If you don't favor the inheritance approach, there is an alternative using a parameter. Basically:
factory :stamps do
ignore do
interval :years # possible values => :years, :months, :weeks
end
sequence(:date_date) { |n| n.send(interval).ago }
# rest of attributes here
end
Now you can do:
FactoryGirl.create(:stamps, :interval => :months)
or
FactoryGirl.create(:stamps)
which defaults to years.
All this you can find in Factory Girl transient attributes
Factories can inherit from other factories. Therefore you can do something like:
factory :stamps do
# common attributes here
.....
factory: sequenced_stamps_by_years do
sequence(:day_date) { |n| n.years.ago }
end
factory: sequenced_stamps_by_months do
sequence(:day_date) { |n| n.months.ago }
end
factory: sequenced_stamps_by_weeks do
sequence(:day_date) { |n| n.weeks.ago }
end
end
Related
I have two FactoryBots => user and post
And post has user_id column
FactoryBot.define do
factory :user do
sequence(:name) { |n| "user#{n}" }
end
end
FactoryBot.define do
factory :post do
sequence(:title) { |n| "title#{n}" }
sequence(:user_id) { |n| n }
end
end
I want to make 10 users, and 100 posts (10posts per each user) so i
tried .times method like this =>
10.times do
FactoryBot.define do
factory :post do
sequence(:title) { |n| "title#{n}" }
sequence(:user_id) { |n| n }
end
end
end
This not worked because of Factory already registered error
FactoryBot.define do
10.times do
factory :post do
sequence(:title) { |n| "title#{n}" }
sequence(:user_id) { |n| n }
end
end
end
This not worked too.
FactoryBot.create_list(:user, 10)
10.times do
FactoryBot.create_list(:post, 10)
end
This makes FactoryBot to make post num 11, user num 11 so foriegn key error occurs.
How can i use create_list method many times?
Just i want to make is, 10 posts per 10 users.
If you don't need to reference any of those users and/or posts afterward, then you can just 10 times create 10 posts using the same user per every 10 posts;
10.times do
create_list(:post, 10, user: create(:user))
end
I am running into issues with RSpec and FactoryBot and some factories because different tests try to generate the same factories which is not possible due to uniqueness constraints - and also logically wrong.
I would like to somehow centrally create the factory objects, so that each test has access to them instead of having "before..." or "let..." blocks / directives in different test files.
How to achieve that?
Example problem:
I have these factories:
FactoryBot.define do
factory :office_tk, class: Shelf do
name { 'Office tk' }
association :user, factory: :me
end
end
and
FactoryBot.define do
factory :hobbit, class: Book do
title { 'The Hobbit' }
year { 1937 }
rating { 5 }
condition { 4 }
synopsis { "<p>#{Faker::Lorem.paragraphs(number: 30).join(' ')}</p>" }
association :book_format, factory: :hardcover
association :user, factory: :me
genres { [ create(:fiction) ] }
association :shelf, factory: :office_tk
end
end
and
FactoryBot.define do
factory :me, class: User do
name { 'tkhobbes' }
email { 'me#example.com' }
password { 'password' }
password_confirmation { 'password' }
admin { true }
activated { true }
activated_at { Time.zone.now }
end
end
When I try to run this test - it complains because it tries to create :me twice (which is not possible, as each user / email can only exist once - apart from the fact that :me should be the same object for both the book and the shelf).
RSpec.describe Shelf, type: :model do
before(:all) do
#book = create(:hobbit)
end
it 'shows the right shelf for a book' do
expect(#book.shelf.name).to eq('Office tk')
end
end
I have a ruby app that I'm using rspec and factorygirl with, and I'm having trouble building a factory. Let me explain with an example:
I've got two factories which are creating registrant.user_demographic like below:
FactoryGirl.define do
factory :registrant do
first_name { "Johnny #{Faker::Name.initials}" }
(...)
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant)
end
end
end
FactoryGirl.define do
factory :user_demographic do
user { create(:registrant) }
phone "1234567890"
(...)
end
end
Now I want to have registrant without phone number sth like: registrant.user_demographic.phone == ''. I've tried with transient defined in user_demographic but it won't work:
FactoryGirl.define do
factory :registrant do
first_name { "Johnny #{Faker::Name.initials}" }
(...)
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant)
end
trait :without_phone do
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant, phone_present: false)
end
end
end
end
FactoryGirl.define do
factory :user_demographic do
(...)
transient do
phone_present { true }
end
phone { '1234567890' if phone_present }
end
end
I am taking over a project that has a question / answer section. I am adding a syndication feature and would like to have a relationship where a question has_one: syndicatable_question.
For my factrory, I have an API like sq = FactoryGirl.create(:question, :with_syndication ) for the simple case and would like something like sq = FactoryGirl.create(:question, :with_syndication(syndicatable_location_id: 345)) but this doesn't work. How could I pass an option / argument for a trait? What changes would I need to make to the factory?
My factory currently looks like this:
FactoryGirl.define do
factory :question, class: Content::Question do
specialty_id 2
subject { Faker::Lorem.sentence }
body { Faker::Lorem.paragraph }
location_id 24005
trait :with_syndication do
after(:create) do |q, options|
create(:syndicatable_question, question_id: q.id, syndicatable_location_id: q.location_id)
end
end
end
end
You need to add transient block to your trait
FactoryGirl.define do
factory :question, class: Content::Question do
specialty_id 2
subject { Faker::Lorem.sentence }
body { Faker::Lorem.paragraph }
location_id 24005
transient do
syndicatable_location_id 24005
end
trait :with_syndication do
after(:create) do |q, options|
create(:syndicatable_question, question_id: q.id, syndicatable_location_id: options.syndicatable_location_id)
end
end
end
end
FactoryGirl.create(:question, :with_syndication, syndicatable_location_id: 345)
Transient Attributes
https://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
I've got two traits in my factory, and I want one of them to be included when I create the object, without it defaulting to one (so randomly pick the trait). Here's what I'm doing:
FactoryGirl.define do
factory :follow_up do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
phone { Faker::PhoneNumber.cell_phone.gsub(/[^\d]/, '').gsub(/^1/, '2')[0..9] }
email { Faker::Internet.email }
email_preferred true
consent false
if [1, 2].sample == 1
by_referral
else
by_provider
end
trait :by_referral do
association :hospital
association :referral
source { FollowUp::REFERRAL}
end
trait :by_provider do
association :provider
source { FollowUp::PROVIDER }
end
end
end
However, it seems to be ignoring that if statement and going straight to by_provider trait. Anyone know how I'd do this?
Use an ignore block.
FactoryGirl.define do
factory :follow_up do
text "text"
author "Jim"
ignore do
if x == 1
by_referral
else
...
end
end
end
end