I have the following AR has_many, belongs_to relationships:
League --> Conference --> Division --> Team
I have an Event model that looks like this:
class Event < ActiveRecord::Base
belongs_to :league
belongs_to :home_team, :class_name => 'Team', :foreign_key => :home_team_id
belongs_to :away_team, :class_name => 'Team', :foreign_key => :away_team_id
validate :same_league
def same_league
return if home_team.blank? || away_team.blank?
errors.add :base, "teams must be in the same league" if home_team.league != away_team.league
end
end
And some factories:
FactoryGirl.define do
factory :league do
name 'NFL'
end
end
Factory.define :conference do |f|
f.name 'NFC'
f.association :league
end
Factory.define :division do |f|
f.name 'North'
f.association :conference
end
Factory.define :team do |f|
f.name 'Packers'
f.locale 'Green Bay'
f.association :division
end
FactoryGirl.define do
factory :event do
association :league
association :home_team, :factory => :team
association :away_team, :factory => :team
end
end
So with all that, how would I go about writing a spec for the same_league validation method?
describe Event do
pending 'should not allow home_team and away_team to be from two different leagues'
end
My issue is knowing what the simplest way to go about creating two teams in different leagues and associating one with home_team and the other with away_team in the event model.
You can store instances you generate with factories and then explicitly use their ID's to fill in the foreign keys for subsequent factories.
Here I'm creating two leagues, then setting up two tests. One where the event has two teams in the same league and another with two teams in different leagues. This way I can test if the event object is properly validating:
describe Event do
before(:each) do
#first_league = Factory(:league)
#second_league = Factory(:league)
end
it "should allow the home_team and away_team to be from the same league" do
home_team = Factory(:team, :league_id => #first_league.id)
away_team = Factory(:team, :league_id => #first_league.id)
event = Factory(:event, :home_team_id => home_team.id, :away_team_id => away_team.id)
event.valid?.should == true
end
it "should not allow the home_team and away_team to be from two different leagues" do
home_team = Factory(:team, :league_id => #first_league.id)
away_team = Factory(:team, :league_id => #second_league.id)
event = Factory(:event, :home_team_id => home_team.id, :away_team_id => away_team.id)
event.valid?.should == false
end
end
Related
I have a class named MenuHeader with the following:
class MenuHeader < ActiveRecord::Base
acts_as_tree :parent_id
belongs_to :menu
I'd like to create several levels of menu_headers but am not sure how to do this:
FactoryGirl.define do
factory :menu_header do
name "my menu header"
menu
after(:create) { |menu_header| do_something_else_to(user) }
end
end
Would I create a :menu_header2 or can I some how just extend the above. How do I set the :parent_id to the value of the recently created menu_header?
thx
I'd create different level of menu_header factories and build their children using after(:build). btw I prefer after :build because I can easily create non-persisted objects for testing.
FactoryGirl.define do
factory :menu_header do
name "my menu header"
menu
factory :menu_header_level1 do
after(:build) do |m|
m.children << FactoryGirl.build(:menu_header, :name => 'level0 submenu0', :parent => m)
m.children << FactoryGirl.build(:menu_header, :name => 'level0 submenu1', :parent => m)
end
end
factory :menu_header_level2 do
after(:build) do |m|
m.children << FactoryGirl.build(:menu_header_level1, :name => 'level1 submenu0', :parent => m)
end
end
end
end
New to RSpec and Factory Girl, and loosing this battle!
I have a join table MealItems which has validation on one of it's properties. In the rails console I can successfully do the following:
meal = Meal.create!( ... )
food = Food.create!( ... )
item1 = MealItem.create!( meal, food, 1234 ) # 1234 being the property that is required
I can then automagically get an array of foods in a given meal through the MealItem like this:
meal.foods
The problem is that I cannot figure out how to properly create factories so this relationship is available in the spec. I can assign the items to a meal, and test those, but cannot get the has_many through relationship working (meal.foods)
Models
class Meal < ActiveRecord::Base
has_many :meal_items
has_many :foods, :through => :meal_items
end
class MealItem < ActiveRecord::Base
belongs_to :meal
belongs_to :food
validates_numericality_of :serving_size, :presence => true,
:greater_than => 0
end
class Food < ActiveRecord::Base
has_many :meal_items
has_many :meals, :through => :meal_items
end
spec/factories.rb
FactoryGirl.define do
factory :lunch, class: Meal do
name "Lunch"
eaten_at Time.now
end
factory :chicken, class: Food do
name "Western Family Bonless Chicken Breast"
serving_size 100
calories 100
fat 2.5
carbohydrates 0
protein 19
end
factory :cheese, class: Food do
name "Armstrong Light Cheddar"
serving_size 30
calories 90
fat 6
carbohydrates 0
protein 8
end
factory :bread, class: Food do
name "'The Big 16' Multigrain Bread"
serving_size 38
calories 100
fat 1
carbohydrates 17
protein 6
end
factory :item1, class: MealItem do
serving_size 100
association :meal, factory: :lunch
association :food, factory: :chicken
end
factory :item2, class: MealItem do
serving_size 15
association :meal, factory: :lunch
association :food, factory: :cheese
end
factory :item3, class: MealItem do
serving_size 76
association :food, factory: :bread
association :meal, factory: :lunch
end
factory :meal_with_foods, :parent => :lunch do |lunch|
lunch.meal_items { |food| [ food.association(:item1),
food.association(:item2),
food.association(:item3)
]}
end
end
spec/models/meal_spec.rb
...
describe "Nutritional Information" do
before(:each) do
##lunch = FactoryGirl.create(:meal_with_foods)
#item1 = FactoryGirl.create(:item1)
#item2 = FactoryGirl.create(:item2)
#item3 = FactoryGirl.create(:item3)
#meal = FactoryGirl.create(:lunch)
#meal.meal_items << #item1
#meal.meal_items << #item2
#meal.meal_items << #item3
#total_cals = BigDecimal('345')
#total_fat = BigDecimal('7.5')
#total_carbs = BigDecimal('34')
#total_protein = BigDecimal('35')
end
# Would really like to have
#it "should have the right foods through meal_items" do
##meal.foods[0].should == #item1.food
#end
it "should have the right foods through meal_items" do
#meal.meal_items[0].food.should == #item1.food
end
it "should have the right amount of calories" do
#meal.calories.should == #total_cals
end
...
My question is:
How would I setup these factories so I can reference Meal.foods in my tests as I cannot assign foods directly to a meal because of the validation requirement on the join table. Am I not properly writing the MealItem Factories to the DB during the test, and that is why the has_many through relationship does not exist in my spec?
Any help is much appreciated.
Yes, you need a factory for MealItem, and you need to use that factory in the spec in order to pass the validation. If you didn't have any required fields other than the two foreign keys, it would probably work already.
So after setting up the factory with a default servering size, you would replace this:
#meal.meal_items << #item1
#meal.meal_items << #item2
#meal.meal_items << #item3
with this:
FactoryGirl.create(:meal_item, :meal => #meal, :item => #item1)
FactoryGirl.create(:meal_item, :meal => #meal, :item => #item1)
FactoryGirl.create(:meal_item, :meal => #meal, :item => #item1)
An alternative would be to set up a default serving size in your model. That would allow you to leave your tests as they were, and may make it easier to use your preferred syntax in your actual code as well.
I've been banging my head on this one for a while. Someone please resque me.
Scenario
I have the following models
class House < ActiveRecord::Base
has_one :tenancy, :dependent => :destroy, :as => :tenant
end
class LeaseAgreement < ActiveRecord::Base
has_many :tenancies
end
class Tenancy < ActiveRecord::Base
belongs_to :tenant, :polymorphic => true
belongs_to :lease_agreement
def lease=(lease)
if lease.rent_amount > 10000
# do something here
else
# do something else here
end
self.lease_agreement = lease
end
end
My factories
Factory.define :lease_agreement do |l|
l.name "Foo"
l.rent_amount 5000
end
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement) }
end
also tried this
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement, :tenant => tenancy) }
end
Both ways in my spec tests when I try this; #house = Factory(:house) I get the following error
NoMethodError: undefined method `rent_amount' for nil:NilClass
from /home/kibet/.rvm/gems/ruby-1.8.7-p352/gems/activesupport-3.0.5/lib/active_support/whiny_nil.rb:48:in `method_missing'
from /home/kibet/code/ruby/stuff/app/models/tenancy.rb:44:in `lease='
How would I go about this?
It looks like an order of operations problem, I think lease is being set to nil before it evaluates your after_build hook where lease is a legit LeaseAgreement instance.
You code can't handle a nil lease being passed in, which is a legitimate value if you want to clear the association. Try handling the nil like so:
class Tenancy < ActiveRecord::Base
belongs_to :tenant, :polymorphic => true
belongs_to :lease_agreement
def lease=(lease)
if lease.present? && lease.rent_amount > 10000
# do something here
else
# do something else here
end
self.lease_agreement = lease
end
end
The code as written will always produce an error with a nil lease passed in.
I think if you write your factory like this:
Factory.define :tenancy do |t|
t.name "Foo"
t.association :tenant, :factory => :house
t.after_build { |tenancy| tenancy.lease = Factory.create(:lease_agreement) }
end
your :lease_agreement will then be created correctly, and it should work. There is no tenancy for a lease_agreement.
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)
I'm using factory_girl_rails instead of fixtures. Here are my models:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
Here are my factories:
Factory.define :user do |u|
end
Factory.define :task do |t|
t.association :user, :factory => :user
end
In a test I do this:
user = Factory.create :user
(1..5).each { Factory.create(:task, :user => user)}
The problem that I'm experiencing is that afterward user.tasks contains only one task.
I have tried defining the user factory like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user)] }
end
and like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user), tasks.association(:user)] }
end
In both cases Factory.create(:user) causes an infinite loop.
I think what you're doing is backwards -- you can create Tasks from the User, but not visa versa like you're trying to do. Conceptually, the association goes from the user to the task.
What I did was create a special factory for that type of user.
Factory.define :user_with_tasks, :parent => :user do |user|
user.after_create {|u| 5.times { Factory.create(:task, :user => user) } }
end
Also, if you just want to use the default factory, you just do this
Factory.define :task do |f|
f.user :user
end
No need to specify the factory
UPDATE: Looks like others have done something similar in the past:
Populating an association with children in factory_girl
Ah, this works:
Factory.define :task do |t|
t.association :user
t.after_create {|t| t.user.tasks << t}
end
John, your approach would work but I actually couldn't use what you described because in my test the tasks need to also reference another (unmentioned) model and I don't see a way of referencing it in the user_with_tasks factory definition.