should factories validate their callbacks?
alexdean opened this issue · comments
This is only a question at this point. Could become a feature request depending on feedback.
Problem this feature will solve
I recently created a factory like
FactoryBot.define do
factory :location, class: Location do
before(:build) do |location, evaluator|
do_a_thing
end
end
end
I was very confused about why my do_a_thing
method was never being called. I eventually (re)-disovered that before(:build)
isn't a supported callback. Duh. I changed this to after(:build)
and problem solved. Reflecting on why I made this mistake, I'd say I'm just very used to the ActiveRecord callback system, where most callback events support both a before
and after
permutation.
I wondered if there would've been a way for FactoryBot to warn me that I was defining a callback that would never be called, since that would've saved a lot of time & head-scratching.
To be clear: I don't think what FactoryBot is doing here is wrong. I was just looking for a way to avoid this pitfall in the future.
I poked in the code a while, and it seems like this kind of validation might be hard to implement in general because strategies don't express which callbacks they support.
- Is that a correct assessment? This is my first time digging into FactoryBot internals so I may have missed something.
- Could this kind of validation be added for the default
:build
and:create
strategies maybe? I have to guess that would cover the large majority of FactoryBot users, so it might be worthwhile even if the general case (including custom strategies) isn't easy.
Desired solution
This is only a guess...
# lib/factory_bot/strategy/build.rb
module FactoryBot
module Strategy
class Build
def association(runner)
runner.run
end
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:after_build, instance)
end
end
def to_sym
:build
end
# NEW THING: allow strategies to express which callbacks they support
def supported_callbacks
[:after_build]
end
end
end
end
# lib/factory_bot/factory.rb
module FactoryBot
class Factory
def run(build_strategy, overrides, &block)
block ||= ->(result) { result }
compile
strategy = StrategyCalculator.new(build_strategy).strategy.new
# THIS IS THE NEW PART
if strategy.respond_to?(:supported_callbacks)
unknown_callbacks = @definition.callbacks.map(&:name) - strategy.supported_callbacks
if unknown_callbacks.size > 0
# maybe a warning here?
end
end
# continue with process here... snipped from example for clarity.
end
From the existing callback specs, it seems like maybe this isn't even a desired feature? I noticed a factory in the test suite which defines a number of callbacks which would never be invoked by a normal FactoryBot.build()
, but the tests use this factory for many :build
calls.
I only raise this because the current approach (to silently ignore callbacks which are not understood by the current strategy) definitely caused me some confusion and I thought I'd ask if others might have the same issue. Thanks for your feedback & consideration!
Good find on that spec. Yeah, I think the challenge is that a callback might be valid for one strategy but not another. Like after(:create)
doesn't do anything when using FactoryBot.build
, but it does do something for FactoryBot.create
.
Like after(:create) doesn't do anything when using FactoryBot.build, but it does do something for FactoryBot.create.
oh good point. Hadn't considered that. So I guess this is a wontfix. :( Thanks for the input!