[Rails 學習心得] ActiveRecord:定義 Model 之間的關係
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.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
通常會得到下面的結果:
$ 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 來對資料庫進行各種關聯式的存取動作!