factory_girl wiggin' out over associations (infinite loop, maybe?) - ruby-on-rails-3

What I have now:
class User < ActiveRecord::Base
has_many :people
end
... and...
class Person < ActiveRecord::Base
belongs_to :user
end
In spec/factories.rb:
Factory.define :user do |u|
u.email "test#test.com"
u.password "testpassword"
u.password_confirmation "testpassword"
u.display_name "neezer"
# u.people { |i| [i.association(:person)] }
end
Factory.define :person do |p|
p.first_name "p_firstname"
p.last_name "p_lastname"
p.gender "male"
p.association :user
end
I want to setup the user factory to create with 1 person association, but if I uncomment that line, when I run my tests, my system hangs for quite some time, before outputting this failure:
1) User can be created from a factory
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
# /Users/test/.rvm/gems/ruby-1.9.2-p0/gems/activerecord-3.0.5/lib/active_record/persistence.rb:285
What am I doing wrong here? I would like to have tests that require an association between these two models, such that (1) a User must have at least 1 person, and (2) a Person must belong to a User.
Is this a first-priority issue? I'll admit I'm a bit lost here...
I'm using rspec 2.5.0, factory_girl_rails 1.0.1, and rails 3.0.5.
My specs:
user_spec.rb:
require 'spec_helper'
describe User do
subject { Factory :user }
# ...
context "has associations, " do
it "can have people" do
subject.should respond_to :people
end
it "must have at least 1 person" do
subject.send "people=", nil
subject.should_not be_valid
subject.errors[:people].should_not be_empty
end
end
end
person_spec.rb:
require 'spec_helper'
describe Person do
subject { Factory :person }
# ...
context "has validation, " do
[:gender, :user].each do |attr|
it "must have a #{ attr }" do
subject.send "#{attr}=", nil
subject.should_not be_valid
subject.errors[attr].should_not be_empty
end
end
end
context "has associations, " do
it "can have a User" do
subject.should respond_to :user
end
end
end

Keep that line but remove p.association :user from your person factory.

I've since discovered Shoulda, which provides a nice rspec matchers like these:
subject.should belong_to :user
subject.should have_many :people
Which has solved my issue.

Related

FactoryGirl belongs_to with Seeded association

I have a UserType object that ideally is seeded in the DB and remains static:
{id: 1, name: 'Individual'}, {id: 2, name: 'Group'}, {id: 3, name: 'Admin'}
class UserType < ActiveRecord::Base
attr_accessible :name
has_many :users
end
class User < ActiveRecord::Base
attr_accessible :email, :first_name
belongs_to :user_type
end
In testing, I simply want to create an admin user that has its user_type_id field set to 3 when created, and for the UserType.all to have those three items. I've tried a number of things, but here's where I'm at:
FactoryGirl.define do
factory :user_type do
id 1
name "Individual"
trait :group do
after(:create) do |user_type|
id 2
name "Group Leader"
end
end
trait :admin do
after(:create) do |user_type|
id 3
name "Administrative"
end
end
end
end
FactoryGirl.define do
factory :user do
first_name 'TestUser'
email { Faker::Internet.email }
user_type
trait :admin do
after(:create) do |user|
admin_user_type = UserType.where(id: 3).first
admin_user_type = create(:user_type, :admin) unless admin_user_type
user_type admin_user_type
end
end
end
And my test in spec/features/sessions/admin_sign_in_spec.rb:
feature "Admin signing in" do
background do
#institution = create(:institution_with_institutiondomains)
#admin = create(:user, :admin, email: "admin##{#institution.subdomain}.com")
end
scenario "with correct credentials", focus: true do
binding.pry
#admin.inspect
page.visit get_host_using_subdomain(#institution.subdomain)
within("#login-box") { fill_in t('email'), with: #admin.email }
click_button t('session.admin.sign_in') #the action in signing in here checks that user.user_type_id == 3
expect(page).to have_content "You're signed in!"
end
end
In many cases, especially in tests where I have multiple users getting created, I'll receive a MySQL duplicate error on the first id: 1 Individual. I appreciate any guidance.
For what it's worth, anyone finding this may not like my answer, but it is the only thing that works for me. UserTypes are static in my test database, so I removed the traits in the :user_type factory. Instead, I simply set the user_type_id directly and call save on it. Without the save, the change does not persist to my #admin variable. The test data is cleaned between tests using DatabaseCleaner, leaving my user_types table alone.
FactoryGirl.define do
factory :user do
first_name 'TestUser'
email { Faker::Internet.email }
user_type
trait :admin do
after(:create) do |user|
# admin_user_type = UserType.where(id: 3).first
# admin_user_type = create(:user_type, :admin) unless admin_user_type
# user_type admin_user_type
user.user_type_id = 3
user.save #without this, the change won't persist
end
end
end
end

Factory girl, dependent factories

UPDATE
I went back to using Fixtures. IMOP, fixtures are FAR better than factories; easier to use, easier to write, easier to understand (no magic). My suggestion: limit your testing library to the very basics (listen to DHH)...use minitest with fixtures.
original post
In my app a district has many schools, a school has many uses, a user has many accounts, an account has one role. In order to create complete factories for testing I need to create a user and school that persists across factories. Im getting a "stack level too deep" error in my recent attempts.
My user_test.rb
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do
association :primarycontact, factory: :user # expecting this to attach the user_id from factory :user as :primary contact_id in the school model
association :district, factory: :district # expecting this to attach the :district_id from the :district factory as :district_id in the school model
name "Test School"
end
factory :user do, aliases: [:primarycontact]
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
association :school, factory: :school # expecting this to create :school_id in the users model, using the :school factory
end
factory :role do
name "student"
end
factory :account do
association :user, factory: :user
association :role, factory: :role
end
end
So, I am attempting to do FactoryGirl.create(:account)... which I am expecting to create an account, with the user and role from the factories above, with the user associated with the school that is associated with the district. This is not working for me. Among failing tests I get a "stack level too deep" error. And, I believe my before each DatabaseCleaner.clean is clearing the test db before each new factory.
The test that calls these factories is:
describe "User integration" do
def log_em_in
visit login_path
fill_in('Username', :with => "wwhat")
fill_in('Password', :with => "123456")
click_button('Log In')
end
it "tests log in" do
user = FactoryGirl.create(:account)
log_em_in
current_path.should == new_user_path
end
end
.
current_path.should == new_user_path returns unknown method error 'should'
How can I improve this code to nest the factories correctly and get a current_user in order to continue testing?
MODELS
school.rb
belongs_to :district
belongs_to :primarycontact, :class_name => "User"
has_many :users, :dependent => :destroy
user.rb
belongs_to :school
has_many :accounts, :dependent => :destroy
district.rb
has_many :schools
account.rb
belongs_to :role
belongs_to :user
role.rb
has_many :accounts
has_many :users, :through => :accounts
Your basic problem is that you have a circular dependency between your user factory and your school factory, caused by the fact that you create a primarycontact (a user) when you create a school, then that user creates a school, and so on.
You can get around this by changing how you define your school association inside the user factory. Before doing that though, I'd suggest as a general rule using the shorthand notation for associations. So replace this:
factory :account do
association :user, factory: :user
association :role, factory: :role
end
with this:
factory :account do
user
role
end
Using this simplification, the following factories will do what you want without generating any circular dependency:
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do |school|
district
primarycontact
name "Test School"
after_build do |s|
s.primarycontact.school = s
end
end
factory :user do
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
school
end
factory :primarycontact, class: "User" do
# add any attributes you want the primarycontact user to have here
end
factory :role do
name "student"
end
factory :account do
user
role
end
end
Notice that what I have done is to create a factory for primarycontact with the class: "User" option. Unlike the user factory, this factory does not create the school by default, avoiding the circular dependency.
Then in the school factory, I use an after_build callback to assign the school itself to the school association on primarycontact, rather than creating a new school (which was causing the problem in your factories).
Hope that makes sense. Note that the callback syntax has changed in the more recent version of factory_girl, see the documentation for details.

What things do I need to know when creating model from another controller and mass-assignment

I have a model called DefaultCompany that has no controller, instead I create it through the companies_controller which calls the user.set_default_company (defined below) if they check the "default company" checkbox on the form.
Default company is a joining table of user_id and company_id.
class DefaultCompany < ActiveRecord::Base
attr_accessible :company_id, :user_id
belongs_to :company
belongs_to :user
end
I keep getting the following error:
Can't mass-assign protected attributes: company, user
app/models/user.rb:22:in `set_default_company'
app/controllers/companies_controller.rb:23:in `create'
I've set my user model to be able to accept nested attributes for DefaultCompany, like this
class User < ActiveRecord::Base
has_one :default_company
accepts_nested_attributes_for :default_company
attr_accessible :default_company_attributes
def set_default_company(company)
exists = DefaultCompany.find(self.id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: self)
end
end
end
And here is the create action for the companies_controller.rb
def create
#company = Company.new(params[:company])
if #company.save
if params[:default_company]
current_user.set_default_company #company.id
end
flash[:notice] = "Company was successfully created."
Role.assign_creator(#company.id, current_user.id)
redirect_to #company
else
redirect_to new_company_path
end
end
So I'm not sure what I need to add so that mass-assignment will pass, can anyone help me figure out / explain this?
I believe rails is strict about the naming in mass-assignment, so although you've whitelisted company_id and user_id, you have not whitelisted company and user.
Try changing the assignment in set_default_company to:
if exists
exists.update_attributes(company_id: company.id)
else
DefaultCompany.create(company_id: company.id, user_id: self.id)
end
You can either change the attr_accessible attributes on Company to :user and :company or set :company_id and :user_id in your set_default_company method call.
Edit:
exists = DefaultCompany.find(self.id)
This seems to be wrong according to your logic.

Rail 3.2.2/Devise: deprecation warning with rspec

I recently upgraded an app to rails 3.2.2.
I'm using Factory_girl
Factory.sequence :name do |n| "name-#{n}" end
Factory.define :user do |u| u.first_name{ Factory.next(:name) }
u.last_name { |u| 'last_' + u.first_name } u.password 'secret'
u.password_confirmation { |u| u.password } u.sequence(:email) { |i|
"user_#{i}#example.com" }
end
and this simple test
specify { Factory.build(:user).should be_valid }
generate the following warning
DEPRECATION WARNING: You're trying to create an attribute user_id'.
Writing arbitrary attributes on a model is deprecated. Please just use
attr_writer` etc. (called from block (2 levels) in
at...
How can I get rid of it?
It's probably because you haven't prepared/migrated your test database with updated column definitions, thus it thinks you're trying to arbitrarily set the attribute.
Run rake db:test:prepare to make sure it's updated.
Here's the source code of that method, where you can see Rails checks for the column or attribute first, then warns if they're not found.
I've met the same warning with the following code:
Ad model:
class Ad < ActiveRecord::Base
belongs_to :user
end
Factories:
FactoryGirl.define do
factory :ad do
association :user
end
end
FactoryGirl.define do
factory :user do
first_name {Factory.next(:first_name)}
last_name {Factory.next(:last_name)}
email {|x| "#{x.first_name}.#{x.last_name}#{Factory.next(:count)}#test.com"}
password Forgery(:basic).password
confirmed_at Date.today << 10
end
end
Test
require 'spec_helper'
describe Ad do
before(:each) do
#ad = Factory.build(:ad)
end
"it is not valid without a user"
end
Running the test gave me a similar error.
Adding
attr_accessor :user
to the Ad model fixed the warning.
I hope it helps.
I had this same warning while doing tests in Rspec and my issue was that I had a Parent model and Child model where I accidentally had this:
class Child < ActiveRecord::Base
belongs_to :parent
end
......
class Parent < ActiveRecord::Base
belongs_to :child
end

Why does the "each" iterator method break rspec?

Background
I'm attempting to test my models.
app/models/user.rb
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
transactions
end
end
app/models/transaction.rb
class Transaction < ActiveRecord::Base
attr_accessor :user
belongs_to :payor, class_name: 'User'
belongs_to :payee, class_name: 'User'
end
In the Transactions class, #user is an ephemeral object instance representing the user accessing the model.
spec/models/user_spec.rb
require 'spec_helper'
describe User do
let(:user) { Factory(:user) }
let(:user2) { Factory(:user) }
let(:user3) { Factory(:user) }
let(:transaction_user_user2) { Factory(:transaction, payor: user, payee: user2) }
let(:transaction_user2_user) { Factory(:transaction, payor: user2, payee: user) }
let(:transaction_user2_user3) { Factory(:transaction, payor: user2, payee: user3) }
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end
end
Using rspec 2.6.4, factory_girl 2.1.2, rails 3.1.0, ruby 1.9.2p290. As shown, the spec passes.
Problem
When I modify the transactions method in app/models/user.rb to iterate over the results such that it reads:
class User < ActiveRecord::Base
has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id
def transactions
transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
transactions.each {|transaction| transaction.user = self}
transactions
end
end
the method transactions now returns [] in rspec, however it works perfectly in the app views.
Since Transaction.user is ephemeral (representing the user accessing the transaction) it must be set (if it exists) every time a Transaction is initialized or built from db records.
I'm at a loss for where to begin to debug this.
All suggestions appreciated!
I think your problem lies in the fact that let is lazy. Basically what is happening is that the transactions are not even created yet when the transactions method is called in the test. Use let! for a non-lazy version. See let and let! for more details.
Couldn't you just return payor_transactions + payee_transactions instead of manually selecting them?
Following the suggestion from #obrok, the solution I settled on to retain the advantage of lazy-loading let in other tests was to touch each transaction before testing User#transactions as so:
describe ".transactions" do
it "should include payor and payee transactions but not 3rd party transactions" do
[transaction_user_user2, transaction_user2_user, transaction_user2_user3].each do |transaction|
[transaction.payor_id, transaction.payee_id].each {|id| id.should_not be_nil }
end
user.transactions.should == [transaction_user_user2, transaction_user2_user]
user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
user3.transactions.should == [transaction_user2_user3]
end
end