FactoryGirl creating an extra object - ruby-on-rails-3

My factory:
FactoryGirl.define do
factory :comment do
content 'bla bla bla bla bla'
user
end
factory :user do
sequence(:username) { |n| "johnsmith#{n}" }
password '123'
factory :user_with_comments do
ignore do
comments_count 5
end
after(:create) do |user, evaluator|
FactoryGirl.create_list(:comment, evaluator.comments_count, user: user)
end
end
end
end
My spec:
require 'spec_helper'
describe Comment do
let(:comment) { Factory.create :comment }
describe "Attributes" do
it { should have_db_column(:content).of_type(:text) }
it { should have_db_column(:user_id).of_type(:integer) }
it { should have_db_column(:profile_id).of_type(:integer) }
end
describe "Relationships" do
it { should belong_to(:profile) }
it { should belong_to(:user) }
end
describe "Methods" do
describe "#user_name" do
it "Should return the comment creater username" do
user = Factory.create :user
binding.pry
comment.user = user
binding.pry
comment.user_username.should == user.username
end
end
end
end
On the first binding.pry, User.count returns 1 as expected. But on the second binding.pry, User.count returns 2. My question is, why the comment.user = user assignment creates a new user record ? Thanks in advance.

The reason is that your let for comment calls Factory.create :comment. In the factory for comment, it calls the association for user.
So when you use your let, it makes a comment object and a user object and connects them. Then, you override that user when you set comment.user=user.

Related

Rspec 'before' scope in context

I am getting an undefinded local variable or method user in this test. Anyone help?
context "[user IS signed in]" do
before do
user = Fabricate(:user)
league = Fabricate(:league)
event = Fabricate(:event, league: league)
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
ApplicationController.any_instance.stub(:primary_leagues).and_return([league])
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: user.id, event_id: event.id, league_id: league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end
Your before block is declaring user as a local variable so it is out of scope within your it block.
The simplest way to fix this is to declare member variables in your before block, e.g.
before do
#user = Fabricate(:user)
#league = Fabricate(:league)
#event = Fabricate(:event, league: #league)
...
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: #user.id, event_id: #event.id, league_id: #league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end
This works because before and it are executing in the context of the same (generated) class instance.
Alternatively you could declare your variables with let and leave the code example as it is, e.g.
context "[user IS signed in]" do
let(:user) { Fabricate(:user) }
let(:league) { Fabricate(:league) }
let(:event) { Fabricate(:event, league: league) }
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
ApplicationController.any_instance.stub(:primary_leagues).and_return([league])
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: user.id, event_id: event.id, league_id: league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end
let will instantiate the given variable when it is first referenced and memoize the result.

rails 3 validations uniqueness on attributes of an association

i have a model called Fund and a model called Company .. where fund belongs_to a company.
i have this validation in my Fund table:
validates :name, presence: true, uniqueness: true
This works both on server side and client side using client_side_validations. But i want my fund names to be unique across both fund.name values and fund.company.name values. And i want to do it in a way it would work with client_side_validations too.
Suggestions?
Ended up creating a very specific validator and adding it to client-side-validation. Here'z the breakdown
In models/fund.rb
validates_fund_name_not_company_name :name
new file in config/initializers/validators .. called fund_name_not_company_name_validator.rb
class FundNameNotCompanyNameValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
if ::Company.exists?(name: value)
record.errors.add(attr_name, :fund_name_not_company_name, options.merge(:value => value))
end
end
end
# This allows us to assign the validator in the model
module ActiveModel::Validations::HelperMethods
def validates_fund_name_not_company_name(*attr_names)
validates_with FundNameNotCompanyNameValidator, _merge_attributes(attr_names)
end
end
module ClientSideValidations::Middleware
class FundNameNotCompanyName < ClientSideValidations::Middleware::Base
def response
if ::Company.exists?(name: request.params[:name])
self.status = 404
else
self.status = 200
end
super
end
end
end
then in app/assets/javascripts/rails.validations.custom.js
clientSideValidations.validators.remote['fund_name_not_company_name'] = function(element, options) {
if ($.ajax({
url: '/validators/fund_name_not_company_name',
data: { name: element.val() },
// async must be false
async: false
}).status == 404) { return options.message; }
}
This helped a great deal

rails 3 - FactoryGirl create associated records

I am trying to create some test data to fill my tables with so that i can test functionality on my site.
The tables in question are: songs, song_arrangements, and song_arrangement_files. The are associated in this way.
Songs:
has_many :song_arrangements, :dependent => :destroy
has_many :song_arrangement_files, :through => :song_arrangements
Song Arrangements:
belongs_to :song
has_many :song_arrangement_files, :dependent => :destroy
Song Arrangement Files:
belongs_to :song
belongs_to :song_arrangement
I have been able to create 25 songs with FactoryGirl with this code:
Code in my spec file:
before { FactoryGirl.create_list(:song, 25) }
Code in the factory:
FactoryGirl.define do
factory :song do |s|
s.sequence(:title) { |n| "Song Title #{n}" }
s.sequence(:artist) { |n| "Song Artist #{n}" }
end
end
Any thoughts on how to create song_arrangements and song_arrangement_files that are correctly assciated with their respective song_arrangement or song record?
I'm guessing i could use after(:create) nested in my factory somehow. I'm very new to FactoryGirl and still fairly new to Rails in general. Any help is much appreciated!
So my other answer helped me do what i needed to do; however, i was needing to basically go one level of associations deeper and i was hitting walls with that solution. I ended re-reading the FactoryGirl documentation for associations and came up with this solution that works in all my cases. It creates songs, song_arrangements, and song_arrangement_files. I'm sure the code isn't pretty, but it works and can be improved upon later. Hope this helps anyone running into the same type of roadblocks.
FactoryGirl.define do
factory :song do |s|
s.sequence(:title) { |n| "Song Title #{n}" }
s.sequence(:artist) { |n| "Song Artist #{n}" }
factory :song_with_song_arrangements do
ignore do
song_arrangements_count 100
end
after(:create) do |song, evaluator|
FactoryGirl.create_list(:song_arrangement, evaluator.song_arrangements_count, song: song)
end
end
end
factory :song_arrangement do |sa|
song
sa.sequence(:title) { |n| "Arrangement #{n}" }
original_key 'A'
bpm 75
sa.sequence(:chart_content) { |n| "This is the chart content for Arrangement #{n}." }
chart_mapping 'V1, C, V2, C, B, C, C'
sa.sequence(:notes) { |n| "These are notes for the Arrangement #{n}." }
factory :song_arrangement_with_song_arrangement_files do
ignore do
song_arrangement_files_count 100
end
after(:create) do |song_arrangement, evaluator|
FactoryGirl.create_list(:song_arrangement_file, evaluator.song_arrangement_files_count, song_arrangement: song_arrangement)
end
end
end
factory :song_arrangement_file do |saf|
song_arrangement
song
saf.sequence(:title) { |n| "Attachment #{n}" }
url 'http://www.google.com'
saf.sequence(:description) { |n| "This is the description of Attachment #{n}." }
end
end
Code used to call these factories:
Songs:
before(:each) { FactoryGirl.create_list(:song, 25) }
Song Arrangements:
before(:each) { FactoryGirl.create(:song_with_song_arrangements) }
Song Arrangement Files:
before(:each) { FactoryGirl.create(:song_arrangement_with_song_arrangement_files) }
For anyone else running in to this issue, here's the solution i came up with. As i said, i am new to FactoryGirl so if there's a better way to do this please share!
FactoryGirl.define do
factory :song do |s|
s.sequence(:title) { |n| "Song Title #{n}" }
s.sequence(:artist) { |n| "Song Artist #{n}" }
before(:create) do |song|
song.song_arrangements << FactoryGirl.build_list(:song_arrangement, 10)
end
end
factory :song_arrangement do |sa|
sa.sequence(:title) { |n| "Arrangement #{n}" }
original_key 'A'
bpm 75
sa.sequence(:chart_content) { |n| "This is the chart content for Arrangement #{n}." }
chart_mapping 'V1, C, V2, C, B, C, C'
sa.sequence(:notes) { |n| "These are notes for the Arrangement #{n}." }
end
end
Giving credit where credit is due, i actually found the answer after a lot of searching from this post: How to user factory girl to create associated lists with a has_many with a validation that requires it on create
It was Blizzo's answer that i pulled the solution from.

Stub associations

I have method and spec.
class Event
def self.renew_subscription(user)
subscription = user.subscription
result = subscription.renew
user.pay(subscription.plan.price_in_cents) if result
result
end
end
let!(:user) { create :user }
describe ".renew_subscription" do
before do
user.subscription.stub!(:renew).and_return(true)
user.subscription.stub!(:plan).
and_return(Struct.new("SP", :price_in_cents).new(699))
end
context "when have to pay" do
it "pays" do
user.should_receive(:pay)
Event.renew_subscription user
end
end
end
There user belongs_to :subscription and subsription belongs_to :plan
Is there the way to stub subscription.renew and subscription.plan (or subscription.plan.price_in_cents)?
I think it's probably safe for you to do something like this:
Subscription.any_instance.stub(:renew).and_return(true)
plan = mock_model(Plan)
Subscription.any_instance.stub(:plan).and_return(plan)
plan.stub(:price_in_cents).and_return(699)
There are probably other ways of doing it too, but I hope that helps.

Destroy method failing in controller test

I'm experiencing a bizarre issue testing a destroy method. I'm using FactoryGirl and Rspec.
Here's a look at the method in question. As you can see, it doesn't actually destroy the dealer, just set it and it's dependent object's active attributes to false:
dealers_controller.rb
def destroy
#dealer = Dealer.find(params[:id])
#dealer.active = false
#dealer.save!
#dealer.leads.each { |lead|
lead.active = false
lead.save!
}
#dealer.users.each { |user|
user.active = false
user.save!
}
redirect_to dealers_path
end
When I run this method in the application it does exactly what it should do. Now, on to the test.
dealers_controller_spec.rb
describe "#destroy" do
context "when deleting a valid record" do
let(:dealer) { FactoryGirl.create(:dealer_with_stuff) }
before do
#user = FactoryGirl.build(:admin_user)
login_user
delete :destroy, :id => dealer.id
end
it { should assign_to(:dealer).with(dealer) }
it { should redirect_to(dealers_path) }
it { should set_the_flash }
it "is no longer active" do
dealer.active.should be_false
end
it "has no active users" do
dealer.users.each do |user|
user.active.should be_false
end
end
it "has no active leads" do
dealer.leads.each do |lead|
lead.active.should be_false
end
end
end
end
The first 3 tests pass, but the last 3 all fail (weirdly, the user.active.should be_false test only fails if I put a sleep(10) after delete :destroy up above, but let's not get into that issue now). So when I check the test log, it goes through the entire destroy process, but then does a ROLLBACK, so for some reason it doesn't save any of the records; but it doesn't give me any more information than that.
Does anyone have any thoughts on this? I've tried everything I can possibly think of.
What if you reload the dealer? The dealer in your tests is different from the #dealer object in the controller (ActiveRecord doesn't do identity maps).
before do
#user = FactoryGirl.build(:admin_user)
login_user
delete :destroy, :id => dealer.id
dealer.reload # << add this
end