This site provides the following access keys:

Brandan Lennox's

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.