30
Sep

如同Title,懶得看我發牢騷的就跳過吧,一不小心就扯遠了:p

上回寫有關ActiveRecord的文章其實到現在都一直提不起勁來寫,一來是我認為基礎性的教學,網路上或書店裡都有很多參考資料,似乎輪不到我來寫;二來是進階的教學我又不夠力。但站在推廣的角度來說,關於Ruby on Rails的文章總是多多益善。

還記得以前我跟老師、同學們分享Ruby on Rails這個嶄新的框架時,快速在短短幾分鐘內依賴ActiveRecord及Dynamic Scaffold所搭建起來的簡單網站,讓在場的人都留下了滿深刻的印象。然而,對老師來說,他認為MVC pattern或ORM的實作雖然可以快速、有效地提高生產力,但這個框架的能耐有多大是他最關心的。

說實在,Ruby on Rails的能耐我也無從說起,因為Ruby on Rails能做到的,其他語言也都能做到,甚至其他語言、框架、技術有著更悠久的歷史、更成熟的架構,甚至更有力的後台,例如.NET, JAVA頗受企業青睞,出問題時即使自己公司內部的技術人員無法搞定,還可以求救於其他公司甚至原廠。

當時老師問我一句:「Ruby on Rails拿來做電子商務、處理金流、交易系統ok嗎?」事實上我當然認為是ok的,但我也很清楚要做這些事情"好像"不該拿RoR來做,總之,當下的我實在難以回答老師的問題,頓時我覺得自己目光如豆,我的視野大概就只停留在Web Development,所謂企業級的解決方案,我可說是個門外漢。(事實上我們那們課修的是Web 2.0研究,我報告純Web相關的應該沒錯啊XD如果是要拿Web做金流、電子商務,那RoR理所當然也是個選擇嘛,要加密、要搞Web Service、要連結不同的資料庫也都沒問題啊~:p)

老師似乎沒有太大興趣,那同學們呢?大概是我沒有表現出Rails的優美,同學們大概也只是當作我在報告給老師聽、而不認為我是在推廣-.-,總之後來我只成功讓一位同學繼續走上學習Ruby on Rails的道路,而他也成功讓一位女同學在某一門課的期末專題使用Ruby on Rails開發網頁。現在回頭想想,當初真是辛苦他們了orz

在那之後,如同日前我剛換上的新版面所言,我的工作、專案主要使用.NET Framework開發,Ruby on Rails是我閒暇時拿來娛樂的工具之一。主要原因有二,一是我認為除了網頁開發之外,我還想學習更多的技術;二是因為人在江湖、身不由己,我的工作、我的專案都是不得已要使用.NET的orz...

不過,學習Ruby on Rails的過程著實讓我成長不少, 寫Rails有時候是一種享受:D

這篇本來是想寫這一陣子開發Rails網頁,有關使用Helper的心得,不過既然已經扯遠了,那就下次再說吧XD

Category : Murmur | Blog
20
Sep

ericksk說:「既然找不到別人寫的,那就自己寫吧!」,因此他寫了一套jq4r,全名叫jQuery helpers for Rails。真是頗吸引我的,之前用過一陣子的jQuery,感覺還滿好用的,最近不但推出新版還搞了一套jQuery UI,之前在某個日文網站看到的評論稱之為「jQuery's Scriptaculous」。

anyway,ericsk的文章在此:jq4r - jQuery helpers for Rails

他另外開了一個blog及google code project專門來放jq4r:

Category : AJAX Framework | Ruby on Rails | Blog
18
Sep

利用ActiveRecord提供的dependent選項處理資料相依性

最近在ptt的Ruby板,有板友在討論刪除資料時如何進行驗證,原po的問題大致上是說倘若某一筆資料跟其他的資料有所關聯,該如何顯示錯誤訊息給使用者、告知因為有資料的關聯參照存在所以無法刪除。

看到這個問題,我心中第一個想到的是「就用CASCADE的方式把資料一口氣刪光啊!」XD不過隨即想想,大多數的資料可不是說想刪就能隨便刪的,例如在Ruby板上的討論串就有人提到要刪除某個分類名稱,但在刪除前要先將該分類底下的文章都指定為未分類。

總之,好奇心驅使之下,我猜想ActiveRecord裡面一定有針對關聯性特別設計一套機制來處理,果不其然其實在has_one, has_many底下就有:dependent來處理互相參照的資料。

:dependent有三個選項:(在此假設User has_many Posts, Post has_many Comments)

class User < ActiveRecord::Base
  has_many :posts, :dependent => :destroy
end

  • :destroy:呼叫User.destroy時,ActiveRecord會呼叫Post.destroy來達到刪除Posts的目的,也就是與該Posts相關聯的comments也會被一併刪除。(除非Post與Comments之間的關係是nullify)
  • :delete_all:呼叫User.destroy時,僅刪除User本身及該user的posts。
  • :nullify:不刪除關聯的物件,僅將User的posts之Foreign Key改為NULL

至於要在物件刪除之前,若要做驗證動作,就如同ptt Ruby的板主godfat分享的一樣,使用"before_destroy"可以在呼叫destroy之前先做其他動作。例如在這個網頁裡面看到的sample code(before_destroy是寫在Model裡,destroy的動作則是由controller中的action來呼叫):

class Group < ActiveRecord::Base
  acts_as_tree :o rder => "name"

  has_and_belongs_to_many => :users

  before_destroy :validates_no_dependents

  def validates_no_dependents
    if children.size > 0 || users.size > 0
      errors.add :base, "Cannot delete this group, as it has sub-groups and/or users"
    end
  end
end

Category : Ruby on Rails學習筆記 | Blog
18
Sep

上一篇文章裡稍微提到了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
通常會得到下面的結果:

deduce@nccu:~/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來對資料庫進行各種關聯式的存取動作!

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

Category : Ruby on Rails學習筆記 | Blog
15
Sep

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 => "搜尋條件", :o rder => "排序條件")來找資料,絕大多數的資料存取都可以靠這樣的方式完成,其他的可以靠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),感激不盡!

Category : Ruby on Rails學習筆記 | Blog