I have two ActiveRecord models User and posts. This is my user model.
class User < ActiveRecord::Base
has_many :posts
end
and this is my Post model.
class Post < ActiveRecord::Base
belongs_to :user
after_initialize :set_name
private
def set_name
self.name = "Post #{self.user.posts.count + 1}"
end
end
all this is working fine but when I write my factories
FactoryGirl.define do
factory :post do
content 'blah blah'
user
end
factory :user do
name 'Dummy name'
end
end
and this is my post_spec.rb file
require 'rails_helper'
RSpec.describe Post do
context 'with valid values' do
it 'should be valid' do
expect(build(:post)).to be_valid
end
end
end
and my test case fails saying that
undefined method posts for nil class in set_name
I don't know where I'm going wrong.
patkoperwas is correct, you're attempting to initialize a Post before you have an associated User object, which your after_initialize demands exist first. If you must use after_initialize, you could create a factory that creates a User before creating a Post and build with that instead.
FactoryGirl.define do
factory :user do
name "blah blah"
factory :user_with_posts do
transient do
post_count = 1
end
after(:create) do |user, evaluator|
evaluator.post_count.times do
create :post, user: user
end
end
end
end
end
I don't believe there is before(:build) functionality built into FactoryGirl. So you can't really use a callback to create a User before building your Post object. I would either create a user_with_post or explicitly create a user and pass it in when you create a post object.
RSpec.describe Post do
context 'with valid values' do
it 'should be valid' do
user = FactoryGirl.create :user_with_posts
post = user.posts.first
expect(post).to be_valid
end
end
You need to configure that association https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
FactoryGirl.define do
factory :user do
name 'Dummy name'
end
factory :post do
content 'blah blah'
association :user
end
end
If you create a post using the factory without a user, FactoryGirl will create the user for you, if you create the user before and pass it when creating a post, FactoryGirl will use the user you provided.
You can use with_initialize in your Post factory like this:
factory :post do
content 'blah blah'
user
initialize_with { new(user: user) }
end
The reason for the issue is described in the factory_bot docs: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#custom-construction
For maximum compatibility with ActiveRecord, the default initializer builds all instances by calling new on your build class without any arguments. It then calls attribute writer methods to assign all the attribute values.
This is why the Post factory doesn't have the associated User within the after_initialize hook.
Related
For example I have two models a user and a post. A post belongs_to a user and a user has many posts
#spec/factories/post.rb
FactoryBot.define do
factory :post do
user
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
#spec/factories/user.rb
FactoryBot.define do
factory :user do
first_name 'Jake'
end
end
Using Rspec in a test I want to do this:
user = create(:user, first_name: 'Barry') #id 1
post = create(:post, user: user)
I would expect that the user_id of post to be 1 however it is creating another user prior and the user_id is 2.
How can you specify the association when you are creating the object with factory_bot / factory_girl?
You should use explicit associations instead of implicit ones:
#spec/factories/post.rb
FactoryBot.define do
factory :post do
association :user # <<<--- here the change
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
#spec/factories/user.rb
FactoryBot.define do
factory :user do
first_name 'Jake'
end
end
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations
Another option is to use #trait method within the parent.
FactoryBot.define do
factory :post do
user nil
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
FactoryBot.define do
factory :user do
first_name 'Jake'
end
trait :with_post do
after(:create) do |user|
create(:post, user_id: user.id)
end
end
end
FactoryBot.create(:user, :with_post)
Here we have another solution in case your association name and factory name is different then you can follow below syntax.
#spec/factories/post.rb
FactoryBot.define do
factory :post do
association :author, factory: :user
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
in case your factory name is author and model name is user then you can use above syntax
I am using rspec and factory girl and am having a weird problem getting a sub class from a factory. I am creating a designer, which is a sub cat of a user, however the test is still receiving a user, not a designer.
FactoryGirl.define do
factory :user do
factory :designer do
role: "designer"
end
end
end
describe StoreRating do
it "should have a rating" do
user = FactoryGirl.create(:designer)
store = FactoryGirl.create(:store)
StoreRating.create(designer: user, store: store, rating: 5)
end
end
Try:
FactoryGirl.define do
factory :user do
factory :designer, class: Designer do
role: "designer"
end
end
end
I am attempting to create a factory for my user model, along with its associations. However, I cannot seem to get the syntax right in my Factory Girl code. I've read through the Factory Girl documentation but cannot seem to find any help with my specific use case. The errors I am currently receiving when I run my test suite are:
undefined method `subscription_args' for #<FactoryGirl::SyntaxRunner...
and
Trait not registered: valid_card_data
Here are my models and associations:
User.rb
has_one :subscription
has_one :plan, :through => :subscription
has_many :projects
Project.rb
belongs_to :user
Plan.rb
has_many :subscriptions
Subscription.rb
belongs_to :plan
belongs_to :user
and
And here is my Factory Girl code:
FactoryGirl.define do
factory :user do
first_name "Joel"
last_name "Brewer"
email { "#{first_name}.#{last_name}#example.com".downcase }
password "foobar"
password_confirmation "foobar"
user_type "entrepreneur"
subscription { build(:subscription, subscription_args) }
after(:create) do |user|
user.subscription.save!
end
end
factory :subscription do
user
plan_id '4'
## I am trying to access a helper method from support/utilities ##
## This call to valid_card_data doesn't seem to be working... ##
stripe_card_token valid_card_data
email "joel.brewer#example.com"
end
factory :project do
title "Sample Project"
user
end
end
Here's how I've done it in the past. Certainly not the only way:
(Note I am using cucumber.)
require 'factory_girl'
FactoryGirl.define do
factory :user do |f|
f.username 'superman'
end
factory :message do |f|
f.association :user
f.content 'Test message content'
end
end
This establishes that the message factory should associate the message to a user. Which user? I establish that at the point of use:
steps.rb:
Given(/^there is a user$/) do
#user = FactoryGirl.create(:user)
end
Given(/^the user has posted the message "(.*?)"$/) do |message_text|
FactoryGirl.create(:message, :content => message_text, :user => #user)
end
When(/^I visit the page for the user$/) do
visit user_path(#user)
end
Then(/^I should see "(.*?)"$/) do |text|
page.should have_content(text)
end
My approach, specifying at the point of use makes sense for this use case. e.g. Given is a user (user must be established first) and that user has posted a message (now the relationship between the existing user and the message can be established)...
That may or may not work out well for you, but it's how I've done it. This may or may not have helped you, but here's hoping.
There are several ways to do it. Here is one example:
after(:build) do |keyword, evaluator|
keyword.text = FactoryGirl.build(:keyword_text, :value => evaluator.keyword_text)
end
You dont need subscription_args - these can be set when you call the factory.
Where are you defining your trait?
In my factories they look like this:
trait :with_category_associations do
..
For more complicated relationships you probably want to use:
after(:create) do |keyword, evaluator|
evaluator.categories.each do |category|
FactoryGirl.create(:join_inventory_keyword, final: keyword, category: category)
end
end
Here is an example from the FactoryGirl documentation:
FactoryGirl.define do
factory :post do
name "Post name"
user
end
end
In this example, user is invoking another factory. What I would like to do is effectively call user.id, but to set it as the definition of an attribute. Here's a stripped-down example:
**models/job.rb**
...
belongs_to :assignee, :class_name => "User"
belongs_to :user
...
attr_accessible :assignee_id, :user_id
...
end
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
assignee_id user.id #what I would like to do, but triggers "undefined method 'id'" error
user_id user.id #user_id is an attribute of the model and is the job assignor
end
I've tried to incorporate the part of the documentation that discusses aliases, but with no luck:
FactoryGirl.define do
factory :user, :aliases => [:assignee] do
....
I feel like (hope?) I'm close here, but any insight is appreciated. Thanks.
EDIT: This code gets my specs running!
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
association :assignee, factory: :user
association :user, factory: :user
sequence(:user_id) { |n| n }
sequence(:assignee_id) { |n| n }
...
end
And it passes my it { should be_valid } spec, so it seems that the factory is fine, though I think I have some refactoring in the spec itself when I'm calling FactoryGirl.create.
The code above incorporates the suggestions from mguymon. Thanks!
FINAL UPDATE
After going back and re-reading Hartl's discussion on model associations, I was able to put this matter to rest. What I have above was techincally valid, but didn't actually pass the attributes in properly when i built or created jobs in my spec. Here's what I should have had:
FactoryGirl.define do
factory :job do
association :assignee, factory: :user
user
end
end
My problem also stemmed from how I was creating factories in my spec, so here's how I should have been doing it (but wasn't...sigh):
let(:user) { create(:user) }
before { #job = create(:job, user: #user) }
It seems that I don't explicitly have to have association :user in my factory, nor do I need the before block from above.
As an aside, I also learned that I can debug by including puts #job within an expect statement, or call #job.assignee_id to make sure that the attributes are being loaded properly. When that particular spec is run, the puts statement will output right by the F or . from the spec.
For the latest version of FactoryGirl, use association to map to other ActiveRecord Models:
factory :job do
# ...
association :assignee, factory: :user
end
This is straight from the docs.
From the error you posted, it is stating you are trying to get the user.id but user is not an ActiveRecord instance but a proxy from FactoryGirl. You will not get this error if you are using the association method. If you need to directly access a model, you have to manually build it first. You can do this by passing a block in your factory:
factory :job do
assignee_id { FactoryGirl.create(:user).id }
end
It looks like you are trying to associate the same model twice, for this you can use before_create callback to create a User model and assign to user and assignee:
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
end
I am trying getting it to work. I made proper associations and it still fails. Take a look at my code.
post.rb
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :content
before_create :format_content
validates :content, presence:true, length: {minimum:21}
def format_content
profil = self.user.profile
if profil.gender == "Mężczyzna"
"Wiadomość od spottera: #{self.content}"
elsif profil.gender == "Kobieta"
"Wiadomość od spotterki: #{self.content}"
end
end
end
post_spec.rb
describe "Properly formats content" do
let(:user) {FactoryGirl.create(:user)}
let!(:poscik) {FactoryGirl.create(:post) }
before(:each) {user.create_profile!(gender: "Kobieta", email: "donatella#dostojnie.pl")}
rspec_failures
Post creation valid should have content Failure/Error: poscik = user.posts.create(content: "Weird #{"a"*25}") NoMethodError: undefined method `gender' for nil:NilClass
How to properly access other classes in models and why it doesn't find my method? I understand the error message - it says that my profile class wasn't defined
user factory
FactoryGirl.define do
factory :user do
sequence(:email) {|i|"maestro#{i}#dot.pl"}
password "kravmaga1290"
association :profile, factory: :profile, strategy: :build
end
end
It seems that you havent created an object of a class Profile. I think it is complaining about nil class that you are calling to get profile.gender.
Try in factories to add something like this:
Factory.define :user do |f|
f.after_build do |user|
user.profile ||= Factory.build(:profile, :user => user)
end
end
And of course, you have to define profile factory as well.
Let me know if this helps
When you're doing self.user.profile in the Post model, the user that it finds does not have a profile associated with it. You should be able to setup the association within your FactoryGirl definition for the User class.