Single-Table Inheritance with Tests in Ruby on Rails


Single-table inheritance was a difficult concept for me to grasp when I first started using Ruby on Rails. Part of this might stem from the fact that it’s not a perfectly modeled concept in Rails itself. Relational databases like MySQL, PostgreSQL, and sqlite don’t have any concept of (inherited) sub-tables.

Let’s say you want to track pets. Cats and dogs need to be treated differently, but they basically have the same set of attributes, and share some methods as well. Here’s how we setup our models:

# app/models/pet.rb
class Pet < ActiveRecord::Base
  validates_attributes :name

  def speak
    ''
  end
end
# app/models/dog.rb
class Dog < Pet
  def speak
    'Woof!'
  end
end
# app/models/cat.rb
class Cat < Pet
  def speak
    'Meow!'
  end
end

Notice we include a default speak method in the parent class. This isn’t required, but it will help with any other pet types we may create in the future. This will be the default, so if we add a Fish category, we don’t need to define its own speak method separately.

All three models will share a common database table. In our migration, we’ll need to add a string column called “type”, that will contain the class name of the individual record:

class CreatePets < ActiveRecord::Migration
  def self.up
    create_table :pets do |t|
      t.string :name
      t.string :type

      t.timestamps
    end
  end

  def self.down
    drop_table :pets
  end
end

This is how Rails will know which records are cats, and which are dogs. You’ll usually never have to work with this column directly. Now calls to Dog.all will return only dog records from the pets table. Calling speak on a pet record will return “Woof!” for a dog record, and “Meow!” for a cat record.

Now, how do we test this? We should create a test file for each model class. We’ll test everything in that is included in the parent model in its test file, then only test the things that change in the child model tests:

# test/unit/pet_test.rb
require 'test_helper'

class PetTest < ActiveSupport::TestCase
  def test_should_validate_presence_of_name
    pet = Pet.new()
    assert !pet.valid?
    assert pet.errors.on(:name).include?("can't be blank")
  end

  def test_should_speak_blank
    pet = Pet.new
    assert_equal '', pet.speak
  end
end
class DogTest < ActiveSupport::TestCase
  def test_should_bark
    dog = Dog.new
    assert_equal 'Woof!', dog.speak
  end
end
class CatTest < ActiveSupport::TestCase
  def test_should_meow
    cat = Cat.new
    assert_equal 'Meow!', cat.speak
  end
end

Single-table inheritance is powerful, and only slightly tricky to grasp. It’s well worth learning.

About these ads

Tags: , , , ,

2 Responses to “Single-Table Inheritance with Tests in Ruby on Rails”

  1. Rafael Says:

    hi!

    can you help me about single table inheritance testing using rspec?

  2. Ereditarieta` in Ruby on Rails | tanasi.it Says:

    […] http://kconrails.com/2010/01/26/single-table-inheritance-with-tests-in-ruby-on-rails/ […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: