ActiveRecord:存取資料庫不用寫 SQL 語法?

首先,Ruby on Rails 對我而言,最大的衝擊便是 Rails 中的 ActiveRecord 實作了 ORM (Object-relational mapping),讓程式對資料庫進行存取的動作時,程式設計師不需要寫任何一行 SQL 語法,就可以存取到特定的資料表並找到自己想要的資料,沒接觸過 Rails 或是 ActiveRecord 的人,會認為:「真的有這麼神奇嗎?」

我可以告訴你,是的,真的就這麼神奇!,90% 的一般性需求不需要寫 SQL 語法,而剩下的 10% 特殊需求,Rails 又提供給你充足的彈性讓你自訂 SQL 語法對資料庫進行存取。

如此的作法正符合了 Convention Over Configuration 的精神,RoR 裡面使用大量的慣例來方便程式設計師進行開發,當慣例無法滿足需求時,她同時間會提供足夠的空間讓你自訂想要的功能。

舉例來說,假設要開發一個類似 Youtube 的網站,資料庫中起碼會有這三個資料表:

  • users:儲存使用者資料
  • videos:儲存影片的基本資料、發表時間(此例不考慮影像壓縮、儲存技術 XD)
  • comments:儲存使用者發表的影片評論

在 Rails 中的作法,通常會使用下列的語法來 建立與資料表相對應的三個 Model

ruby generate model user
ruby generate model video
ruby generate model comment

rails generate 指令除了會產生上述的三個 Model,同時還會產生三個對應的 migration, migration 中文通常翻作遷移,概念是資料庫的版本演進由 migration 來控制,包含新增、刪除資料表,或是新增欄位、刪除欄位、欄位類型變更、欄位名稱變更、欄位屬性變更等等,都可以在 migration 裡面完成。

由 migration 來進行資料表的管理,優點是無須在意後端使用的資料庫系統為何,migration 會自動產生對應到不同資料庫的資料型態及語法,因此無論後端是 MySQL、MSSQL Server、DB2、Oracle,ActiveRecord 的 migration 都可以替你完成資料表的建置與管理。(事實上可能不是每個資料庫系統都有 100% 的支援。)

rails generate 指令所產生的 migration 檔案通常如下:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
    end
  end

  def self.down
    drop_table :users
  end
end

上述的程式碼會在資料庫新增 table,其中定義了 self.upself.down 兩個 method,這代表當開發者對於目前對資料庫進行的修改感到不滿意時,可以馬上回到前一個版本(rollback),此時 migration 便會執行 self.down 的內容;換句話說,migration 裡面定義的是兩個相對的動作,例如:新增 table、移除 table;新增欄位、移除欄位。

接下來我將這個 CreateUsers 的 migration 修改如下:

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

我定義了七個欄位給 users 這張 table,如此的定義方式,在接下來的開發過程將會有幾個重要的效果:

  • ActiveRecord 的慣例是自動為每個 Table 建立 id 欄位,作為 Primary Key 以供存取;換句話說,表格內實際上會有我定義的七個欄位加上 id 共八個欄位。當然,這是慣例,你不想要 id 這個欄位也是可以的。
  • 接下來可以使用 User.find(1) 來找到 users 資料表中 id=1 的資料,背後其實是自動幫你完成一段 SQL 語法:SELECT * FROM users WHERE users.id=1
  • 你可以使用 User.find_by_name("linyiru") 或是 User.find_by_email("linyiru@gmail.com") 這樣的方式來找到特定的資料,背後實際上是轉換成類似 SELECT * FROM users WHERE users.email = 'deduce@gmail.com' 這樣的語法。這在 Ruby 裡是 Dynamic Methods、metaprogramming 的概念,她會根據 class definition 產生對應的 class method 來方便使用。
  • 另外 Find method 也可以用 User.find(:all, :conditions => "搜尋條件", :order => "排序條件") 來找資料,絕大多數的資料存取都可以靠這樣的方式完成,其他的可以靠 User.find_by_sql("SQL 語法") 來完成
  • 接著,下面這段程式可以簡單地在網頁上 show 出某個 user 的基本資料

        <% user = User.find(1) %>
        帳號:<%= user.name %>
        電子郵件:<%= user.email %>
        暱稱:<%= user.nickname %>
        何時註冊?<%= user.created\_at %>
    

    如何?是不是非常簡單而且非常直觀:)

  • 此外,注意到兩個欄位: created_atupdated_at,這樣的欄位名稱在 ActiveRecord 中稱為「Magic Column Names」,欄位只要是以 Magic Column Names 來命名,在資料寫入 Table 的同時,ActiveRecord 會自動幫你產生相關的資料。以 created_at 來說就是資料的建立時間,而 updated_at 則是資料的修改時間。常見的 Magic Column Names 還有 created_on, updated_on_at_on 的差別在於一個有記錄詳細時間、一個只記錄日期),xxxx_id 則是預設的 Foreign Key 名稱,例如我們稍後會在 video 及 comment 兩個 table 建立 user_id 當作 Foreign Key 以參照到 user table,如此便可以得知某個 video 或 comment 是屬於哪個 user。
  • 想統計目前站上有多少註冊會員?簡單,網頁上輸出一行 <%= User.count %> 便是目前的會員數了,或是想一口氣輸出全站 User 的資料,可以用如下寫法:

         <% users = User.find(:all) %>
           <% users.each do |user| %>
           User 序號:<= user.id %>
           帳號:<%= user.name %>
           電子郵件:<%= user.email %>
           暱稱:<%= user.nickname %>
           何時註冊?<%= user.created_at %>
         <% end %>
    

    (Ruby 也可以用 for user in users 的寫法,不過我比較習慣用.each

事實上,要深入談 ActiveRecord 的話,是可以寫成一本書的,最近才剛出版的一本新書「Pro ActiveRecord: Database with Ruby on Rails」內容便是在探討 ActiveRecord 的深入技巧,Amazon 上的 Book Description 中有一段話是這麼說的:

Because ActiveRecord is configured with default assumptions that mesh perfectly with the Rails framework, Rails developers often find they hardly need think about it at all. However, if you are developing in Ruby without Rails, or are deploying against legacy databases designed without Rails in mind, or you just want to take advantage of database-specific features such as large objects and stored procedures, you need the in-depth knowledge of ActiveRecord found in this book.

大致上是說,身為一個 Rails developer,由於 Rails 跟 ActiveRecord 有著許多的慣例(configured with defaul assumptions),因此我們通常不太需要想太多,反正用就對了。然而如果身為一個 Rails developer,或是你想單獨拿 ActiveRecord 來開發其他與資料庫有關的 project,就需要有更多深入的瞭解,這也是這本書的主要目的。