5
votes

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.

1

1 Answers

0
votes

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.