Archive for the ‘Database’ Category

Single-Table Inheritance with Tests in Ruby on Rails

January 26, 2010

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.

Adding Columns and Default Data to Existing Models

January 25, 2010

If you have an existing model in your Ruby on Rails application and you’ve already run the migration to create the table in the database, you may want to add columns later on. This is easy. Let’s say we have an ActiveRecord user model, and we want to add a rating for each user, to allow others to vote a user up or down. First, create the migration:

script/generate migration AddRatingToUsers

Or if you want to be fancy, we can use a little magic and specify our new column along with it:

script/generate migration AddRatingToUsers rating:integer

Rails is smart enough to figure out the table name from the migration name, and you’ll see this in the migration file that is created:

class AddRatingToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :rating, :integer
  end

  def self.down
    remove_column :users, :rating
  end
end

If you run the migration right away, you’ll run into problems because all the users that exist will have a rating of nil, and this will probably break any calculations you have in your code. Let’s update it with a default value:

    add_column :users, :rating, :integer, :default => 0

Now the database will default all existing and new users to a rating of zero.

More Complex Default Values

But what if you need a more complex default? Let’s say you want to add a unique API key for each user. Maybe we have a user instance method called “generate_api_key”, and it’s called whenever a new user is created:

class User < ActiveRecord::Base
  after_create :generate_api_key

  def generate_api_key
    self.update_attribute(:api_key, self.username + '123')
  end
end

But what about the users that were already in the database when we added the column? Should we put that in the migration, too? Absolutely not. It can be done, but migrations are not meant for data loading – only creating and changing the structure of your underlying database.

A better way is a rake task. These belong in the lib/tasks folder of your application, and they’re easy to create:

# lib/tasks/invitation_tokens.rake
namespace :seed do
  desc "generate api keys for users that don't have one already"
  task :api_keys => :environment do
    User.all(:conditions => {:api_key => nil}).each do |user|
      user.generate_api_key
    end
  end
end

Then, you can call this rake task from the command line like this:

rake seed:api_keys

This has a couple benefits. First, if you setup a new instance of your rails app on a different server (or even somewhere else on the same server) you can call this rake task to jumpstart your users. Even better, since you’re relying on a model method, you can unit test that method to make sure it performs as expected.

Restful Controller Tests with Shoulda – Stubbing

January 20, 2010

View the Source Code

This is part 2 of a 5 part series on restful controller tests, using Shoulda as the foundation. Here are all of them so far:

  1. The Basics
  2. Stubbing for Speed

Stubbing

My initial set of controller tests are a great foundation. They lay out exactly how to take advantage of Shoulda to create tests for three different roles of user: anonymous visitor, member, and admin. Now we’ll use the Mocha ruby gem to speed up our tests, by eliminating database calls.

Here’s my (amateurish) video of the changes, followed by a more detailed explanation:

We’ll start by ensuring that rails will require the gem. Add this to config/environments/test.rb:

config.gem 'mocha'

Then on the commandline:

rake gems:install RAILS_ENV=test

Mocha allows us to stub (or “fake”) methods on an object, and track how often they’re called during a test. For example, if you stub an object’s save method in a create action, you can test what happens when saving succeeds (returns true) or fails (returns false), with no messy database interaction.

While I won’t get into a primer on Mocha itself, I will say that almost every database interaction can be removed from functional tests, with the exception of user authentication. I usually leave that in, and that’s my one and only use for fixtures. Fixtures are fast, but cumbersome if you begin adding multiple records for every model in your application. I use factories instead, but they’re not as fast for loading the database. Factories are another chapter in the Functional Tests saga, however.

That said, let’s get on to reviewing the stubbed versions of my tests. Starting with the admin context, our setup changes from this:

    setup do
      @valid = Factory.build(:setting).attributes
      @setting = Factory :setting
      login_as :admin
    end

to this:

    setup do
      @setting = Factory.build :setting
      @setting.id = 1001

      Setting.stubs(:find).returns(@setting)
      Setting.stubs(:find).with(:all, anything).returns([@setting])
      
      login_as :admin
    end

Instead of creating a new setting, we’re using our factory to build an unsaved one. We’re giving it an id since saving would have normally done this. Finally, we’re stubbing out the find method, both for an individual and for all settings, so they return the one we’ve built. Loggin in as admin will be our only database hit.

Now for the actions. The index, show, and new action tests stay exactly the same, except the first two are no longer hitting the database – we’ve stubbed out the find method to automatically return our fake setting. On to the create method, in the “with valid data” context. Here’s the original:

context "with valid data" do
  setup do
    post :create, :setting => @valid
  end
        
  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully created."
        
  should "create the record" do
    assert Setting.find_by_name(@valid['name'])
  end
end

And here’s the stubbed version:

context "with valid data" do
  setup do
    Setting.any_instance.expects(:save).returns(true).once
    Setting.any_instance.stubs(:id).returns(1001)
          
    post :create, :setting => {}
  end

  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(1001)}
  should_set_the_flash_to "Setting was successfully created."
end

We’ve stubbed any instance of Setting to return true upon save, without actually saving. No database hit, and we still get to test everything. We’ve even dropped our “create the record” test, and stopped passing in valid data, because the expectation we set in the setup handles this. To be more paranoid, we could pass in valid data and check that those params end up in the Setting.new call, but I think that’s overkill since it’s such a basic step.

Next, the “with invalid data” context. First the original:

context "with invalid data" do
  setup do
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    Setting.any_instance.expects(:save).returns(false).once
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

The only changes here are that we’re setting a stubbed expectation of a call to save, forcing a return value of false, and again we don’t need to pass in valid data. Now we can run all the same tests, because we’ve forced the code down the failure branch.

Our edit tests stay the same, with the exception again of no database hit thanks to our stubbed finders. Next up is the update action, and we’ll start with the valid data context. Here’s the original:

context "with valid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => 'Bob'}
  end
  
  should_assign_to(:setting){@setting}
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully updated."
  
  should "update the record" do
    @setting.reload
    assert_equal 'Bob', @setting.name
  end
end

And here’s the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

Much like a successful create, we stub out our update to succeed and run all the same tests except that the record was actually changed. Now the invalid data context. Here’s the original:

context "with invalid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => nil}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And much like our unsuccessful create, we stub out the update to return false, and run the same tests! The finaly action that changes is update, and it’s very easy. First the original:

context "destroying" do
  setup do
    delete :destroy, :id => @setting.id
  end
      
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
      
  should "delete the record" do
    assert !Setting.find_by_id(@setting.id)
  end
end

And the new version:

context "destroying" do
  setup do
    @setting.expects(:destroy).once
    delete :destroy, :id => @setting.id
  end
    
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
end

This time we’re only stubbing out the call to destroy, expecting it to happen once. All the tests stay the same, except we no longer test that a record has actually been removed. No record was actually in the database, so we can’t. Plus, a failing call to destroy is so rare, it doesn’t even let you know in the return value.

The admin context is the only one that changes with these upgrades, members and visitors never get this far in our examples. This will generally make your functional tests over twice as fast, which is huge when you’re faced with mounting test times. I believe I was able to cut testing time down from 90 seconds to just under 30, implementing stubbing across an application’s test suite.

If you view the source code for this part of the series, you’ll see a lot of repetition in the tests. We’ll DRY (Don’t Repeat Yourself) that code in the next chapter in the series.

Running your Rails Test Database in Memory (RAM)

August 4, 2009

I recently read a blog post by Amr Mostafa that benchmarked running MySQL databases in memory. I’ve been trying to figure out how to do this, and he had the answer: use the tmpfs filesystem, which runs in memory, to store your database.  I’ll have to figure out just how difficult that is later, since I’m not a super DBA…or even really a DBA at all.

Amr is not a Rails developer, and the purpose of his benchmark was to simulate regular web traffic.  His results seemed ambiguous, but I noticed something missing in his trial: writes to the database.  His benchmark tests only used select statements, which read from the database.  While this is the majority of most database usage, I think the perfomance gain during writes would tip the scales decidedly in favor of running MySQL in memory, if you can afford the RAM.

This has an added benefit to us Ruby on Rails developers: we could potentially use it to dramatically increase the speed of our Test Driven Development, especially for those of use (should be all of us) using autotest!  TDD requires running tests every few minutes, or even seconds.  And writing to the database in tests is a lot bigger piece of the puzzle, since the database is recreated from scratch before every test.

For a lot of us, test suites are manageable.  Autotest only runs tests for changed files during normal development, occasionally running the entire test suite.  This, coupled with judicious mocking, stubbing and unit testing techniques can keep most test suites under control.  But larger apps use increasingly more tests, and higher-level tools like RSpec can be especially resource-intensive.

I tried to contact Mostafa about running some benchmarks for write speed, but my comment was considered spam!  I did get a smaller message through, so hopefully I’ll hear back.  If so, I’ll post a link to his thoughts/results.  Until then, I may dabble with doing this myself, and seeing what amateurish benchmarks I can run myself.

Retro-reading RailsSpace, part 3: Migration Magic

March 15, 2009

This is Part 3 in my series Retro-reading RailsSpace. Despite the book being slightly out of date, and geared toward beginners, it’s a classic. I’m reading it (finally) and posting what I learn.

First, I’m Cheating

I didn’t actually learn today’s migration mojo from RailsSpace directly. The book was more of an inspiration. I saw the old-school t.column call, and for the first time I wondered, “how does it work?”

The “What”

When I started using Ruby on Rails, this was just magic. And I didn’t question it…

create_table :posts do |t|
  t.column  :name, :string  # poof, a string column #
  t.string  :desc           # poof, a string column with even shorter syntax #
  t.integer :views          # poof, an integer column #
end

The “How”

So how does it work? The answer is the TableDefinition class we’ve been using. Don’t see it? It’s the t in t.column, t.string, and t.integer above. When we call create_table an instance of the TableDefinition class is created, and passed to the block (everything between do and end).

column is an instance method of TableDefinition, and adds the column definition to the TableDefinition class. t.string, t.integer, and others just call t.column on the backend.

The “Why”

I think it’s obvious why it was coded this way. Can you imagine a shorter, sweeter syntax? I experimented with lots of SQL shortcuts back in my Perl days, but nothing that worked this well. Blocks in general are a very cool aspect of Ruby, and Rails would lose a lot of its power without them.

Ruby on Rails: Bool vs. Boolean in SQLite3

July 18, 2008

I was looking at a new (to me) project today, and stumbled across a rails “gotcha” that needs attention. I tried to run rake test, but it failed. I received this error on each and every test:

rake test
  SQLite3::SQLException: no such table

There are something like 60 migrations in this project, so I went (almost) straight to db/schema.rb which gives you the all-in-one look at your database structure. That’s where I stumbled on this:

db/schema.rb
  # Could not dump table "generic_table" because of following StandardError
  #   Unknown type 'bool' for column 'generic_column'

it turns out a field was created with the type bool. I’m using SQLite3 for this project, but I did flip over to MySQL during debugging. Here’s what I found:

Under MySQL, bool and boolean appear to work just fine as column types. schema.rb is fine, tests are fine, and these two descriptors are synonymous.

Under SQLite3, boolean is the correct field descriptor. bool won’t break your migration, but:

  • rake db:schema:dump (which is called by rake db:migrate) will fail SILENTLY, and your schema.rb file will be corrupted.
  • rake db:test:prepare uses schema.rb, so it will fail, also SILENTLY.
  • tests will fail
  • rake db:reset also uses schema.rb, and so it will recreate a broken database.
  • Oddly, the bool field in ruby will return ‘f’ for false, and ‘t’ for true. If you set it to true, it does the “magic” on the backend, and there will be a ‘t’ in that field. Therefore, setting something to false doesn’t really work, because that field contains ‘f’, which equates to true.

Rails will do all of this without complaint. Your migrations, db:test:prepare, even code will run without raising red flags. Scary. Tests are where things will start breaking.

Ruby on Rails Schema Migration using schema.rb

June 24, 2008

A few months ago, we had a problem. We had a (relatively) mature application we wanted to deploy to a new server. In this case, waited to deploy to the production server until after several weeks of development/testing in stage. We deployed via Webistrano, then we tried to deploy:migrate, and that’s where things went south.

Migrations that were written and worked with version X of our app failed 10 versions down the road. You’re especially vulnerable when something that was a model attribute (ergo, a table field) before is now an instance method, or vice versa. For example, maybe password starts out as an attribute, but later becomes a method because you want password=(x) to generate a hash.

If you’re doing a fresh setup on a new server, and your app has been through several migrations already, you should ditch migrations for the initial deploy and run db/schema.rb instead, with the command rake db:schema:load. It will create the latest version of your database schema, including the current version number so future migrations will run without a problem.

There is even a note about this in db/schema.rb itself, which I just found today:

helpful hint provided in schema.rb itself
  Note that this schema.rb definition is the authoritative source for your
  database schema. If you need to create the application database on another
  system, you should be using db:schema:load, not running all the migrations
  from scratch. The latter is a flawed and unsustainable approach (the more
  migrations you'll amass, the slower it'll run and the greater likelihood
  for issues).

Also, db/schema.rb is a great place to see the current state of your tables! Just this morning I returned to a dusty project, and needed to know what table fields already existed, and which I needed to add. In fact, that’s how I stumbled on the above notice.


Follow

Get every new post delivered to your Inbox.