上一篇文章裡稍微提到了ActiveRecord能讓你不需寫SQL語法便能快速地存取資料,(事實上我只提到了讀取的部份,寫入其實也相當簡單,依樣畫葫蘆,只是使用的method不同罷了)這篇便來談談在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.up、self.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
通常會得到下面的結果:

[email protected]:~/test$ rake db:migrate
(in /home/deduce/test)
== 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來對資料庫進行各種關聯式的存取動作!

註:實在是有點拖稿,不過我寫這些文章是希望可以盡量淺白,所以盡量詳細、盡量簡單,一樣,有任何錯誤或建議請多指教。