Deleting children does work in real app, but not in test - ruby-on-rails

I can't for the life of me figure out what's going wrong here. I have a Client model and an Invoice model.
Client.rb
has_many :invoices, dependent: :destroy
Invoices.rb
belongs_to :client
I have the following client spec:
it "destroys its children upon destruction" do
i = FactoryGirl.create(:invoice) # As per the factory, the :client is the parent of :invoice and is automatically created with the :invoice factory
lambda {
i.client.destroy
}.should change(Invoice.all, :count).by(-1)
end
And here are my factories:
Client factory
FactoryGirl.define do
factory :client do
city "MyString"
end
end
Invoice factory
FactoryGirl.define do
factory :invoice do
association :client
gross_amount 3.14
end
end
If I do i = FactoryGirl.create(:invoice) and afterwards i.client.destroy manually in the console, the invoice is in fact destroyed. But for some reason, the test fails, giving me "count should have been changed by -1, but was changed by 0".
What am I doing wrong?

The return value of Invoice.all is an array and so database operations won't affect it. The array doesn't change when you destroy records, and should change(receiver, message) is going to just send message to the same receiver. Try either:
lambda {
i.client.destroy
}.should change(Invoice, :count).by(-1)
or
lambda {
i.client.destroy
}.should change{Invoice.all.count}.by(-1)

Related

Factory bot creates incorrect data

Why is this test passing? I don't understand what the problem is: Factory bot or Rails?
Model:
class Vote < ApplicationRecord
belongs_to :user
belongs_to :votable, polymorphic: true
validate :self_like
private
def self_like
errors.add(:user, 'self-like') if votable.author_id == user_id
end
end
Factory:
FactoryBot.define do
factory :vote do
value { 1 }
user
association :votable, factory: :question
end
end
If you output the tested object (pp vote), then all the attributes will be nil. In this case, it is possible to get the associated object (pp vote.votable)
describe 'validate :self_like' do
let!(:vote) { build :vote }
it "self-like" do
vote.valid?
expect(vote.errors[:user]).to include('self-like')
end
end
The way the factories are defined you don't specify that votable author and user match, so votable.author_id == user_id is going to be false.
The best solution I could think of is to update the votable author with an after_build hook and force it to match the user.
after(:build) do |vote|
vote.votable.update!(author: vote.user)
end

Validating That A has_many Association Has At Least One Model When Using FactoryGirl

Putting aside arguments on whether or not you should test existence of a model's associations, I have a model called Order and I am validating that it has at least one item in its has_many association using:
class Order < ActiveRecord::Base
has_many :items
validates :items, presence: true
end
I have set FactoryGirl to lint my factories (checking for validity). So my order factory is not valid unless I create an item for its has_many collection.
My orders factory looks like this:
FactoryGirl.define do
factory :order do
ignore do
items_count 1
end
after(:build) do |order, evaluator|
create_list(:item, evaluator.items_count, order: order)
end
end
end
According to Factory Girl's Getting Started:
FactoryGirl.lint builds each factory and subsequently calls #valid? on it
However when I run my specs, Factory Girl throws an FactoryGirl::InvalidFactoryError because the order factory is invalid.
Workaround
after(:build) do |order, evaluator|
evaluator.items_count.times do
order.items << FactoryGirl.create(:item)
end
#create_list(:item, evaluator.items_count, order: order)
end
According to the definition, it will call .valid? AFTER building. It seems that it will call this before running the after(:build) block.
Try writing you factory like this:
FactoryGirl.define do
factory :order do
ignore do
items_count 1
end
items { build_list(:item, items_count) }
end
end
This should build the item before the .valid? is called.
Let me know if this works :)

FactoryGirl define attribute by calling method on another factory

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

Using associations in hooks with FactoryGirl

I'm in a hard situation with FactoryGirl that maybe you can help me to solve. The code is like this:
class Bet
belongs_to :market
belongs_to :option
has_one :market, :through => :option
has_one :event, :through => :market
before_validation :set_event_date
scope :by_event_date, order(arel_table[:event_date].desc)
def set_event_date
self.event_date = event.date
end
end
I need to materialize the event_date attribute in Bet because the scope by_event_date is too costly without the materialization. The problem comes when I run FactoryGirl.create :bet. The hook gets executed, but bet.event is nil, and therefore an exception raises. Is there anyway to configure FactoryGirl to really create the associated objects?
what's your factory code? you can do something like
FactoryGirl.define do
factory :event do
#something
end
factory :bet do
#something
event
end
end
that should create an event for you
you can also use factorygirl callbacks to customize it a little
FactoryGirl.define do
factory :event do
#something
end
factory :bet do
#something
after_build do |bet| #for newer version it is after(:build) do |bet|...
bet.event = Factory.build(:event)
end
end
end
when the record is saved the event will be saved to
EDIT: try assigning a market then
FactoryGirl.define do
factory :event do
#something
end
factory :market do
event
end
factory :bet do
#something
market
end
end

FactoryGirl and polymorphic associations

The design
I have a User model that belongs to a profile through a polymorphic association. The reason I chose this design can be found here. To summarize, there are many users of the application that have really different profiles.
class User < ActiveRecord::Base
belongs_to :profile, :dependent => :destroy, :polymorphic => true
end
class Artist < ActiveRecord::Base
has_one :user, :as => :profile
end
class Musician < ActiveRecord::Base
has_one :user, :as => :profile
end
After choosing this design, I'm having a hard time coming up with good tests. Using FactoryGirl and RSpec, I'm not sure how to declare the association the most efficient way.
First attempt
factories.rb
Factory.define :user do |f|
# ... attributes on the user
# this creates a dependency on the artist factory
f.association :profile, :factory => :artist
end
Factory.define :artist do |a|
# ... attributes for the artist profile
end
user_spec.rb
it "should destroy a users profile when the user is destroyed" do
# using the class Artist seems wrong to me, what if I change my factories?
user = Factory(:user)
profile = user.profile
lambda {
user.destroy
}.should change(Artist, :count).by(-1)
end
Comments / other thoughts
As mentioned in the comments in the user spec, using Artist seems brittle. What if my factories change in the future?
Maybe I should use factory_girl callbacks and define an "artist user" and "musician user"? All input is appreciated.
Although there is an accepted answer, here is some code using the new syntax which worked for me and might be useful to someone else.
spec/factories.rb
FactoryGirl.define do
factory :musical_user, class: "User" do
association :profile, factory: :musician
#attributes for user
end
factory :artist_user, class: "User" do
association :profile, factory: :artist
#attributes for user
end
factory :artist do
#attributes for artist
end
factory :musician do
#attributes for musician
end
end
spec/models/artist_spec.rb
before(:each) do
#artist = FactoryGirl.create(:artist_user)
end
Which will create the artist instance as well as the user instance. So you can call:
#artist.profile
to get the Artist instance.
Use traits like this;
FactoryGirl.define do
factory :user do
# attributes_for user
trait :artist do
association :profile, factory: :artist
end
trait :musician do
association :profile, factory: :musician
end
end
end
now you can get user instance by FactoryGirl.create(:user, :artist)
Factory_Girl callbacks would make life much easier. How about something like this?
Factory.define :user do |user|
#attributes for user
end
Factory.define :artist do |artist|
#attributes for artist
artist.after_create {|a| Factory(:user, :profile => a)}
end
Factory.define :musician do |musician|
#attributes for musician
musician.after_create {|m| Factory(:user, :profile => m)}
end
You can also solve this using nested factories (inheritance), this way you create a basic factory for each class then
nest factories that inherit from this basic parent.
FactoryGirl.define do
factory :user do
# attributes_for user
factory :artist_profile do
association :profile, factory: :artist
end
factory :musician_profile do
association :profile, factory: :musician
end
end
end
You now have access to the nested factories as follows:
artist_profile = create(:artist_profile)
musician_profile = create(:musician_profile)
Hope this helps someone.
It seems that polymorphic associations in factories behave the same as regular Rails associations.
So there is another less verbose way if you don't care about attributes of model on "belongs_to" association side (User in this example):
# Factories
FactoryGirl.define do
sequence(:email) { Faker::Internet.email }
factory :user do
# you can predefine some user attributes with sequence
email { generate :email }
end
factory :artist do
# define association according to documentation
user
end
end
# Using in specs
describe Artist do
it 'created from factory' do
# its more naturally to starts from "main" Artist model
artist = FactoryGirl.create :artist
artist.user.should be_an(User)
end
end
FactoryGirl associations: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
I currently use this implementation for dealing with polymorphic associations in FactoryGirl:
In /spec/factories/users.rb:
FactoryGirl.define do
factory :user do
# attributes for user
end
# define your Artist factory elsewhere
factory :artist_user, parent: :user do
profile { create(:artist) }
profile_type 'Artist'
# optionally add attributes specific to Artists
end
# define your Musician factory elsewhere
factory :musician_user, parent: :user do
profile { create(:musician) }
profile_type 'Musician'
# optionally add attributes specific to Musicians
end
end
Then, create the records as usual: FactoryGirl.create(:artist_user)

Resources