home icon contact icon rss icon

Archive for tag Bdd

A RSpec matcher for ActiveRecord validation

One thing that i don’t like about ActiveRecord is the way that it deals with validation messages. I don’t like the fact that it actually push a complete message into the errors stack.

Just to explain it a little better let me illustrate the situation with some code.

The current behavior is the following:

1
2
3
4
5
6
7
  class User < ActiveRecord::Base
    validates_presence_of :username
  end
  
  @user = User.new
  @user.valid?                # => false
  @user.errors.on(:username)  # => ["can't be blank"]

I really think it would be better to store the error messages somewhere else and push only an error identifier instead of the whole error message (i.e. “can’t be blank”). Then it would be translate to a message only when needed, probably in the view.

So we would have this instead:


  @user.errors.on(:username)  # => [:blank]

Someone might ask: what’s the difference? Well, since we would have to store the error messages somewhere it would actually be easier to support internationalization. Besides i don’t like the fact that when writing examples/tests i need to compare error messages to verify that a validation error exists.

Comparing error messages directly is bad. If you change the error message you have to change all the examples that check for that error.

A example that checks for presence of username would be something like this:

1
2
3
4
5
6
7
8
9
  describe User do
    before :each do
      @user = User.new
    end
    
    it "should require a username" do
      @user.errors.on(:username).should include("can't be blank")
    end
  end

Now if you decide to change the default error message for validatespresenceof to “is required”, you’d have to change all the examples that check against “can’t be blank”.

While this behavior is not changed – and i tried – i’ve found a good solution to check for validations in my specs that allows me to do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe User do
  include Matchers::ActiveRecordMatchers

  before :each do
    @user = User.new
  end
    
  it "should require a username" do
    @user.should have_error_on(:username, :blank)
  end
    
  it "should require a username of at least 3 characters" do
    @user.username = "ab"
    @user.should have_error_on(:username, :too_short, 3)
  end
    
  it "should require a username of at most 15 characters" do
    @user.username = "a"*16
    @user.should have_error_on(:username, :too_long, 15)
  end
end

Do you like it?

Then download this, save it as spec/matchers/activerecord_matchers.rb and put this in your spec/spec_helper.rb:

1
2
3
%W(matchers).each do |dir|
  Dir["#{RAILS_ROOT}/spec/#{dir}/*.rb"].each {|file| require file}
end

Enjoy!