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.
Tags: developers, Rails, Ruby, ruby on rails, single table inheritance
July 27, 2010 at 12:43 pm |
hi!
can you help me about single table inheritance testing using rspec?
October 14, 2010 at 10:50 am |
[...] http://kconrails.com/2010/01/26/single-table-inheritance-with-tests-in-ruby-on-rails/ [...]