我的網誌已搬家至blog.miaout17.net,本網誌將不再更新

2010年10月26日 星期二

AlgoCat 開發筆記 (Rails3+MongoDB)

大概從三、四個月前起,為了加強自己coding的基本功,我開始參加TopCoder及Codeforces的線上算法解題競賽。到目前為止,成績其實不太理想。在這些比賽的過程中,最能督促自己努力上進的事,莫過於被其他人慘電吧(Ex. 白俄羅斯高中生tourist名列TopCoder Algorithm Rating前五名)TopCoder官網雖然可以簡單的查詢一個人的成績,但無法追踨其他人的競賽狀況,並做出比較。

因為最近在練Rails3 + MongoDB,於是選了這個當作自己練習的題目,專案名稱暫定為"AlgoCat",也就是「演算貓」(Algorithm Cat)。這個名字是想告訴自己「在演算法的世界中,一隻大貓努力向上爬」。AlgoCat的目標為「追踨算法競賽的活動及競賽成績,並標記算法競賽及題庫中,解過及待學習的題目」。

AlgoCat還相當的不完整,可以說還在Pre-Alpha的階段吧。目前AlgoCat提供的功能僅有:查詢TopCoder Algorithm Rating的排行、觀看TopCoder使用者的成績記錄、追踨某些特定的TopCoder使用者並進行比較(以折線圖方式呈現)。

這篇文章其實不是打算announce這個網站,而是將自己的開發筆記整理一下。目前的成果可以在algocat.heroku.com看到。

Environment & Framework
  • Rails 3
  • MongoDB
  • Mongoid
  • Devise
目前是Deploy在Heroku+MongoHQ上,不排除移到其他地方。這些環境的設定可以參考上一篇。

Fetching Data from TopCoder

TopCoder本身提供了XML Feed的介面,用Ruby抓取資料只要簡單地用Net::HTTP和和REXML即可。

由於AlgoCat並不是一開始就抓好TopCoder的所有資料。有些資料是等使用者查詢才向TopCoder抓,並存放在資料庫。這方面的行為應該用Delayed Job之類的方法放在背景處理。AlgoCat在開發初期就有想到這點,但因為早期開發實作方便,還是暫時在前景處理。之後會移到background task去。BTW, Heroku第一隻worker就要收錢....之後可能會想些替代方案。

Mongoid

基本上照著官網的教學,應該可以做到大部份的功能。但要用NoSQL的資料庫,只學Object Mapper而不了解資料庫本身,應該會造成最佳化的困難。討論一些我踩到的問題好了:

@users = User.asc(:rating).limit(10)
現在,呼叫多次@users.each,其實會造成多次的query。解決方法官網有寫,只要呼叫cache,或是直接@user=@user.to_a亦可

上面這個算是小問題,只是要熟析Mongoid的細節。但另一個問題是Mongoid缺乏了對MongoDB的一些簡單低階操作。舉例來說,如果db中有一個articles的collection,而每篇文章中有一個以tags為key的array,array中存放了tag的字串。依MongoDB的官方教學,要增加一個tag的query可以這樣下:
db.articles.update( { title: "Rails3" }, { $addToSet : { tags: "Web" } } );
或者這樣亦可
db.articles.update( {title: "Rails3", tags: {"$ne": "Web"}}, {"$push": { tags : "Web" } } );
這兩者者可以確保「在文章原本沒有該tag的情況下,將tag插入」

但Mongoid的封裝似乎只支援這樣的動作:
article = Article.where( :title=>"Rails3" ).first
unless article.tags.elem? then
article.tags << Devise
article.save
end

原本是DB上的一個query,變成一定要將資料拉回app server判斷,再將「所有tags」寫回去,效能應有不小的差距。

Rails jquery-ujs

Rails內建了一些與JavaScript&AJAX相關的helpers,並以rails.js與prototyle javascript library橋接。如果想用jQuery,又想保留rails的helpers功能的話,可以用jquery-ujs中的rails.js將原本的蓋掉,就可以將原本prototype相關的javascript刪掉,換上jquery。

AlgoCat目前的「追踨使用者」(Watch)功能是用AJAX + jQuery做的。

Chart Library
要在網頁中繪製圖表,目前應該有三大主流:
1.在Server端繪製圖表,再傳送圖片到Client端
2.萬惡Flash (?)
3.以JavaScript為基礎,操作HTML Canvas之類的方式繪作圖表

其中第一點是傳統的方法,從以前的GnuPlot,到現在可以把資料丟到雲裡給Google畫圖(Google Chart API),但其缺點是比較不容易做圖表的互動操作。我個人不喜歡使用Flash作圖表,覺得多了一層整合上的麻煩,而且有點拖慢網頁的速度,但Flash應是最容易實作圖表互動操作的方式。而隨著瀏覽器對Canvas的支援趨近完整,JavaScript Engine運行的速度越來越快,用JavaScript在Client繪製圖表也成了選擇之一。而且靠JavaScript及DOM,同樣也可以進行與圖表的互動操作。

在開發AlgoCat時,一開始我選擇嘗試googlecharts gem,結果發現目前的版本還滿雷的。首先,我遇到了Linc Chart中,y軸座標值完全不對的問題。後來我發現其實這個Issue是同一個問題。試了一下後,決定git clone一份googlecharts下來,git bisect找bug。後來是把這個問題修掉了,但似乎還有其他小問題存在。之後有空應該會整理一下,發patch或pull request吧…

後來考慮到可能要在圖表上做互動,所以還是換回JavaScript-based的flot。Flot是一套以jQuery為基礎的JavaScript Plotting Library。

BTW,因為flot相依於jQuery,所以rails內建的Prototype一定要移掉,不然會相衝。我使用了前段提到的jquery-ujs。

Ongoing work
目前最大的問題,應該就是Fetch XML DATA應該要放在背景做吧。然後會補強自動更新的機制、加上Codeforces或Uva題庫功能等…

Source Code?
目前還沒有打算open source,而且可能還會大規模的重構,等我整理好code後有可能會open。

Rails3, Mongoid, Devise, Heroku, MongoHQ筆記

最近在看一些NoSQL的東西,於是想以MongoDB進行一些實作的練習。這篇文章是我在練習過程中,環境設定的筆記。

Installing Rails 3

請看我的上一篇文章

Installing MongoDB

我的開發環境是Ubuntu和Windows(在家用Ubuntu、出外用Windows,所以試了兩個環境)。Linux的話,個人建議不要用package system(apt/yum)的MongoDB。首先,絕對不要apt-get install mongodb。我一開始練習MongoDB時,是用apt-get裝的,會抓到一個舊到爆炸(1.4.4)的stable版本,這個版本mongo console client打錯指令還會crash(這是stable版嗎?)。

如果一定要用package的話可以考慮apt-get install mongodb-unstable或apt-get install mongodb-snapshot。但我是直接去官網抓build好的版本,再sudo copy到/usr/bin就好了。

MongoDB Object Mapper: Mongoid

Mongo的Ruby Object Mapper有很多種,可以參考官網上的列表。我選用Mongoid的原因有二:
  1. Mongoid的官網做的還滿有模有樣的,看樣子應該有認真維護 XD
  2. Devise認證機制內建支援Mongoid
安裝照著官方installation文件做就好了:

gem i mongoid --pre
gem i bson_ext

如果是Windows系統的話,mongo gem需要build一些native code,所以推薦裝RubyDevKit,裝完就可以順利的安裝mongo相關gems。

Mongoid使用上非常的簡單,Mongoid蓋掉了ActiveRecord的generators。要快速建出一個能動的環境,在設定好gems後只需要:

rails g mongoid:config
rails g scaffold article title:string content:string

只要mongod開著,這樣就可以動了,不用做data migration。唯一會踩到的雷可能是用了舊版的mongodb,所以就如前面提到的,請不要apt-get install。

Authentication: Devise

Devise是一套不錯的rails認證機制,而且內建對Mongoid的支援
一樣照著官方文件做就能動,整合非常簡單。安裝:
gem i devise

整合進app:
rails generate devise:install
rails generate devise User

接下來只要在view裡把link接一接就好。如果原本就用過devise,基本上用ActiveRecord或Mongoid沒啥差別。要速查link path helpers的話可以用rake routes。

唯一要注意的地方是要記得自己打index,我原本以為Model中的
devise :database_authenticatable, :registerable ......
這行,會自動在Mongoid Model上加上index。但rake db:mobgoid:create_indexes後,User collection上還是沒有任何index,所以要自己在Model上加:
index :email, :unique => true
然後再執行一次rake db:mongoid:create_indexes,把index打上去。

另外,有人做了Rails3+Mongoid+Devise的整合包。但Devise和Mongoid的整合實在太簡單了,不彷自己做一遍,也比較容易了解細節。

Deployment Environment: Heroku & MongoHQ

Heroku是一個雲端的Rails Hosting Platform,其使用方式應該很多文章介紹過了,這裡就不多廢話。基本上注冊好heroku帳號後,在rails app路徑下:
heroku create appname
git push heroku master
就可以把app deploy上去。

MongoHQ是一個雲端的MongoDB Hosting服務,提供了免費(16MB Limit)及付費的MongoDB空間。由於Heroku與MongoHQ有合作,並將MongoHQ做成了Heroku的一個addon,所以整合特別簡單。這裡是官方文件,只要下一行指令:
heroku addons:add mongohq:free
heroku就會自動向MongoHQ註冊一個資料庫,並設定rails的環境變數。注意官方文件有個地方沒講的很清楚,在config/mongoid.yml中,多加上mongohq的設定:
production:
uri: <%= ENV['MONGOHQ_URL'] %>
設定好後,MongoID就可以吃到MongoHQ的環境變數,並正確地建立資料庫連線。

順便列一下Heroku合作的資料庫中,免費方案的比較:
  • Heroku內建Database: 5MB (Postgre SQL)
  • Redis To Go: 5MB (Redis)
  • MongoHQ: 16MB (MongoDB)
  • Cloudant: 100MB, 500k requrest per month (CouchDB)
開發筆記待續…

2010年10月18日 星期一

Rails 3練習筆記

這幾天有空時,把rails 3的東西練了一下。基本上我是照著xdite的這篇練習中,rails部份的七個步驟做一遍。雖然xdite在寫這篇時用的應該還是rails 2.x,但要練的概念應該大同小異吧。我手上的參考書是AWDR2(是的,是第二版),和Beginning Rails 3(買來後發覺有點太淺了,但對於與rails脫節太久的我來說,還是不錯的參考書)。如果要玩rails 3的,要查東西似乎還是先查Rails Guide比較快 -- 因為Rails 3太新了,直接Google很常會搜到舊版本的solution。

以下是我印像較深的問題筆記,內容包括把xdite的練習跑一遍,並試著deploy到Heroku和自己的Linux Server上…

安裝ruby + rails development環境…我使用的OS是Ubuntu。一開始沒看到xdite這篇的說明,很白目的用了apt-get install ruby。結果就踩中了ruby 1.9.1 + rails 3.0.0當機的雷(rails 3現在官方只支援ruby 1.8.7及1.9.2)。我想不只是這個原因,用套件的ruby還會有很多相依性的問題吧。最後我因為想同時試rubby 1.9.2和1.8.7,所以選用了rvm,就算沒有要切版本,感覺用rvm管理ruby也滿乾淨的。

gem install rails不知為何速度超慢,而且console完全沒有回應。改用gem i --verbose rails,才能確認真的有在跑,沒有當機。
( 3)論壇的文章要能夠分頁,每一頁 20 筆,每一個論壇要秀出現在論壇裡有多少 post 數量。可用 params 配合 named_scope 在文章列表排序。
一開始我寫好了counter cache,在「看版列表」中成功的使用了counter cache減少select count query。接下來做paginate時,一開始我看別人的範例寫成這樣(省略無關的condition)

Article.paginate :per_page => 20, :page => params[:page]

但在server console中發現還是有select count的query,trace了一下will_paginate源碼,發現雖然will_paginate沒有正確讀到cached count,但可以在paginate時當參數塞進去:

Article.paginate :per_page => 20, :page => params[:page], :total_entries => @forum.articles_count,

當然,最後才發現rdoc根本有寫可以這樣下參數 Orz
( 4)每篇文章可以上傳附件
雖然可以用paperclip plugin,但這功能我選擇試著手刻看看。我在開發過程中有丟上Heroku,然後才想到「啊...Heroku沒辦法寫入靜態檔案,要的話要用S3」。
( 5)Admin 可以刪改所有文章,建立一個後台做這件事。網址是 http://example.org/admin/。只有身分是 admin 的人可以進後台。admin 的判別方是 column 裡加一個 boolean,判斷是否 admin。這個 attribute 必須用 attr_accessible / attr_protected 保護。
Admin namespace我是照這篇做的,做了看版管理及文章管理。就一般論壇而言,像「看版管理」這種東西,一般人不會有新增、修改、刪除的權限(版主可能有修改),所以scaffold架出來的東西應該的確比較適合移到admin去,而給使用者的邏輯再手動打造。但我的實作有種不夠DRY的感覺,把view和controller的code複置兩份,感覺不太好。可以想見,其實可以利用繼承、共用函式等方式,將程式碼變的更精簡。這部份有更好的best practice嗎?
( 6)用 Rake 撰寫自動化步驟,生假資料。寫一個 rake 可以達成以下步驟:「砍 db => 建 db => 跑 migration => 生種子資料」,另一個 rake 是生假文章與假論壇。
heroku不給直接跑rake db:drop。也就是說不能heroku rake db:drop,但可以heroku db:reset; heroku rake dev:fake。當然,這應該也可以寫成一個local的task。
( 7)在租來的 VPS 上面建置 Ruby on Rails production 環境,使用 Ruby Enterprise 與 mod_rails。使用 capistrano 佈署 application。
首先,我選用的VPS是linode。之前因為好奇,租過一個月的linode來玩,所以就選擇了這個較熟析的環境。灌了linode提供的Debian image後,接下來照著xdite建議的rails-nginx-passenger-ubuntu這篇做。一路上沒碰到什麼問題。

接下來,聞capistrano之名已久,但不知為何這個ruby上最有名的deploy工具,文件似乎有點少。Anyway,看著AWDR2和一些網上的教學,還是可以做的出來。我一開始照著網路上的一些文件,把deploy時的symbolic link功能做好。但bundle gems的問題我一時找不到best practice…看了這個影片,雖然覺得解法好像不夠好,但還是先照著做了。bundle path找不到的問題,除了在run中打absolute path外,也可以在deploy.rb中下

default_environment["PATH"] = "/opt/ruby/bin:/usr/bin:/bin:/usr/sbin:/sbin"

去指定path。

最後我的deploy.rb大概致上如這個gist。但我對capistrano的best practice仍有許多疑問…bundle install要自動化嗎?如果不自動化,在機器很多時要一台一台bundle install?自動化的話,要讓capistrano use_sudo嗎?不sudo的話,gems要裝在vendor/gems嗎?如果這樣做的話,vendor/gems要不要做symbolic link呢?(像我現在每cap deploy一次,就把所有系統沒有的bundle gems裝一次,其實有點慢)。

目前練習到這邊。基本上要跑完這份練習沒有難度,但很多地方會覺得處理的不夠好。Best practice可能需要看書或資料、有人指導、或經驗的累積吧…

2010年10月4日 星期一

再度考慮搬家

最近應該比較有閒寫心得了,又開始考慮搬Blog的可能性。其實一年前就murmur過這件事了。如果要搬的話,應該就是搬到自己架的Wordpress吧。主要的原因還是這些:
  • 貼code:Bloggger貼code縮排好像還是有機會跑掉。我真的不喜歡一天倒晚embed gist,然後python&haskell縮排跑掉真的很杯具。另外是Wordpress有儲存文章revision的功能,code不小心貼爛還可以回退,而Blogger就沒招了,只能從原本的code從貼一次。
  • 貼數學式:雖然有好幾個方法可以在Blogger上貼數學式(先自己用tex轉圖、用Google Graph API...),可是我都嫌不太方便。我還是較喜歡直接在文章中打類似tex的控製碼。Wordpress有一些plugin可以做到這一點。MathML是一個理想的解法,但目前只有Firefox有較完整的支援,而在Chrome慘不忍睹。
有人有什麼好建議嗎?