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 script/generate model user
ruby script/generate model video
ruby script/generate model comment

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

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

由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.up及self.down兩個method,這代表當程式設計者對於目前對資料庫進行的修改感到不滿意時,可以馬上回到前一個版本,此時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這個欄位也是可以的:D
  • 接下來你可以使用User.find(1)來找到在users資料表中id=1的資料,背後其實是自動幫你完成一段SQL語法:"SELECT * FROM users WHERE users.id=1"
  • 當然,你可以使用User.find_by_name("deduce")或是User.find_by_email("[email protected]")這樣的方式來找到特定的資料,背後實際上是幫你轉換成類似"SELECT * FROM users WHERE users.email = [email protected]'"這樣的語法。這在Ruby裡面是Dynamic Methods的概念,她會根據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_at及updated_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來reference到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,就需要有更多深入的瞭解,這也是這本書的主要目的。

我想這或許會是一本值得購入的好書吧?:D

這篇文章簡單地介紹了ActiveRecord如何讓Rails developer輕鬆地存取資料,下一篇再來談Model與Model之間要如何運作,文中有任何錯誤或對本文有任何指教請留言或來信告知(deduce_at_gmail_dot_com),感激不盡!