This simple example uses DataMapper's before :save
callback (aka hook) to increment callback_count
. callback_count is initialized to 0 and should be set to 1 by the callback.
This callback is invoked when the TestObject is created via:
TestObject.create()
but the callback is skipped when created by FactoryGirl via:
FactoryGirl.create(:test_object)
Any idea why? [Note: I'm running ruby 1.9.3, factory_girl 4.2.0, data_mapper 1.2.0]
Full details follow...
# file: models/test_model.rb
class TestModel
include DataMapper::Resource
property :id, Serial
property :callback_count, Integer, :default => 0
before :save do
self.callback_count += 1
end
end
# file: spec/factories.rb
FactoryGirl.define do
factory :test_model do
end
end
# file: spec/models/test_model_spec.rb
require 'spec_helper'
describe "TestModel Model" do
it 'calls before :save using TestModel.create' do
test_model = TestModel.create
test_model.callback_count.should == 1
end
it 'fails to call before :save using FactoryGirl.create' do
test_model = FactoryGirl.create(:test_model)
test_model.callback_count.should == 1
end
end
Failures:
1) TestModel Model fails to call before :save using FactoryGirl.create
Failure/Error: test_model.callback_count.should == 1
expected: 1
got: 0 (using ==)
# ./spec/models/test_model_spec.rb:10:in `block (2 levels) in <top (required)>'
Finished in 0.00534 seconds
2 examples, 1 failure
At least for factory_girl 4.2
(don't know since which version it is supported), there is another workwaround through the use of custom methods to persist objects. As it is stated in a response to an issue about it in Github, it is just a matter of calling save
instead of save!
.
FactoryGirl.define do
to_create do |instance|
if !instance.save
raise "Save failed for #{instance.class}"
end
end
end
Of course it is not ideal because it should be functional in FactoryGirl core, but I think right now it is the best solution and, at the moment, I'm not having conflicts with other tests...
The caveat is that you have to define it in each factory (but for me it wasn't an inconvenient)
Solved.
@Jim Stewart pointed me to this FactoryGirl issue where it says "FactoryGirl calls save! on the instance [that it creates]". In the world of DataMapper, save!
expressly does not run the callbacks -- this explains the behavior that I'm seeing. (But it doesn't explain why it works for @enthrops!)
That same link offers some workarounds specifically for DataMapper and I'll probably go with one of them. Still, it would be nice if an un-modified FactoryGirl played nice with DataMapper.
Here's the code suggested by Joshua Clayton of thoughtbot. I added it to my spec/factories.rb
file and test_model_spec.rb
now passes without error. Cool beans.
# file: factories.rb
class CreateForDataMapper
def initialize
@default_strategy = FactoryGirl::Strategy::Create.new
end
delegate :association, to: :@default_strategy
def result(evaluation)
evaluation.singleton_class.send :define_method, :create do |instance|
instance.save ||
raise(instance.errors.send(:errors).map{|attr,errors| "- #{attr}: #{errors}" }.join("\n"))
end
@default_strategy.result(evaluation)
end
end
FactoryGirl.register_strategy(:create, CreateForDataMapper)
Well. perhaps I spoke too soon. Adding the CreateForDataMapper fixes that one specific test, but appears to break others. So I'm un-answering my question for now. Someone else have a good solution?
Use build to build your object, then call save manually...
t = build(:test_model)
t.save
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With