Adding Columns and Default Data to Existing Models

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

  def self.down
    remove_column :users, :rating

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')

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|

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.


Tags: , , , , , , , ,

3 Responses to “Adding Columns and Default Data to Existing Models”

  1. Senthil Says:

    Thanks Jamie. This takes out a lot of confusion I had before.

  2. Clay Says:

    I’m curious… I need to add some columns to my County model. I’m tracking population statistics and recently was given the requirement to track race statistics, too. There is A LOT of data. I already have a row in the database for each county in the US. How do I add columns and the data specific to them? If I have the data in a .csv file with the county ID as one of the columns, can I seed and require a match to that column?

  3. Rory Walker Says:

    Thanks Jamie. Fixed my error :)

Leave a Reply

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

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

Google+ photo

You are commenting using your Google+ 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 )


Connecting to %s

%d bloggers like this: