Use Hash#except to easily test Rails validations

Ruby on Rails provides a large API for web development, including adding new methods to Ruby’s base classes. One method I don’t see used that often is Hash#except. From the Rails API documentation:

Return a hash that includes everything but the given keys. This is useful for limiting a set of parameters to everything but a few known toggles:

@person.update_attributes(params[:person].except(:admin))

A great use I found for it is to help test validation methods on Model objects. By defining a method that will return all the valid attributes on a model, you can easily exclude the ones you want to test with Hash#except.

I’m using RSpec in this example but the same idea works with Test::Unit or any other testing framework. I created a method valid_attributes that will return the attributes my Model needs to be valid. In this case my SystemNotification object needs a body, subject, and some users.

1
2
3
4
5
6
7
8
9
10
11
module SystemNotificationSpecHelper
  def valid_attributes
    user1 = mock_model(User, :mail => 'user1@example.com')
    user2 = mock_model(User, :mail => 'user2@example.com')
    return { 
      :body => 'a body',
      :subject => 'a subject line',
      :users => [user1, user2]
    }
  end
end

I used mocks for the users because I don’t care about them, only that they are present. So to test if my object is valid, I can easily just pass in valid_attributes to my new method:

1
2
3
4
5
6
7
8
describe SystemNotification, "valid?" do
  include SystemNotificationSpecHelper
 
  it 'should be valid with the body, subject, and users' do
    system_notification = SystemNotification.new(valid_attributes)
    system_notification.valid?.should be_true
  end
end

Now I want to write some more specs to make sure if a SystemNotification is invalid if it’s missing any of the required attribute. Since valid_attributes is a Hash, I can use Hash#except to remove each attribute individually and make sure the object is invalid for each case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe SystemNotification, "valid?" do
  include SystemNotificationSpecHelper
 
  it 'should be valid with the body, subject, and users' do
    system_notification = SystemNotification.new(valid_attributes)
    system_notification.valid?.should be_true
  end
 
  it 'should be invalid without a subject' do
    system_notification = SystemNotification.new(valid_attributes.except(:subject))
    system_notification.valid?.should be_false
  end
 
  it 'should be invalid without a body' do
    system_notification = SystemNotification.new(valid_attributes.except(:body))
    system_notification.valid?.should be_false
  end
 
  it 'should be invalid without any users' do
    system_notification = SystemNotification.new(valid_attributes.except(:users))
    system_notification.valid?.should be_false
  end
end

This is just a simple example but you can use Hash#except in many other places:

  • updating attributes like the Rail documentation sample
  • copying values from one object to another
  • extracting options from a argument

If you’d like some extra practice, refactor my specs to make sure SystemNotification#errors is populated based on what attribute failed. You can find the full code on GitHub, just send me a pull request when you’re done.

Eric