ActiveRecord 能讓你不需寫 SQL 語法便能快速地存取資料,本文談談在 ActiveRecord 裡,是如何處理 Model 與 Model 之間的關係。

定義 Model 之間的關係

平常我們在描述兩個資料表之間的關係,通常可能會說兩張 table 是一對一、一對多、或是多對多,而在 ActiveRecord 裡,兩個 Model 之間的關係主要以上述的三種方式來定義。

延續上一篇文章中我所建立的三個 model: User, Video, and Comment,這三個 model 之間的關係如下:

  • User has_many videos:User 可以擁有多個 Videos
  • User has_many comments:User 可以擁有多個 Comments
  • Video belongs_to user:Video 必然會屬於某個 User
  • Comment belongs_to user:Comment 也會屬於某個 User
  • Video has_many comments:每個影片都可以有多筆 Comments
  • Comment belongs_to video:每個 comment 也會屬於某個 Video

看這樣的描述,如果是尚未接觸過 Ruby on Rails 或是 ActiveRecord 的讀者一定會感到疑惑,根本聽不懂我在說什麼嘛!請看下列三段程式碼,分別是上述三個 Model 的程式:

#Model: rails_app/app/models/user.rb
class User < ActiveRecord::Base
  has_many :videos
  has_many :comments
end
#Model: rails_app/app/models/video.rb
class Video < ActiveRecord::Base
  has_many :comments
  belongs_to :user
end
#Model: rails_app/app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :user
  belongs_to :video
end

看出什麼了嗎?只要使用很直觀的 has_many, belongs_to 這樣的 method 便可以輕鬆定義 Model 之間的關係。

定義資料表欄位:使用 Migration

接著,我們還必須仰賴 migration 替我們新增對應的資料表或欄位,在上一篇文章中已經完成了 User migration 的程式:

#Migration: rails_app/app/db/migrate/001_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string, :null => false
      t.column :email, :string, :null => false
      t.column :password, :string, :null => false
      t.column :nickname, :string, :null => false
      t.column :memo, :text
      t.column :created_at, :datetime
      t.column :updated_at, :datetime
    end
  end

  def self.down
    drop_table :users
  end
end

在上一篇文章中,除了 self.upself.down 兩個 method 之外,我並沒有交代其他程式碼所代表的意義,這邊稍微補充一下

  • create_table 代表建立一個新的資料表。透過對資料表進行管理的 method 尚有:deop_table, add_column, remove_column, rename_column 等等。
  • create_table :users 代表建立一個名為 users 的資料表,其後開始是一個 block
  • t.column :name, :string, :null => false 分別代表 “table 中的欄位”、”欄位名稱”、”資料型態”、”不可為 null”,這樣的寫法在 Ruby 是很常見的寫法,假設你不懂 Ruby、但卻對 Rails 有興趣,我建議你可以透過在學習 Rails 的過程中慢慢熟悉 Ruby 語言的寫法,我相信很快就上手。
  • 有關於建立欄位時的相關選項,可以參考 Rails 的文件,或是往後的文章或許會用到,我會再一一介紹。
  • 別忘了 created_at, updated_at 是 ActiveRecord 中的 Magic Column Name,它可以幫你在對資料表寫入資料的時候,自動產生資料建立或修改的時間。(雖然說寫入時間這種事情只要一行就可以解決)

接下來我們要繼續完成與 Video、Comment 這兩個 model 相關的 migration。migration 檔案通常是由 generator 在建立 model 的同時,就會自動新增一個 migration 的檔案,而 generator 也可以單獨產生一個 migration file 讓你對資料庫的 schema 進行改變。

#Migration: rails_app/app/db/migrate/002_create_videos.rb
class CreateVideos < ActiveRecord::Migration
  def self.up
    create_table :videos do |t|
      t.column :title, :string, :null => false
      t.column :description, :text
      t.column :user_id, :integer
      t.column :created_at, :datetime
    end
  end

  def self.down
    drop_table :videos
  end
end
#Migration: rails_app/app/db/migrate/003_create_comments.rb
class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :content, :text
      t.column :user_id, :integer
      t.column :video_id, :integer
      t.column :created_at, :datetime
    end
  end

  def self.down
    drop_table :comments
  end
end

從上面的 Migration 可以發現下列兩點:

  • Migrate 目錄裡面的檔案,檔名會依 001, 002, 003… 的次序為開頭,這代表 migration 的版本。例如你開發到第 10 版時,發現第十版新增的 table 或欄位是有錯的,你可以馬上將資料庫的 schema 還原到第九版。
  • 上一篇文中我有提到,Magic Column Name 中包含了「xxxx_id」這樣的格式,因此在 video、comment 的 table 中可以看到出現「user_id」、「video_id」這樣的欄位,往後 ActiveRecord 便是用這個欄位來替你找到對應的資料(自動幫你 join table)

將 Migration 定義的內容寫入資料庫

從上一篇文章透過 generate 指令來產生 model、migration files,到本篇文章我們實際去修改上述的檔案內容,如果熟練大概三分鐘就可以完成,接下來便可以開始感受到 ActiveRecord 所帶來的高生產力與享受: D

做完上述動作之後,我們便要將定義好的 table schema 寫入資料庫裡面,在 Rails 的目錄結構裡面,config/database.yml 便是在定義資料庫的相關設定值。設定資料庫的動作一般是在建立好 Rails Project 便該完成的,這算是開發 Rails project 的前置動作之一,那為甚麼我到現在才說咧?其實只是我忘了先講怎麼建立專案、怎麼設定資料庫罷了….. 以後如果要把這個系列彙整起來給 Rails 新鮮人看,再寫一篇好了 XD

關於 Rails Project 的前置動作可以參考 午夜盧比人 Winson 寫的「My Rails Way」,裡面提到的技巧都相當實用,像是第一點提到的

為了節省時間,我通常會維護一個叫做 sample 的 Rails project,裡面把簡易的帳號、密碼、登入做起來,以及設計一個 model,也把他的 tag、comment 功能作起來,並把相關的測試案例寫好、常用的外掛裝起來,然後整包壓成一個 sample.zip 檔。

倘若資料庫已經設定好,接下來會使用到「rake」指令來將我們規劃好的 schema 寫入資料庫裡面,指令如下: rake db:migrate 通常會得到下面的結果:

$ rake db:migrate
== CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0170s
== CreateUsers: migrated (0.0174s) =============================

== CreateVideos: migrating =====================================
-- create_table(:videos)
   -> 0.0090s
== CreateVideos: migrated (0.0094s) ============================

== CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0089s
== CreateComments: migrated (0.0093s) ==========================

如果沒有出現錯誤訊息就代表資料表皆成功建立,接下來我們便可以開始使用 ActiveRecord 來對資料庫進行各種關聯式的存取動作!