I'm trying to run some tests on a model "Click".
# models/click_spec.rb
describe Click do
it "should have a valid constructor" do
FactoryGirl.create(:click).should be_valid
end
end
The objective is that the model uses two tables that have the same country. I don't want to use a sequence for the country (as I found for Emails).
But it raise this error:
Validation failed: Name has already been taken, Slug has already been taken
The problem is that it seems that it create twice the country [name:"United States", slug:"us"]
Here's the factories used.
# factories/countries.rb
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
end
end
# factories/offers.rb
FactoryGirl.define do
factory :offer do
association :country, factory: :country
# Other columns
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
association :country, factory: :country
# Other columns
end
end
# factories/clicks.rb
FactoryGirl.define do
factory :click do
association :offer, factory: :offer
association :user, factory: :user
# Other columns
end
end
and the model of Country:
class Country < ActiveRecord::Base
validates :name, :slug,
presence: true,
uniqueness: { case_sensitive: false }
validates :slug,
length: { is: 2 }
end
I've tried to change the association strategy to something like this:
association :country, factory: :country, strategy: :build
But it raise this error:
Validation failed: Country can't be blank
Any idea?
Thanks,
As per the shared code,
when you call FactoryGirl.create(:click),
it will go to execute factory :click where it finds association :offer, factory: :offer
which in turn calls factory: :offer where you create a country with name "United States" and slug "us" for the first time.
Again, in factory :click, it finds association :user, factory: :user which in turn calls factory: :user where you create a country again with the same name "United States" and slug "us" for the second time.
Issue #1: Validation failed: Name has already been taken, Slug has already been taken
The above error is because of the uniqueness constraint on Country model for name and slug.
Issue #2: Validation failed: Country can't be blank
When you do association :country, factory: :country, strategy: :build then strategy: :build only creates an instance of Country, it does create a record in database.
The Country can't be blank error is because you didn't create a country record in the database for user and offer. And you must be having a validation presence: true in these two models for country OR schema level check of not null.
Related
error message:
Failure/Error: let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
So, as I suppose, the :rubric factory is tried to be created second
time (because the :rubric_in_grenoble should have association to the
same culture as :rubric_in_wroclaw). How should I change this code to
not create again same factory model, but to associate it with the
existing one?
I use Database Cleaner
I'm writing test to the model Population.
class Population < ApplicationRecord
belongs_to :province
belongs_to :culture
validates_presence_of :province, :culture
#some methods
end
It's associated to Province
class Province < ApplicationRecord
enum terrain: [:sea, :plains, :hills, :mountains]
validates :terrain, presence: true
validates :name,
presence: true,
uniqueness: true,
format: {
with: /\A[A-Za-z ]{3,30}\z/,
message: "province name has to contain letters and spaces only"
}
has_many :populations
##some methods
end
And to Culture
class Culture < ApplicationRecord
before_validation :downcase_name
validates :name, presence: true,
format: {
with: /\A[a-z]{3,20}\z/,
message: "culture name has to contain letters only"
},
length: { minimum: 3, maximum: 20 }
##many-to-many associations to different model - tested correctly
has_many :populations
end
In my test suite I use FactoryGirl and RSpec. Rails 5.0.1. I've got also simplecov installed (I'm 99.99% sure it's not interfering, but it's better to highlight this) and DatabaseCleaner works properly.
Culture and Province models are tested and everything's fine. In my FactoryGirl file for Populations there is:
FactoryGirl.define do
factory :rubric_in_wroclaw, class: "Population" do
association :province, factory: :wroclaw
association :culture, factory: :rubric
quantity 10
end
factory :javan_in_wroclaw, class: 'Population' do
association :province, factory: :grenoble
association :culture, factory: :javan
quantity 15
end
factory :rubric_in_grenoble, class: 'Population' do
association :province, factory: :grenoble
association :culture, factory: :rubric
quantity 25
end
end
And in my population_spec.rb file I've got:
require 'rails_helper'
RSpec.describe Population, type: :model do
let(:rubric_in_wroclaw){ create(:rubric_in_wroclaw) }
let(:javan_in_wroclaw){ create(:javan_in_wroclaw) }
let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
let(:pops){ [ rubric_in_wroclaw, javan_in_wroclaw, rubric_in_grenoble] }
describe '#validations' #shoulda-matchers validations - GREEN
describe '#methods' do
describe '#global_population' do
context 'without any pop' #without any fixture created
context 'with many pops' do
before { pops } <--- there is an error
it 'is equal to 50' do
expect(Population.global_population).to eq 50
end
end
end**
end
describe '#factories' #each factory is tested separately - everything's ok
end
Repeated question (because post is very long)
So, as I suppose, the :rubric factory is tried to be created second
time (because the :rubric_in_grenoble should have association to the
same culture as :rubric_in_wroclaw). How should I change this code to
not create again same factory model, but to associate it with the
existing one?
Short Answer:
Probably you are giving duplicate value to your Province.name field. The sequence should fix the problem.
Long Answer:
Let's look at the error again.
Failure/Error: let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
The exception tells us we are trying to create a record with a name twice also
I can see a uniqueness validation in Province.name.
class Province < ApplicationRecord
enum terrain: [:sea, :plains, :hills, :mountains]
validates :terrain, presence: true
validates :name,
presence: true,
uniqueness: true,
format: {
with: /\A[A-Za-z ]{3,30}\z/,
message: "province name has to contain letters and spaces only"
}
has_many :populations
##some methods
end
You didn't paste the Province factory, but I expect factory is something like this:
FactoryGirl.define do
factory :my_factory, class: "Province" do
name 'blabla'
# other fields
end
end
So we have to use sequence instead of static value.
FactoryGirl.define do
factory :grenoble, class: "Province" do
sequence(:name) { |i| "BLABLA#{(i+64).chr}" }
# other fields
end
end
Assign same Province
RSpec.describe Population, type: :model do
let(:my_province){ create(:my_province) }
let(:rubric_in_wroclaw){ create(:rubric_in_wroclaw, province: :my_province) }
let(:javan_in_wroclaw){ create(:javan_in_wroclaw, province: :my_province) }
end
I have model Student, which has_one :account.
All input-related data is stored inside of account. Student model just plays it's role when it comes to relations (some other models belong to student).
Problem: I can't test it with factory girl.
factory :student do
end
As I can't define anything besides it.
What I get on every attempt of #student = FactoryGirl.create(:student):
undefined method `valid?' for nil:NilClass
Any fixes?
Additional code
class Account < ActiveRecord::Base
belongs_to :account_holder, :polymorphic => true
...
end
factory :account do
sequence :name do |n|
"Name#{n}"
end
sequence :surname do |n|
"Surname#{n}"
end
sequence :phone do |n|
"8911222332#{n}"
end
sequence :email do |n|
"somemail#{n}#mail.ru"
end
student
end
Source of issue
Student has:
validates_associated_extended :account
which is basically usual validate but with error extraction for parent model.
So when FactoryGirl attempts to create student, it validates account, which is nil.
I tried this:
before(:create) {|student| build(:account, :account_holder =>student )}
in student factory, while in account factory:
association :account_holder, :factory=>:student
But it still doesn't work.
factory :student do
after :build do |student|
student.account << create(:account, :account_holder => student) if student.account_holder.nil?
end
end
This allows you to have both a valid student and to specify an account if you want to force one.
I think latest versions of FactoryGirl even allow that to be written as lazy attribute syntax:
factory :student do
account { create(:account) }
end
I was wondering if there's an equivalent for find_or_initialize_by in FactoryGirl that solve teh following issue:
The objective is that the model uses two tables that have the same country. I don't want to use a sequence for the country (as I found for Emails).
There's a uniqueness constraint on Country, but my main issue is that it create twice the same record of Country when I call once FactoryGirl.create(:click)
Thus, the Validation fail in the test.
Rspec:
# models/click_spec.rb
describe Click do
it "should have a valid constructor" do
FactoryGirl.create(:click).should be_valid
end
end
Factories:
# factories/countries.rb
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
end
end
# factories/offers.rb
FactoryGirl.define do
factory :offer do
association :country, factory: :country
# Other columns
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
association :country, factory: :country
# Other columns
end
end
# factories/clicks.rb
FactoryGirl.define do
factory :click do
association :offer, factory: :offer
association :user, factory: :user
# Other columns
end
end
Model:
class Country < ActiveRecord::Base
validates :name, :slug,
presence: true,
uniqueness: { case_sensitive: false }
validates :slug,
length: { is: 2 }
end
You should be able to make this work by using initialize_with:
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
initialize_with { Country.find_or_create_by_name(name) }
end
end
This will always use the same country. You may want to nest the factory to allow other factories to use different names:
FactoryGirl.define do
factory :country do
initialize_with { Country.find_or_create_by_name(name) }
factory :united_states do
name "United States"
slug "us"
end
end
end
I faced similar issues, also with the Country model of my application. Here's what I did.
To ensure FactoryBot's build and create still behaves as it should, we should only override the logic of to_create, by doing:
factory :country do
to_create do |instance|
instance.id = Country.create_with(name: instance.name).find_or_create_by(slug: instance.slug).id
instance.reload
end
name { "United States" }
slug { "us" }
end
Query explained:
Country
.create_with(name: instance.name) # if not found, create with this `name` (and `slug` defined below)
.find_or_create_by(slug: instance.slug) # find by primary key `slug'
This ensures build maintains it's default behavior of "building/initializing the object" and does not perform any database read or write so it's always fast. Only logic of create is overridden to fetch an existing record if exists, instead of attempting to always create a new record.
Originally posted on https://stackoverflow.com/a/55235861/3956879.
Check out my article explaining this.
I have a pretty basic rails app that I'm working on.
Parts are in certain states (state_id), they are created by a user (user_id) and have an associated type with them (type_id).
Trying to create a factory for part, I have:
FactoryGirl.define do
factory :part do
name "blah"
association :state_id, factory: :state
association :user_id, factory: :user
association :techtype_id, factory: :techtype
end
factory :state do
name "blah"
end
factory :user do
login "blah"
end
factory :techtype do
name "blah"
example "bleh"
end
end
Yet FactoryGirl.create(:part) doesn't seem to work:
2.0.0p353 :001 > part = FactoryGirl.create(:part)
[SQL insert for State, User, and Techtype outputs here and succeeds, then...]
ActiveRecord::RecordInvalid: Validation failed:
State can't be blank, Techtype can't be blank, User can't be blank
I've tried removing the _id attribute (i.e. association :state, factory: :state) but that doesnt work either, I just get a NoMethodError: undefined method 'state=' for #<Part:0x007fa3e8e798a0>. I've also just tried using the short form association (i.e. state instead of association :state_id, factory: :state) but I get the same NoMethodError.
Your model should look like this
class Part < ActiveRecord::Base
belongs_to :state
belongs_to :user
belongs_to :techtype
end
And your factory like this
factory :part do
name "blah"
association :state
association :user
association :techtype
end
I am writing an rspec for an address model that has a polymorphic attribute called :addressable. I am using factory girl for testing.
This model has no controller because I do not wish to create a standalone address but that doesnt stop the rspec from creating a standalone address. My model, factory and rspec are
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
belongs_to :state
attr_accessible :street, :city, :state_id, :zip
validates_presence_of :street, :city, :zip, :state
validates_associated :state
end
Factory.define :address do |address|
address.street "1234 Any st"
address.city "Any City"
address.zip "90001"
address.association :state
end
describe Address do
before(:each) do
state = Factory(:state)
#attr = {:street => "1234 Any St", :city => "Any City", :zip => "Any Zip", :state_id => state.id}
end
it "should create a new address given valid attributes" do
Address.create!(#attr).valid?.should be_true
end
end
This rspec test will create an address with addressable_id and addressable_type of NULL and NULL but if I create from any other model, lets say a users model or a store model it will put in the correct data. So what I'm trying to figure out and research, unsuccessfully, is how do you validate_presence_of a polymorphic attribute and test it through a factory.
Sorry for being long winded but I havent been able to find a solution for this in the last couple of hours and I'm starting to consider I might be approaching this the wrong way.
I don't see anything in your spec that associates the address with an addressable. You are creating an address, but it's just out in space -- what is it connected to? NULL for addressable_id and addressable_type seems correct.