In this post, I’ll show how to write custom validations for Rails’ models, and how to test them with Shoulda, the ‘Makes tests easy on the fingers and the eyes’ testing plugin.

Rails’ ActiveRecord validations are great when you want to make sure attributes are the way you want them. But what about the use-cases where you need to write a custom one?

Creating the Validation

The validation we’ll write is called validates_positive_or_zero, which will return an error if a relevant integer attribute is negative.

Create the file RAILS_ROOT/lib/validations.rb with the following contents:

def validates_positive_or_zero(*attr_names)
  # Set the default error message.
  configuration = { :message => "Cannot be negative" }
  
  # Set a custom error message if there is one.
  configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  
  # Check none of the object's attributes are negative.
  validates_each attr_names do |obj, atr, val| obj.errors.add(atr, configuration[:message]) if val 

Note: This particular validation is complementary to validates_numericality_of, you’ll get some odd failures if you don’t use it.

We need to require this file before we can use the new validation in our model:

class Badger  'no-one is that light'
end

The first validates_positive_or_zero validates two attributes at once, whereas the second validates only one attribute with a custom error message.

Testing the Validation

I feel I should give a little introduction to Shoulda, it’s one of those tools that leaves me wondering how I ever managed before. Despite the popularity of RSpec within the Ruby community, I’m not a fan, it’s just too verbose, which I find gets in the way (it doesn’t render views though, which makes functional testing a lot easier, this feature for standard Rails tests please!). Shoulda is the answer for those of us who want standard Rails tests, but with them being neater and tidier. I encourage you to check it out more fully, but for this example, I’ll stay on-subject.

For our custom validation we could test it in the regular fashion with a test_foo method, or we could write a custom method that we could use again and again without re-writing the same code. The latter is the approach we are going to take here, with the method being in the style of the other shoulda validation tests.

First up, install the Shoulda plugin:

./script/plugin install git://github.com/thoughtbot/shoulda.git

Next, create the file RAILS_ROOT/test/shoulda_macros/validations.rb with the following contents:

class Test::Unit::TestCase
  
  def self.should_only_allow_positive_or_zero_values_for(*attributes)
    configuration = { :message => "Cannot be negative" }
    configuration.update(attributes.pop) if attributes.last.is_a?(Hash)
    klass = model_class
    attributes.each do |attribute|
      attribute = attribute.to_sym
      should "only allow positive or zero values for #{attribute}" do
        assert_bad_value(klass, attribute, -1, configuration[:message])
      end
    end
  end
  
  private
  
  # Taken from the shoulda private methods
  def model_class
    self.name.gsub(/Test$/, '').constantize
  end
  
end

Now we set up our tests in RAILS_ROOT/test/units/badger_test.rb:

require 'test_helper'

class BadgerTest  'no-one is that light'
  
end

And we’re done! I hope this was useful for you.