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.
Related
I have an account model that belongs_to a role model.
factory :role do
name "student"
end
factory :account do
user
role
end
The first factory creates a role named "student". The second factory creates an account that is associated to the student role that was created in the previous factory. It also is associated with a user...which is not important for this question.
I have many roles to be tested (admin, student, assistant)... I dont want to specify 'student' in the role factory...thats too static. How do I specify what role to create at the time the account factory is created? Like:
factory :account do
user
role_id { factory :role { name: "admin"} }
end
What is the best way to accomplish this?
If you want a purely FG solution, you could use Traits:
factory :account do
user
trait :student do
association :role, :name => "student"
end
trait :admin do
association :role, :name => "admin"
end
end
FactoryGirl.create :account, :student
FactoryGirl.create :account, :admin
However, you can override the properties of the factory when you create the factory object. This allows for more flexibility:
FactoryGirl.create(:account,
:role => FactoryGirl.create(:role, :name => "student")
)
Since this is obviously verbose, I'd create a little helper method:
def account_as(role, options = {})
FactoryGirl.create(:account,
options.merge(:role => FactoryGirl.create(:role, :name => "student"))
)
end
Then in your tests:
let(:account) { account_as "student" }
Alternately, you could just shorten up your role generator so you could use it like:
def role(role, options = {})
FactoryGirl.create :role, options.merge(:name => role)
end
account = FactoryGirl.create :account, :role => role("student")
I have a User model:
class User < ActiveRecord::Base
has_many :cards
end
and a Card model:
class Card< ActiveRecord::Base
belongs_to :user, :foreign_key => "owner_id"
end
the card model also has an attribute called "owner_id", which I'd like to use in way like this:
Card.first.owner which will retrieve the User which owns that card
my problem as that, I know that rails will automagically connect the id's in the association but that doesnt happen.
in the CardController, rails get stuck in the create action on the line
#card=current_user.cards.new(params[:card])
and says unknown attribute: user_id
I've done db:migrate and it still won't work.
must I do as follows for it to work?
#card = Card.new(params[:card])
#card.owner_id=current_user.id
or am I missing something?
First of all, you don't need a owner_id column for this. All you need is
class User
has_many :cards
end
This will give you #user.cards
class Card
belongs_to :owner, :class_name => "User", :foreign_key => "user_id"
end
This will give you #card.owner
I've been following this guide on the Separate Role Model implementation in CanCan. When a User, tries to sign up this error is thrown when creating the Assignment.
User(#21477600) expected, got Symbol(#5785720)
I'm using a Devise generated User with the following before_save functions
class User < ActiveRecord::Base
.
.
.
def create_profile
profile = Profile.new :user_id => :id
end
def create_role
Assignment.new :user => :id, :role => Role.find_by_role("user").id
end
end
I want to default the user's role to "user", but I'm obviously doing something wrong. How should this be implemented?
Not sure if you've seen this or not, but Ryan Bates has produced a wonderful document regarding:
Separate Role Models
EDIT:
Here's what I am currently using. I believe your 'Assignment' is the same as my 'UserRole'.
user.rb
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :roles, :through => :user_roles, :uniq => true
#--
# Instance Method
# Determine if the user has a specified role
# You can find this method at: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
# Written by Ryan Bates, I added the downcase though to detect 'Admin' vs 'admin'.
# Example:
# user.has_role? :Admin
# => true
def has_role?(role_sym)
roles.any? { |role| role.name.underscore.to_sym == role_sym.downcase }
end
role.rb
# id :integer(4) not null, primary key
# name :string(255)
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :users, :through => :user_roles, :uniq => true
user_role.rb
# id :integer(4) not null, primary key
# user_id :integer(4)
# role_id :integer(4)
#--
# Relationship
belongs_to :user
belongs_to :role
Then in my ability.rb
def initialize(user)
user ||= User.new # in case of a guest
if user.has_role? :Admin # The user is an Administrator
can :manage, :all
else
can :read, :all
end
end
Then I can easily assign roles, like in my seed file by doing something like:
# Create Users
...
# Roles
admin = Role.create!(:name => "admin")
standard = Role.create!(:name => "standard")
# UserRoles :Admin
user1.roles << admin
user2.roles << standard
So by calling user.roles << [role_name], I am essentially creating a UserRole, which has a user_id and a role_id.
There might be some more effective ways to accomplish this, but I cant tell without the exact model associations.
Anyway, I think this should work:
def create_role
Assignment.new :user => self, :role => Role.find_by_role("user")
end
Since you specify :user and not :user_id, you should pass self. The same thing for :role. If you had specified :role_id then you should have entered .id after find_by_role but since you only specify :role then remove .id
It looks like you're passing symbols to hash conditions that are expecting objects.
DanneManne's answer should work. You could alternatively do
Assignment.new( :user_id=>self.id, :role_id => Role.find_by_role('user').id )
(but Danne's is better, imo)
One last suggestion -- why not say the name of the role is "name", not "role". So then you'd be doing, Role.find_by_name('user'). That would be easier for a subsequent programmer to follow.
Firstly, you should not use save callback because it will be fired on both create & update.
Secondly, if you set up associations between models like that:
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
end
class Profile < ActiveRecord::Base
belongs_to :user
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
You will have convenient methods like user.profile, user.build_profile and user.create_profile. Build & create will set up user_id on profile automatically. You can use them in your callbacks without having to define any methods.
Note that before user is saved it does not have an id. So you need to use either before_create :build_profile either after_create :create_profile. The first one will create profile in memory that will be autosaved after user is saved, the second one is pretty straightforward.
There will be similar methods for assignments too: user.assignments.build user.assignments.create. So the final code for User will look something like this
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
after_create :create_profile, :create_assignment
def create_assignment
assignments.create :role => Role.find_by_role("user")
end
end
I have the following:
class Org < ActiveRecord::Base
has_many :users
has_many :entries
end
class Entry < ActiveRecord::Base
belongs_to :org
belongs_to :user
validates_presence_of :entry_text
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end
I can Create Orgs and Users... How do i create an entry if there are two belongs_to? and what is this pattern called?
Double nested resources are tricky. The trick with users usually is to keep it out of your desired entry path.
Your question is kind of broad, but if you specify more information, people would be able to help you better. Also, I would recommend using the gem Devise for your user management system. Since you're using 'users' I would assume you want users from orgs to create entries. The entry created would be a part of org and the user would be the session's current user. Sorry if I am wrong to assume this.
Your routes.rb file can look something like this (assuming rails 3):
resources :orgs do
resources :entries
end
Then the create of your entry controller would look like:
#entry = #org.entries.new(params[:topic])
#entry.user = current_user #or however you are managing the current user's session.
And you'd want to set the org for the entire class by making a method that loads your current org and do a before_filter :loadOrg
def loadOrg
#org = Org.find(params[:id])
end
This is of course assuming your path is something like: /org/(id)/entry/(entry_id)
and not
/org/(id)/user/(user_id)/entry/(entry_id)
which in my opinion is unnecessary and can lead to more problems. You can always create a userpage model that calls all entries by users, but the default route doesn't necessarily have to include users in the path.
I don't see any problem.
#entry = Entry.create(:entry_text => "Hello World!")
Now questions to clarify what do you need:
Can #entry belongs both org and user at the same time? Or it can belongs to only one of them?
Should #entry belongs to at least one of them?
If #entry supposed to belong only one of them, so you should use Polymorphism
http://railscasts.com/episodes/154-polymorphic-association
class Entry < ActiveRecord::Base
belongs_to :textable, :polymorphic => true
validates_presence_of :entry_text
end
class Org < ActiveRecord::Base
has_many :users
has_many :entries, :as => :textable
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries, :as => :textable
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end
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.