Why is my factory not recognizing my pool association? - ruby-on-rails

we defined the following model
class UserPool < ActiveRecord::Base
belongs_to :pool
belongs_to :user
validates :pool, presence: true
validates :user, presence: true
def self.created(date)
where("DATE(created_at) = ?", date)
end
end
and the following Factroy
FactoryGirl.define do
factory :user_pool do
pool
user
factory :paid_user_pool do
money_on_pool 10
end
end
end
When I run the following test I recive an error
describe "obtain users_pools created at specifict time" do
before do
users = create_list :user, 3, active: false
user4 = create :user
#pool = create :pool
users.each do |user|
create :user_pool, user: user, pool: #pool, created_at: 1.days.ago
end
create :user_pool, user: user4, pool: #pool
end
it "should return just users_pools created at specifict time" do
users_pools = #pool.user_pools.created( 1.days.ago )
users_pools.count.should eq 3
end
end
Error:
ActiveRecord::RecordInvalid:
The validation failed: Pool can’t be blank
Why is my factory not recognizing my pool association?

When creating a factory, you list the attributes with predefined values. Otherwise, you can omit them from the factory and explicitly state it in the test (during create).
# Example for :user
factory :user do
sequence(:name) { |n| "Test User #{n}" }
end
Now, when you call create(:user), the default name will include a number that increases by 1 for every user created. See #sequence and "Sequences" for more information.
Now onto your specific example. You can create the user_pool factory one of two ways.
# No default attributes, requires explicit assignment
factory :user_pool do
end
create(:user_pool, user: user, pool: #pool)
# Default attributes can be overridden during test
# Requires you to create :user and :pool factories
factory :user_pool do
after(:build) do |user_pool|
user_pool.user = create(:user)
user_pool.pool = create(:pool)
end
end
When you build an ActiveRecord object, it is not committed to the database. You are allowed to leave out required attributes. After building the object, two are created (user, pool), and assigned to the correct user_pool attributes. See "Callbacks" in the docs for more information.
If you want to create #pool in your test, you can still do the following. It will override the default pool and user attributes.
user_pool = create(:user_pool, user: user, pool: #pool)

Related

Rspecs for model and before_create methods

I have model name: UserApplicationPatient.
That model having two associations:
belongs_to :patient
belongs_to :customer
before_create :set_defaults
private
def set_defaults
self.enrol_date_and_time = Time.now.utc
self.pap = '1'
self.flg = '1'
self.patient_num = "hos_#{patient_id}"
end
Factories of UserApplicationPatient
FactoryBot.define do
factory :user_application_patient do
association :patient
association :customer
before(:create) do |user_application_patient, evaluator|
FactoryBot.create(:patient)
FactoryBot.create(:customer)
end
end
end
Model spec:
require 'spec_helper'
describe UserApplicationPatient do
describe "required attributes" do
let!(:user_application_patient) { described_class.create }
it "return an error with all required attributes" do
expect(user_application_patient.errors.messages).to eq(
{ patient: ["must exist"],
customer: ["must exist"]
},
)
end
end
end
This is the first time I am writing specs of models.
Could someone please tell me how to write specs for set_defaults before_create methods and factories what I have written is correct or not.
Since you are setting default values in the before_create hook, I will recommend validating it like this
describe UserApplicationPatient do
describe "required attributes" do
let!(:user_application_patient) { described_class.create }
it "return an error with all required attributes" do
# it will validate if your default values are populating when you are creating a new object
expect(user_application_patient.pap).to eq('1')
end
end
end
To test your defaults are set, create a user and test if the defaults are set.
You definitely don't want to use let!, there's no need to create the object until you need it.
Since we're testing create there's no use for a let here at all.
How do we test Time.now? We can freeze time!
I assume patient_id should be patient.id.
Here's a first pass.
it 'will set default attributes on create' do
freeze_time do
# Time will be the same everywhere in this block.
uap = create(:user_application_patient)
expect(uap).to have_attributes(
enrol_date_and_time: Time.now.utc,
pap: '1',
flg: '1',
patient_num: "hos_#{uap.patient.id}"
)
end
end
it 'will not override existing attributes' do
uap_attributes = {
enrol_date_and_time: 2.days.ago,
pap: '23',
flg: '42',
patient_num: "hos_1234"
}
uap = create(:user_application_patient, **uap_attributes)
expect(uap).to have_attributes(**uap_attributes)
end
These will probably fail.
Defaults are set after validation has taken place.
Existing attributes are overwritten.
What is patient_id?
We can move setting defaults to before validation. That way the object can pass validation, and we can also see the object's attributes before writing it to the database.
We can fix set_defaults so it doesn't override existing attributes.
Time.now should not be used, it is not aware of time zones. Use Time.current. And there's no reason to pass in UTC, the database will store times as UTC and Rails will convert for you.
belongs_to :patient
belongs_to :customer
before_validation :set_defaults
private
def set_defaults
self.enrol_date_and_time ||= Time.current
self.pap ||= '1'
self.flg ||= '1'
self.patient_num ||= "hos_#{patient.id}"
end
We can also make your factory a bit more flexible.
FactoryBot.define do
factory :user_application_patient do
association :patient, strategy: :create
association :customer, strategy: :create
end
end
This way, the patient and customer will be created regardless whether you build(:user_application_patient) or create(:user_application_patient). This is necessary for user_application_patient to be able to reference its patient.id.
In general, don't do things at create time.

With Rails 6.2 how do I detect if I've passed an empty array into my factory?

I’m using Rails 6.2. I have a user factory (FactoryBot 6.2) that I set up like so
FactoryBot.define do
factory :user do
…
after(:build) do |user, vars|
…
if vars.addresses.nil?
user.addresses = build_list(:address, 1, user: user)
end
end
My user model has
has_many :addresses
And my address model has
belongs_to :user
The problem with all of the above is that these calls behave the same way
create(:user, addresses: [])
And
create(:user)
In the first case, I want an empty array to be assigned to the user object. In the second case, I would like the addresses auto-created using my address factory. How do I distinguish between passing in an empty array and not passing in anything at all?
I tend to use transients for this sort of thing to make it clear what I'm trying to do.
So something like:
FactoryBot.define do
factory :user do
transient do
with_addresses { true }
end
...
after(:build) do |user, vars|
if vars.with_addresses == true
user.addresses = build_list(:address, 1, user: user)
elsif vars.with_addresses
user.addresses = vars.with_addresses
end
end
end
end
and then:
create(:user) # Builds one address for the user
create(:user, with_addresses: []) # Starts with no addresses
create(:user, with_addresses: [a1, a2]) # Starts with two addresses
And you could make it a little smarter and more flexible so that you could say things like:
create(:user, with_addresses: 0) # To create with none
create(:user, with_addresses: 6) # To get 6 from your address factory
You'd just need a couple small tweaks to the after(:build) block.

Factory Girl self-join throwing validation errors

I'm trying to test a system for creating article translations where there is a self-join on the publications table. I've created a factory that will create multiple translations and associate them with a 'parent' article.
Using Rails 5 with factory_girl 4.7.0, rspec, and Database_cleaner
All actions work as expected, but creating a test is the problem
Here's the relevant model validations and methods:
# models/publication.rb
has_many :translations, class_name: "Publication", foreign_key: "translation_id", dependent: :nullify
belongs_to :translation, class_name: "Publication", optional: true
validates :language, uniqueness: { scope: :translation_id }, if: :is_translation?
def is_translation?
!translation.nil?
end
Factory (irrelevant code omitted):
# spec/factories/publication.rb
factory :publication, aliases: [:published_pub] do
title 'Default Title'
language 'EN'
published
after(:build) do |object|
create(:version, publication: object)
end
#-- This is where I suspect the problem stems from
trait :with_translations do
association :user, factory: :random_user
after(:build) do |object|
create_list(:translation, 3, {user: object.user, translation:object})
end
end
end
factory :translation, class: Publication do
sequence(:title) { |n| ['French Article', 'Spanish Article', 'German Article', 'Chinese Article'][n]}
sequence(:language) { |n| ['FR', 'ES', 'DE', 'CN'][n]}
user
end
And a basic test:
# spec/models/publication_spec.rb
before(:each) do
#translation_parent = create(:publication, :with_translations)
#pub_without_trans = create(:publication, :with_random_user)
end
scenario 'is_translation?' do
# No actual test code needed, this passes regardless
end
scenario 'has_translations?' do
# No actual test code needed, this (and subsequent tests) fail regardless
end
Finally, the error:
Failure/Error: create_list(:translation, 3, {user: object.user, translation:object})
ActiveRecord::RecordInvalid:
Validation failed: Language has already been taken
The first test passes (and the publication object with translations is created correctly)but any subsequent test fails. The issue is that I have a uniqueness validation scoped to translation_id and it appears that factorygirl is trying to add the generated translations to an already existing publication instead of creating an entirely new publication.
Any help is appreciated!
Solved!
The issue was that the sequence iterator in the translation factory was not resetting to 0 after each test. So after test 1, it started trying to access an array index that didn't exist. After it did that one more time, it triggered the validation and the tests failed!
The solution is not cute, but it's good enough for the time being
sequence(:language) do |iteration|
array = ['FR', 'ES', 'DE', 'CN']
# Returns a number between 0 and array.length
array[iteration%array.length]
end
sequence(:title) do |iteration|
array = ['French Article', 'Spanish Article', 'German Article', 'Chinese Article']
# Returns a number between 0 and array.length
array[iteration%array.length]
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?

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