Ruby on Rails 3 Model

Generate Rails Model

Generate the DB migration and rollback code

% rails generate model Account user_name:string description:text premium:boolean \
          income:integer ranking:float fee:decimal birthday:date login_time:time
      invoke  active_record
      create    db/migrate/20110223173431_create_accounts.rb
      create    app/models/account.rb
      invoke    test_unit
      create      test/unit/account_test.rb
      create      test/fixtures/accounts.yml

Files Created

In directory File Description
app/models account.rb Model Account file
db/migrate 20110223173431_create_accounts.rb Migration code: Ruby code to create the DB table "accounts"
test/fixtures accounts.yml Test fixture for Account
test/unit account_test.rb Unit test code
  • DB Migration filename begins with a creation timestamp (this timestamp is often refer as the version number)

Rails migration code generated

"~/my_sys/my_app/db/migrate/20110222020736_create_accounts.rb"
class CreateAccounts < ActiveRecord::Migration
  def self.up
    create_table :accounts do |t|
      t.string :user_name
      t.text :description
      t.boolean :premium
      t.integer :income
      t.float :ranking
      t.decimal :fee
      t.date :birthday
      t.time :login_time

      t.timestamps
    end
  end

  def self.down
    drop_table :accounts
  end
end

The generated migration code is simply a Ruby program and therefore any Ruby programming code can be added

  def self.up
    ...
    Account.update_all ["fee = ?", 100]
  end
  • Adding logic to set the value of existing data to a specific value
    • For example, set the "fee" column to 100 for all rows in "accounts"

To execute the DB changes using rake

rake db:migrate
  • rake db:migrate calls self.up to make the DB changes
  • create_table :accounts creates a DB table "accounts"
  • t.string :user_name creates a DB column "user_name" of type string
  • DB logic is wrapped within a transaction if the underneath DB engine supports it

To roll back the last DB changes using rake

rake db:rollback
  • rake db:rollback calls self.down to rollback the DB changes
  • drop_table :accounts drop the DB table "accounts"

Generated DB schema

mysql> describe accounts;
+-------------+---------------+------+-----+---------+----------------+
| Field       | Type          | Null | Key | Default | Extra          |
+-------------+---------------+------+-----+---------+----------------+
| id          | int(11)       | NO   | PRI | NULL    | auto_increment |
| user_name   | varchar(255)  | YES  |     | NULL    |                |
| description | text          | YES  |     | NULL    |                |
| premium     | tinyint(1)    | YES  |     | NULL    |                |
| income      | int(11)       | YES  |     | NULL    |                |
| ranking     | float         | YES  |     | NULL    |                |
| fee         | decimal(10,0) | YES  |     | NULL    |                |
| birthday    | date          | YES  |     | NULL    |                |
| login_time  | time          | YES  |     | NULL    |                |
| created_at  | datetime      | YES  |     | NULL    |                |
| updated_at  | datetime      | YES  |     | NULL    |                |
+-------------+---------------+------+-----+---------+----------------+
  • A primary column id is automatically added
  • t.timestamps creates 2 columns created_at and updated_at of type datetime to track when changes are made

To display created_at or updated_at

    <td><%= account.updated_at.strftime("%Y-%m-%d %I:%M %p") %></td>
  • The Ruby type of created_at & updated_at is DateTime

Create a custom migration file

Create a migration file

% rails generate migration AddCouponCodeToAccounts
      invoke  active_record
      create    db/migrate/20110223180124_add_coupon_code_to_accounts.rb

Create a migration file with new DB columns

Rails assumes migration name with AddXxxToYyy means adding column to a table

% rails generate migration AddCouponCodeToAccounts coupon_code:string coupon_date:time

Corresponding Rails DB Migration file

db/migrate/20110223180124_add_coupon_code_to_accounts.rb
class AddCouponCodeToAccounts < ActiveRecord::Migration
  def self.up
    add_column :accounts, :coupon_code, :string
    add_column :accounts, :coupon_date, :time
  end

  def self.down
    remove_column :accounts, :coupon_code
    remove_column :accounts, :coupon_date
  end
end

Use native SQL type

    add_column :accounts, :coupon_code, "SMALLINT(5) UNSIGNED", :null => false

Create a migration file to remove DB columns

Rails assumes migration name with RemoveXxxFromYyy means removing column from a table

% rails generate migration RemoveCouponCodeFromAccounts coupon_code:string coupon_date:time

Corresponding Rails DB Migration file

class RemoveCouponCodeFromAccounts < ActiveRecord::Migration
  def self.up
    remove_column :accounts, :coupon_code
    remove_column :accounts, :coupon_date
  end

  def self.down
    add_column :accounts, :coupon_date, :time
    add_column :accounts, :coupon_code, :string
  end
end

Active Record Methods in making DB changes using rake

class CreateAccounts < ActiveRecord::Migration
  def self.up
    create_table :accounts do |t|
  ...
Rails Migration Methods Description
create_table Create a new DB table
change_table Modify an existing DB table
drop_table Drop a table
add_column Add a new column to a table
change_column Change a DB column
rename_column Rename a DB column
remove_column Remove a DB column
add_index Add a DB index
remove_index Remove a DB index

Define a new DB table with Rails

Define a DB Column in Rails

    create_table :accounts do |t|
      t.string :user_name

To explicitly set a native DB column type

create_table :members do |t|
  t.column :income, 'SMALLINT UNSIGNED', :null => false
end

Mapping Rails DB type to MySQL column type

Rails Column Type MySQL Type Comment
primary_key int(11) t.primary_key user_id : Set this to be the primary key
string varchar(255)  
text text  
boolean tinyint(1) 0 for false, 1 for true
integer int(11)  
float float  
decimal decimal(10,0)  
date date  
time time  
binary blob  
datatime datetime  
timestamp datetime  

MySQL enum type

Migration code

      t.column :status, "ENUM('init', 'success', 'fail')"

Model

class Member < ActiveRecord::Base
   validates_inclusion_of :status, :in => [:init, :success, :fail]

   def status
     read_attribute(:status).to_sym
   end

   def status= (value)
     write_attribute(:status, value.to_s)
   end
 end

Define Foreign Key in Rails

class CreateItems < ActiveRecord::Migration
  def self.up
    create_table :items do |t|
      t.references :order

      ...

    end
  end
  ...
end
  • Use the model name order rather than the DB table name
  • Rake will create a DB column order_id referencing the orders table

The foreign key constraint however is not created and needed to be added programmatically by executing native SQL using "execute"

def self.up
  ...
  execute <<-SQL
      ALTER TABLE items
        ADD CONSTRAINT fk_items_orders
        FOREIGN KEY (order_id)
        REFERENCES orders(id)
 SQL
end

def self.down
   ...
   execute <<-SQL
      ALTER TABLE items
          DROP FOREIGN KEY fk_items_orders
SQL
end
  • For MySQL, neither tables can be using MyISAM which does not support foreign key constraint

Polymorphic association

t.references :order, :polymorphic => {:default => 'express'}
  • Create a order_id column reference the orders table
  • polymorphic define a new column order_type with default value set to express

Override Primary Key Setting

Override the default name for the primary key

# Use user_id as the column name for the primary_key
create_table :members, {:primary_key=>:user_id} do |t|
...
end

Do not generate primary key

create_table :members, {:id=>false} do |t|
...
end

Setting Native DB Options

For example, use InnoDB Storage Engine (default) as MySQL storage engine

create_table :members, {:id=>false, :options => "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" } do |t|
...
end

Define meta-data for DB Column

create_table :clients_vendors, :id => false do |t|
   # Column cannot be null
   t.integer :client_id, :null => false
   t.integer :vendor_id, :null => false

   # Set the column size VARCHAR(10)
   t.string :comment, :limit => 10, :null => false
end
DB Meta-Data Description
:default => "unknown" Set column default value
:limit => 10 Set DB column size
:null => true DB column cannot be null
:decimal :precision :scale Define precision

Modify a DB schema with Rails

  1. Generate an empty migration script
    % rails generate migration AddBiosToMember
    
  2. Add the migration code
    db/migrate/20090930225306_add_bios_to_member.rb
    class AddBiosToMember < ActiveRecord::Migration
      def self.up
        change_table :members do |t|
          t.string :bios
          t.boolean :bios_hidden, :default => false
        end
        # Set all old records to true
        Member.update_all ["bios_hidden = ?", true]
      end
    
      def self.down
        remove_column :members, :bios
        remove_column :members, :bios_hidden
      end
    end
    
  3. Modify the DB table "members"
    % rake db:migrate
    

change_table

Use change_table to modify DB schema

change_table :members do |t|
  t.string :bios                     # add_column
  t.remove :ranking, :fee            # remove_column
  t.rename :login_time, :t_signin    # rename_column
  t.index  :birthday                 # add_index
end

Other migration methods changing DB Schema

Rails DB migration methods in modifying a Db Schema

Modifying index

add_index :tbl1, [:col1, :col2], :unique => true
remove_index :tbl1, :column => [:user_name]

Modifying column

add_column :members, :bios, :string
change_column :members, :dob, :string
rename_column :members, :login_time, :t_signin
remove_column :members, :ranking
change_column_default

Modify table

rename_table :old_table_name, :new_table_name

Execute Native SQL

class CreateMembers < ActiveRecord::Migration
  def self.up
    ...
    execute <<-SQL
       ALTER TABLE members CHANGE income m_income INTEGER;
    SQL
  end

Reload Column Meta-data in Rails

Account.reset_column_information
  • Allows the migration code to use the new schema definition immediately after the code that adds the new column

rake Command

rake command Description
db:migrate Update DB with the latest change & update db/schema.rb to match the entire DB Schema
db:rollback Rollback DB changes
db:drop Drop all tables
db:reset Drop tables and rebuild it with db/schema.rb
db:version DB data version

Dump Database Schema

Update the whole DB schema schema.rb again

rake db:schema:dump db/schema.rb

To change the format of the dump file

config/application.rb
config.active_record.schema_format = :ruby    # Dump the schema as RoR migration code
config.active_record.schema_format = :sql     # Dump the schema as SQL statements

To dump the DB schema manually to db/{Rails.env}_structure.sql in SQL

rake db:structure:dump RAILS_ENV=production

Reload the database

rake db:schema:load

Migrate DB to a specific version

Command Target version
rake db:migrate version=20091003212243 Migrate up to 20091003212243

Or migrate down before 20091003212243
rake db:migrate:up version=20091003212243 Up to 20091003212243 or do nothing
rake db:migrate:down version=20091003212243 Down before 20091003212243 or do nothing
rake db:rollback step=2 rollback 2 version
rake db:migrate:redo step=2 rollback 2 version and redo the changes again