我曾寫過一篇「Rails: 建立Permalink,避免流水號洩漏網站資料」針對有人擔心(或質疑)Ruby on Rails中以資料表主鍵流水號作為URL中的參數,會導致網站洩漏某些不希望被外界一眼看穿的資訊,例如使用者總數或文章總數之類的數據,因此簡單地透過Model在存取資料庫時的before_create方法建立每一筆資料的unique key來避免這個問題。

之前使用Rails 1.2.x版本時根據我當時的作法並無不妥,因為我自己的coding習慣是會完整地指定controller, action以及id,並在對應的action中使用find_by_key的方法來找到資料。

然而,升級到Rails 2.0後便會出現許多麻煩,例如預設產生的scaffold必須修改許多地方,才能讓Model.find_by_key、redirect_to post_url(@post)之類的方法正確運作。

於是我一直到前陣子才找到應該比較正規的作法:在Model中使用to_param方法。

to_param(): Enables Active Record objects to be used as URL parameters in Action Pack automatically.

這麼好用的方法我居然不是一開始就發現,只能說相見恨晚,好險我開發Rails 2.0的時間還不算長,沒走到太多冤枉路。要實踐本標題所說的在Rails 2.0中如何避免在URL使用資料流水號便相當簡單!

1. 建立Scaffold:(通常會對key這個欄位建立index並設定字串長度limit,別忘了:p)
script/generate post title:string content:text key:string

2. 將Post的migration寫進資料庫後修改Post model如下:

class Post < ActiveRecord::Base
  before_create :generate_key
  
  def self.find(*args)
    if args.first.is_a?(String)
      find_by_key(args.shift, *args) or raise ActiveRecord::RecordNotFound
    else
      super
    end
  end
  
  def to_param
    key
  end
  
  protected
  # 我前一篇文章產生key的方式不太一樣,這應該是個人習慣。
  # 另外,其實亂碼的網址沒有SEO的效益,建議還是弄個slug吧!
  def generate_key
    require 'digest/sha1'
    self.key = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )[15..24]    
  end
end

就這樣!概念很簡單,就是不再用ID當查詢的參數,全面改用key啦!關鍵就在於使用to_param()方法以及對find做進一步的判斷,因為我除了Model.find(:all)之類的Symbol,其餘的CRUD都是傳入Key作為參數,因此在此僅簡單地判斷是否為字串。

如此一來,不管你是什麼user_posts_url, new_user_post_url都可以輕鬆地直接以ActiveRecord Object傳遞,起碼我之前曾經嘗試用edit_post_url(@post.key)這種方式傳值,非常麻煩、要改Controller又要改View,現在用了to_param(),什麼都不用改了!又優雅又簡單,寫起來更加快樂、效率也提昇了:p

P.S. 其實在Rails 1.2.x應該也是這麼做比較優雅,不限於2.0