I am trying to use FactoryGirl to create a test database with an association that is Parent has_many Entries. At this point, it is throwing the ActiveRecord validation error that Parent can't be blank. I'm having a tough time with this one and have tried many, many methods of creating this test database with this association. I think I am close, but I may not be even close and may have basic errors, so any and all advice is much appreciated.
My guess is that the hash { parent_id: :id } is not being passed to the Entry factory. That would fail the validation. But, I don't know that that is actually the case and, even if it is, I don't know how to fix it. Thanks in advance for your help...
The error is:
ActiveRecord::RecordInvalid: Validation failed: Parent can't be blank
The RSpec call is:
before(:all) do
rand(11..25).times { FactoryGirl.create(:parent) }
visit "/parents?direction=asc&sort=parent_blog"
end
after(:all) do
Parent.delete_all
end
The Parent model is:
class Parent < ActiveRecord::Base
has_many :entries, dependent: :destroy
accepts_nested_attributes_for :entries, :allow_destroy => :true
validates :parent_blog, presence: true,
uniqueness: true
end
The Entry model is:
class Entry < ActiveRecord::Base
belongs_to :parent
validates :entry_blog, presence:true,
uniqueness: true
validates :parent_id, presence: true
end
The Parent factory is:
FactoryGirl.define do
factory :parent do
sequence(:parent_blog) { |n| "http://www.parent%.3d.com/ss" % n }
entries { rand(5..15).times { FactoryGirl.create(:entry, parent_id: :id) } }
end
end
The Entry factory is:
FactoryGirl.define do
factory :entry do
sequence(:entry_blog) { |n| "http://www.parent%.3d.com/ss/entry%.3d" % [n, n] }
parent_id { :parent_id }
end
end
The following modification to your factory definitions should work. I'll be back later to offer some explanation.
FactoryGirl.define do
factory :parent do
sequence(:parent_blog) { |n| "http://www.parent%.3d.com/ss" % n }
after(:create) {|parent| rand(5..15).times {FactoryGirl.create(:entry, parent: parent)}}
end
factory :entry do
sequence(:entry_blog) { |n| "http://www.parent%.3d.com/ss/entry%.3d" % [n, n] }
parent
end
end
The two changes, introducing the use of after in the :parent factory and using parent instead of parent_id, are both examples of RSpec's support of associations, as discussed in https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations.
As for why your code didn't work, I can only provide a partial answer at this point. The reason you can't include entries as part of the initial parent creation is that you don't have the parent id until the parent record is created, yet you need the parent id to create the entries because of Entry validating the presence of parent_id. Put another way, parent_id hadn't been set when the entries block was evaluated in your parent factory.
What I'm not sure of is why you can't replace parent with parent_id in the entry factory and correspondingly replace parent: parent with parent_id: parent.id in the FactoryGirl.create call in the parent factory. I tried that variant before submitting the above and it failed with:
1) test
Failure/Error: rand(11..25).times { FactoryGirl.create(:parent) }
ArgumentError:
Trait not registered: parent_id
# ./spec/factories.rb:4:in `block (4 levels) in <top (required)>'
# ./spec/factories.rb:4:in `times'
# ./spec/factories.rb:4:in `block (3 levels) in <top (required)>'
# ./tmp/factory_spec.rb:5:in `block (3 levels) in <top (required)>'
# ./tmp/factory_spec.rb:5:in `times'
# ./tmp/factory_spec.rb:5:in `block (2 levels) in <top (required)>'
I'll update this answer again if/when I figure that out.
I ran into this issue when I upgraded from factory bot 4.11 to 6.2. I suspect this is because a factory bot 5 change:
Changed: use_parent_strategy now defaults to true, so by default the build strategy will build, rather than create associations
Here's what my parent and child factories look like:
factory :parent do
sequence(:name) { |n| "Parent #{n}" }
end
factory :child do
sequence(:name) { |n| "Child #{n}" }
parent
end
I had to change the way I was defining my variables in the spec file.
Instead of:
let(:parent){ create(:parent) }
let(:child) { build(:department) }
Used this:
let(:parent){ create(:parent) }
let(:child) { build(:department, parent: parent) }
Related
I have two factories as follows:
FactoryBot.define do
factory :proofread_document do
factory :proofread_document_with_paragraphs do
after(:create) {|instance| create_list(:paragraph, 5, proofread_document: instance) }
end
end
end
FactoryBot.define do
factory :paragraph do
level { 1 }
association :proofread_document
end
end
In my RSpec test:
describe '#number_of_paragraphs_for' do
let(:proofread_document) { create(:proofread_document_with_paragraphs)}
it 'returns the number of paragraphs for the given level' do
expect(proofread_document.number_of_paragraphs_for("level_1")).to eq(1)
end
end
The test fails because there are no paragraphs:
proofead_document.paragraphs
=> []
Why are the associated paragraph objects not being created?
Associations are not magically reloaded on existing instances. This is not due to FactoryBot, but to ActiveRecord itself.
# example with activerecord:
class Foo
has_many :bars
end
class Bar
belongs_to :foo
end
foo = Foo.first
foo.bars
# => []
3.times { Bar.create(foo: foo) }
foo.bars
# => []
foo.reload.bars
# => [<#Bar ...>, <#Bar ...>, <#Bar ...>]
So you just need to reload the record (or just the association)
after(:create) do |inst|
create_list(...)
inst.paragraphs.reload
# or inst.reload
end
I found the problem.
In my paragraphs model I had placed a default scope as follows:
default_scope :minimum_word_count, ->{ where(proofread_word_count: MINIMUM_LEVEL_DATA_WORD_COUNT..Float::INFINITY)}
This caused some issues as the paragraph I was saving in my tests had a word count too low for the parameters defined in this scope.
#P.Boro and #rewritten helped by getting me to re-check my models and scopes.
I have a reference to a Model object called Admin which has a field called id. The object property is accessible in the puts line. I now need to pass in that ID into an ActiveRecord call to create another object (where it serves as foreign key) as follows but it throws an exception copied below (the admin.id does not get picked up in the Bill.create call):
admin = Admin.find_by(email:email)
puts "admin id #{admin.id}" # this gets printed with correct value
bill = Bill.create(admin_id: admin.id, body: body)
Exception:
/usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:59:in `rescue in _assign_attribute': unknown attribute 'admin_id' for Bill. (ActiveRecord::UnknownAttributeError)
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:54:in `_assign_attribute'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:41:in `block in assign_attributes'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:35:in `each'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:35:in `assign_attributes'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/core.rb:564:in `init_attributes'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/core.rb:281:in `initialize'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/inheritance.rb:61:in `new'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/inheritance.rb:61:in `new'
from /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.4/lib/active_record/persistence.rb:33:in `create'
from test.rb:34:in `<main>'
ActiveRecord migration for the Bills model:
def up
create_table :bills do |t|
t.integer :admin_id
t.text :body
end
add_foreign_key :bills, :admins
end
Thank you in advance for sharing insights on what I am doing wrong.
Per my comment, you should really make this into a belongs_to relation, here's the refactored migration (using latest Rails 4 syntax):
class CreateBills < ActiveRecord::Migration
def change
create_table :bills do |t|
t.references :admin
t.text :body
end
end
end
add these relations to the models:
class Admin < ActiveRecord::Base
has_many :bills
end
class Bill < ActiveRecord::Base
belongs_to :admin
end
and the code for creating a bill associated with an admin:
admin = Admin.find_by(email:email)
puts "admin id #{admin.id}" # this gets printed with correct value
bill = admin.bills.create(body: body)
In my Grape ruby project. I'm separating my models to a gem so I can use it between my ruby projects.
Now the problem is that with my activerecords, let's say I'm dealing with User model, now it looks something like this:
module MyApp
module Core
class User < ActiveRecord::Base
self.table_name = 'users'
end
end
end
And I'm using Factory girl something like this:
FactoryGirl.define do
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password { "12345678" }
password_confirmation { "12345678" }
end
end
And let's say I have the following rspec test:
require 'spec_helper'
describe MyApp::Core::User do
it "has name assigned" do
user = build(:member, first_name: 'Eki', last_name: 'Eqbal')
expect(user.first_name).to eq('Eki')
expect(user.last_name).to eq('Eqbal')
end
end
And when I try to run the test:
⇒ bundle exec rspec spec/unit/users_spec.rb
warning: parser/current is loading parser/ruby22, which recognizes
warning: 2.2.3-compliant syntax, but you are running 2.2.0.
Run options: include {:focus=>true}
All examples were filtered out; ignoring {:focus=>true}
F
Failures:
1) MyApp::Core::User has name assigned
Failure/Error: user = build(:user, first_name: 'Eki', last_name: 'Eqbal')
NameError:
uninitialized constant User
# ./spec/unit/users_spec.rb:37:in `block (2 levels) in <top (required)>'
Finished in 0.14788 seconds
1 example, 1 failure
Failed examples:
Instead of:
factory :user do
Try:
factory :user, class: MyApp::Core::User do
FactoryGirl guesses the class name based on the factory name, so if it's in a module like that, it won't find it.
I've been trying to implement the association at FrontEnd but as currently the application in not having any database directly connected with the website, so as a result we can not use the ActiveRecord and only using the ActiveModel for supporting the validations and core features of a Model. Now as we need to use the nested attributes which we are going to send along with an object, the addresses which are associated with the User, so for this we need to first define the association on the corresponding model. But after defining the association it is throwing exception of undefined method "has_many" on User model. I'm currently searching the way to implement it in our website and implement the logic of nested attributes.
It would be great if you can suggest me anything related to this or if you have met with such issue in the past.
I've also tried the approach using the gem https://github.com/softace/activerecord-tableless but not working for me. Also I've added a tableless.rb
tableless.rb
class Tableless < ActiveRecord::Base
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new( name.to_s, default, sql_type.to_s, null )
end
def self.columns()
#columns ||= [];
end
def self.columns_hash
h = {}
for c in self.columns
h[c.name] = c
end
return h
end
def self.column_defaults
Hash[self.columns.map{ |col|
[col.name, col.default]
}]
end
def self.descends_from_active_record?
return true
end
def persisted?
return false
end
# override the save method to prevent exceptions
end
But getting the following exception Exception:
Console Error:
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:546:in `retrieve_connection'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/connection_handling.rb:79:in `retrieve_connection'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/connection_handling.rb:53:in `connection'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/model_schema.rb:203:in `table_exists?'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/primary_key.rb:92:in `get_primary_key'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/primary_key.rb:77:in `reset_primary_key'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/primary_key.rb:65:in `primary_key'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/primary_key.rb:79:in `reset_primary_key'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/primary_key.rb:65:in `primary_key'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/write.rb:32:in `write_attribute'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/dirty.rb:70:in `write_attribute'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/attribute_methods/write.rb:19:in `__temp__9646='
from /home/cis/API_OTGJ/Tableless/app/models/book.rb:13:in `block in initialize'
from /home/cis/API_OTGJ/Tableless/app/models/book.rb:12:in `each'
from /home/cis/API_OTGJ/Tableless/app/models/book.rb:12:in `initialize'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/inheritance.rb:27:in `new'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/activerecord-4.0.0/lib/active_record/inheritance.rb:27:in `new'
from (irb):19
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in `start'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in `start'
from /home/cis/.rvm/gems/ruby-2.0.0-p0#website/gems/railties-4.0.0/lib/rails/commands.rb:64:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
It seems that you forgot to call the method
has_no_table
On your model, as per https://github.com/softace/activerecord-tableless#usage. In their example:
class ContactMessage < ActiveRecord::Base
has_no_table
column :name, :string
column :email, :string
validates_presence_of :name, :email
end
Hope this helps. =)
I have a simple restaurant class that looks like this:
module Restaurant
class Identity
attr_reader :name, :location
def initialize (name, location)
#name = name
#location = location
end
end
end
My factory looks like this:
FactoryGirl.define do
factory :restaurant, :class => Restaurant::Identity do |f|
f.name "Alfredos"
f.location "Andheri"
end
end
And my test is written like this:
describe Restaurant::Identity do
subject { build(:restaurant) }
its(:name) {should_not be_nil}
its(:location) {should_not be_nil}
end
But when I run this, I get
1) Restaurant::Identity name
Failure/Error: subject { build(:restaurant) }
ArgumentError:
wrong number of arguments (0 for 2)
# ./lib/restaurant.rb:7:in `initialize'
# ./spec/restaurant_spec.rb:9:in `block (2 levels) in <top (required)>'
# ./spec/restaurant_spec.rb:11:in `block (2 levels) in <top (required)>'
Why is this happening? What am I doing wrong?
Ok so the solution is to use initialize_with in your factory girl setup:
FactoryGirl.define do
factory :restaurant, :class => Restaurant::Identity do |f|
f.name "Alfredos"
f.location "Andheri"
initialize_with { new(name, location) } # add this line
end
end
ref: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#custom-construction