arthurlewis
arthurlewis

Reputation: 422

Factory Girl skips "before_add" callback on Rails Association

I can't seem to get FactoryGirl to call my before_add callback with an associated model.

I've got a Course model with:

has_many :lessons, dependent: :destroy, before_add: :set_lesson_number
def set_lesson_number
  #stuff
end

a Lesson model with:

belongs_to :course.

a Lesson factory with:

FactoryGirl.define do
  factory :lesson do
    course
  end
end

and a Course factory, defined as suggested by the Factory Girl wiki:

FactoryGirl.define do
  factory :course do
    factory :course_with_lessons do
      transient do
        lessons_count 10
      end

      after(:create) do |course, evaluator|
        create_list(:lesson, evaluator.lessons_count, course: course)
      end
    end
  end
end 

The before_add callback doesn't get called when I do FactoryGirl.create(:lesson), but it does get called if I do this:

lesson = FactoryGirl.build(:lesson)
course = lesson.course
course.lessons << l

In both cases, course.lessons ends up including lesson.

By the same token, FactoryGirl.build(:course_with_lessons) doesn't work with the above Course factory, but if I replace the create_list line with:

evaluator.lessons_count.times do
  course.lessons << build(lesson)
end

it does. It seems like FactoryGirl is creating the Lessons and setting their Course ID's, but somehow not actually "adding" them to the collection, so I have to do it manually.

Am I missing something about how FactoryGirl is supposed to work? Or about how ActiveRecord works?

Upvotes: 0

Views: 196

Answers (1)

Jordan Allan
Jordan Allan

Reputation: 4486

This is how ActiveRecord works.

If you run the following in your rails console you'll see the before_add callback on the association is not called:

course = Course.create
Lesson.create(course_id: course.id)

I imagine FactoryGirl.create_list generates objects in a similar way.

The lesson needs to be added to the collection in order for the callback to fire. This can be done in a couple of ways.

1. Create a Lesson through course

course.lessons.create

2. Explicitly add the lesson to course.lessons

course.lessons << lesson

3. Explicitly add a collection of lesson to course.lessons

course.lessons = [lesson1, lesson2]

To get the callback to fire, you could modify your factory like so:

  factory :course do
    factory :course_with_lessons do
      transient do
        lessons_count 10
      end

      after(:create) do |course, evaluator|
        course.lessons = 
          build_list(:lesson, evaluator.lessons_count, course: course)
        course.save
      end
    end
  end

Hope that helps.

Upvotes: 2

Related Questions