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!
