Machinist and :validates_uniqueness_of
I’ve lately been having a problem with Machinist while running specs on models using :validates_uniqueness_of
. Sometimes an example will fail with a spectacularly unhelpful “Item is invalid” error message when it ran fine just moments before.
I eventually discovered that when an example fails, Machinist doesn’t always destroy all the records it created. This means that data may be left in your test database between examples or invocations of your spec task (if you’re using script/spec
). I had been solving this problem by emptying a few important tables in a before(:all)
block, but that destroys fixtures that other specs may depend on. So I couldn’t do that anymore.
The problem actually lies with Sham. It will attempt to generate unique values for the lifetime of a single invocation of a spec, but one of its selling points is replicability. Each invocation of a spec results in the same sequence of values for a given attribute. So if the first User generated by Machinist gets the login “buntaluffigus,” and the example fails for some reason, then that user persists to the next run of the spec, at which point Sham will generate the login “buntaluffigus” again, and assuming User logins must be unique, this new model will fail validation and you may simply see that some “Item is invalid.” Sucko.
So blah blah blah, ultimately I had to monkey patch Machinist::Lathe
to check for any attribute who :validates_uniqueness_of
itself. It depends on Christopher Redinger’s validation_reflection plugin and this file in spec/support
:
# spec/support/machinist_monkey_patches.rb
module Machinist
class Lathe
private
def generate_attribute_value_with_uniqueness_check(attribute, *args, &block)
value = generate_attribute_value_without_uniqueness_check(attribute, *args, &block)
if attribute_must_be_unique?(attribute)
while object.class.first(:conditions => { attribute => value })
value = generate_attribute_value_without_uniqueness_check(attribute, *args, &block)
end
end
value
end
alias_method_chain :generate_attribute_value, :uniqueness_check
def attribute_must_be_unique?(attribute)
object.class.reflect_on_validations_for(attribute).detect { |v| v.macro == :validates_uniqueness_of }
end
end
end
Now all my examples run like they’re supposed to, regardless of fixtures or previous failures or the position of Ares in the fourth quadrant. YESSSSSS.