DRY FactoryGirl after create/build hooks
Asked Answered
M

2

7

I want to DRY up the after create/build hooks in my Factory:

FactoryGirl.define do

  factory :poll do

    sequence :title do |n|
      "MyPollTitle#{n}"
    end
    sequence :description do |n|
      "MyPollDescription#{n}"
    end
    user

    factory :poll_with_answers do

      ignore do
        answers_count 2
      end

      after(:build) do |poll, evaluator|
        evaluator.answers_count.times do
          poll.answers << build(:answer, poll: poll)
        end
      end

      after(:create) do |poll, evaluator|
        evaluator.answers_count.times do
          poll.answers << create(:answer, poll: poll)
        end
      end
    end
  end
end

The problem I was facing is that it seems I can't define methods in FG? Idea how to DRY this up?

Michaelmichaela answered 18/7, 2012 at 5:51 Comment(0)
M
7

Firstly, after(:create) implicitly calls after(:build), at least in recent versions of FactoryGirl:

after(:build) - called after a factory is built (via FactoryGirl.build, FactoryGirl.create)

https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#callbacks

So in your case, the following code should be sufficient:

after(:build) do |poll, evaluator|
  evaluator.answers_count.times do
    poll.answers << build(:answer, poll: poll)
  end
end

However, after(:build) isn't triggered when you use build_stubbed() instead of build(), which is what I was trying to do when I came across this thread. To DRY up this code, it turns out you can use the callback() method to call the same block for multiple methods:

factory :user do
  callback(:after_build, :after_stub) do |user|
    do_something_with(user)
  end
end
Mastectomy answered 25/2, 2014 at 19:14 Comment(0)
F
1

This might be a cheap trick, but you can create a lambda in the second factory:

factory :poll_with_answers do
  ignore do
    answers_count 2
  end

  make_answers = lambda do |poll, evaluator, method|
    evaluator.answers_count.times do
      poll.answers << send(method, :answer, poll: poll)
    end
  end

  after(:build) do |poll, evaluator|
    make_answers.call poll, evaluator, :build
  end

  after(:create) do |poll, evaluator|
    make_answers.call poll, evaluator, :create
  end
end

I'm not at all happy with this pattern, but at least it DRY's stuff up.

Farrar answered 18/7, 2012 at 8:28 Comment(7)
It should do the trick. It's pretty equivalent to your code, so if the evaluator is nil in the lambda version, then it should be nil in your version too. Can you gist me both the original working code and the code that gets nils (also the stacktrace). I don't have sufficient information to debug currently.Farrar
You don't need to invest any more work in this, it doesn't matter much, I basically just wanted to check if theres a shortcut. Anyway i made a gist at gist.github.com/3140033 where you can see the models, the rest is 1:1.Michaelmichaela
Well, no worries, but after checking out the code, I have no idea what evaluator is :)Farrar
It says its a class lol but in fact its some FactoryGirl instance that holds a reference to the ignore-attribute (answers_count). If I run my code, I can see that evaluator is not nil, if I run your code evaluator is nil. I guess its some FactoryGirl anomaly causing this, this library is kinda bad.Michaelmichaela
It should not have anything to do with factory_girl - this should be pure ruby semantics. Damn it, it got me really curious :) . Can you tell me where my code is nil? In the lambda, in after(:create) or in both? Futhermore, this is a long shot, but can you try renaming the variables to e1, e2 and e3 in the lambda and the two blocks. It should yield the same result, but I just want to be extra certain, since there is a bunch of weird rules with blocks and scope in Ruby.Farrar
Hi, sorry I forgot about this question. I tried your suggestions and the code is nil in both cases, also renaming the variables isn't working. I think this is really FactoryGirl specific and not worth our effort anymore, b/c its just a few lines and no big deal to have duplicated code in a Factory imho.Michaelmichaela
FWIW, I got similar code working by moving the lambda outside of the factory definition (when it was inside it, it got caught by method_missing somehow).Bonefish

© 2022 - 2024 — McMap. All rights reserved.