1. ValidAttribute 1.0

    12 June 2011

    I just released ValidAttribute 1.0 So what is ValidAttribute and why should you be using it?
    See the README for installation information.

    I grew frustrated with how I was writing the specs for my models. Very often I found myself doing this:

    describe User do
      it { should validate_presence_of :name }
      it { should validate_length_of :name, :is => 5 }
    end
    

    This felt wrong. I was duplicating the effort of writing the validations in the spec, the implementation of how these validations worked was now tied to the spec. My specs shouldn’t care about what validations my model is using. The only thing my specs should care about is if a given value is valid or invalid under certain circumstances. So I wrote out a simple DSL of how I wanted this to work:

    describe User do
      it { should have_valid(:name).when('Brian') }
      it { should_not have_valid(:name).when(nil, '', 'Ed') }
    end
    

    This felt much better. Now my specs do not care what validations I have declared on my model just as long as the values I provide are valid or not. I have been using this simple matcher for a few months and have found it provides a nicer BDD process than Shoulda or Remarkable. In fact, it is all I use to test my models now. (I won’t get into why specing your relationships in models is a waste of time)

    So what about other validations like uniqueness and confirmation. It is pretty simple:

    describe User do
      context '#email' do
        before { Factory(:user, :email => 'test@test.com' }
        it { should have_valid(:email).when('brian@test.com') }
        it { shoud_not have_valid(:email).when(nil, '', 'abc', 'test@test.com') }
      end
    
      context '#password' do
        before { subject.password_confirmation = 'password' }
        it { should have_valid(:password).when('password') }
        it { should_not have_valid(:password).when(nil, '', 'badpassword') }
      end
    end
    

    ValidAttribute also works with Test::Unit. You need to be using thoughtbot’s shoulda-context:

    class UserTest < Test::Unit::TestCase
      should have_valid(:name).when('Brian')
      should_not have_valid(:name).when(nil, '', 'Ed')
    
      context '#password' do
        subject { User.new(:password_confirmation => 'password') }
        should have_valid(:password).when('password')
        should_not have_valid(:password).when(nil, '', 'badpassword')
      end
    end
    

    ValidAttribute is compatible with any ActiveModel based model. It also supports ActiveRecord 2.x and Datamapper. In fact, as long as your model responds to #valid? and #errors you can use ValidAttribute. See the README for more information.

    ValidAttribute Vs Shoulda’s #allow_value

    Shoulda has a method its matchers depend upon called #allow_value which provides something similar to ValueAttribute. Here is the difference:

    describe User do
    
      # ValidAttribute Syntax
      context '#email' do
        before { Factory(:user, :email => 'test@test.com' }
        it { should have_valid(:email).when('brian@test.com', 'brian+blog@test.com') }
        it { shoud_not have_valid(:email).when(nil, '', 'abc', 'test@test.com') }
      end
    
      # Shoulda's #allow_value Syntax
      context '#email' do
        before { Factory(:user, :email => 'test@test.com' }
        %w{brian@test.com brian+blog@test.com}.each do |email|
          it { should allow_value(email).for(:email) }
        end
        [nil, '', 'abc', 'test@test.com'].each do |email|
          it { should_not allow_value(email).for(:email) }
        end
      end
    end
    

    #allow_value only takes one value, and the wording feels backwards.

    To accomplish the same task #allow_value needs to iterate over a collection of values and inject each into the matcher. ValidAttribute can take any number of values. Clean code is very important to me.

    Here is another example:

    describe User do
    
      # ValidAttribute Syntax
      context '#email' do
        before { Factory(:user, :email => 'test@test.com' }
        it { should have_valid(:email).when('brian@test.com', 'brian+blog@test.com') }
        it { shoud_not have_valid(:email).when(nil, '', 'abc', 'test@test.com') }
      end
    
      # Shoulda's #allow_value Syntax
      context '#email' do
        before { Factory(:user, :email => 'test@test.com' }
        it 'should have valid email' do
          %w{brian@test.com brian+blog@test.com}.each do |email|
            subject.should allow_value(email).for(:email)
          end
        end
        it 'should not have valid email' do
          [nil, '', 'abc', 'test@test.com'].each do |email|
            should_not allow_value(email).for(:email)
          end
        end
      end
    end
    

    In this 2nd example the iterators for #allow_value are inside of an ‘it’ block. If the validators have not been implemented yet ValidAttribute will give you a failure message containing all the values that caused a failure. Shoulda will stop on the first value.

Notes

  1. kbedell reblogged this from bcardarella
  2. bcardarella posted this