4/30/2007

Beast :最符合 Lighty 想法的討論區

當我一開始看到 Beast 的時候,我總覺得這是一個奇怪的東西, Beast 只是一個簡單到不能再簡單的「討論區」,而且真的沒別的了。可是當我開始真正花時間在 survey 他的時候,我發現到這真的是一個把精簡 code 的精神發揮到極限的東西,裡面的 code 相當的精彩。

我們來看看他的內容,截至目前的 trunk 為止,Controller 504行,Helper 94行,Model 238 行,Library 69行,如果不算 Unit test 一共 905行。一千行以下的討論區,到底能有什麼功能呢?

  1. 簡單易用的討論區:因為他就是討論區,所以一切的功能就是討論區,不過使用起來感覺蠻好用的。也支援一些文字格式化。
  2. REST Web API:他是目前 REST 化最徹底的 Rails 套件之一,也延續了 Rails 1.2 REST 最棒的特色,「當你完成一個內部功能的同時,你也順便寫完了 REST Web API」。
  3. OpenID Support:ㄜ,你沒看錯,他有 Open ID Support 。
  4. GetTEXT Support:所以 i18n 解決了。Btw,在我讀 Beast code 的同時,我也差不多把 po 檔寫完了,明後天就可以 release 出來。
  5. 沒有做任何縮圖上傳的機制:沒錯,你沒看錯。 Beast 是直接使用 gravatar 的縮圖,所以如果你沒有 gravatar 的帳號,就註冊一個吧。這樣的作法雖然奇怪,但是相當的合乎 MarshUP 的想法
仔細觀察 Beast 的設計哲學,可以說是「討論區該有的功能,Beast 一行都不少,除了最重要的功能之外,Beast一行 code 都不多」。不管是自己使用 Beast ,或是看 Beast 裡面的 code , Beast 都是相當好的選擇。

介紹了Beast,當然簡單講一下怎麼安裝。

下載 beast
這裡使用 svn 來安裝
svn checkout http://svn.techno-weenie.net/projects/beast/trunk
安裝 Edge Rails
在剛剛下載下來的svn目錄下,打入
rake freeze_edge
安裝 RedCloth
gem install RedCloth
修改 config
修改 dayabase.yml 的 production 設定

將 db schema 設定到 db 裡面
rake db:schema:load RAILS_ENV=production
如此,啟動您的 Rails App 即可。

4/28/2007

Rails Cache 效率上的問題

Fragement Cache 是大家常用的Rails 內建 Cache 機制,不過昨天看到石鍋拌飯先生寫的rails缓存机制的几个问题,裡面提到一些 Fragement Cache 的問題,這裡做一下筆記。




1. read_fragment 實做方式效率不佳

actionpack/lib/action_controller/caching.rb 裡面,read_fragment 假設是使用 file_store 的話,實做方式不佳
def read(name, options = nil) #:nodoc:
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
end
每次判斷 fragment 都得 open file 一次,的確會損耗效率。解決方式是用 File.exist? 來判斷即可。

2. expire_fragment 使用 regular expression 效率不佳

不算問題啦,假設 expire_fragment 使用 regular expression ,根據內建的實做方式,他會一個一個目錄下去用 regular expression ,然後 regular expression 通常執行速度不佳,所以就....

解決方式有兩個,第一個就是不要在 expire_fragment 使用 regular expression,另外就是使用 backend script 來 expire cache 即可。

3. expire_cache 的時間點

簡單講就是 controller read_fragment 的到 cache ,但是當 render 的時候卻剛剛好被 expire 掉了,就會出現 nil object error 。因為發生機率比較低,解決方式只能說視情況而定。

當然也可以照著原作者的講法,在 view 當中判斷 Model obj 是不是存在,不過這個作法就比較 dirty。

4/27/2007

acts_as_taggable Plugin 使用方式

Tag 似乎是目前所有Web2.0網站所必備的功能,也有人這樣講「沒有 Tag 就不是 Web 2.0 的網站」。雖然綜觀 Web 2.0 的定義,跟 tag 似乎一點關係都沒有 XD ,不過這完全不要緊,tag 不管是不是 Web 2.0,他對於「歸類」這檔事所帶來的影響是跟「分類」一樣重大的。

Rails 上面要實踐 tag 功能最簡單的就是使用 acts_as_taggable 來做,說到這個 acts_as_taggable 可能會令人有點 confuse,他分成 Plugin 版本,跟 Gem 版本的。兩者實做方式不太一樣, Plugin 版本跟 Gem 版本最大的不同呢,就是 Plugin 版本 acts_as_taggable 他是使用兩個 table 就可以表示許多個 Model 的 tag,你不需要使用多個 table 來表示不同 Model 的 Tag ,再這一點來看,的確是一個不錯的設計,而 GEM 版本的 acts_as_taggable 你必須每個 Model 的都新增一個 table 來表示 tag。也因為設計哲學上面的不同,也有投票來投到底那個比較好。

我們這裡先介紹 Plugin。要先注意到的,act_as_taggable plugin 是depend on Rails 1.1 以上的,所以你必須使用 Rails 1.1 以上的版本。

安裝 acts_as_taggable

script/plugin install acts_as_taggable

建立相關 table

script/generate migration add_tag_support

在 db/migrate/ 檔案裡面這樣寫

class AddTagSupport < ActiveRecord::Migration
def self.up
#Table for your Tags
create_table :tags do |t|
t.column :name, :string
end

create_table :taggings do |t|
t.column :tag_id, :integer
#id of tagged object
t.column :taggable_id, :integer
#type of object tagged
t.column :taggable_type, :string
end

# Index your tags/taggings
add_index :tags, :name
add_index :taggings, [:tag_id, :taggable_id, :taggable_type]
end

def self.down
drop_table :taggings
drop_table :tags
end
end
然後打入

rake migrate

建立相關 table。

設定 Model

設定方式沒有比這個更簡單了,就是在你要下 tag 的 Model 寫入
class ModelName < ActiveRecord::Base
acts_as_taggable
end
即可。

開始使用

下 tag

如果要對其中一個 Model 物件下 tag ,tag 內容是 'abc def ghi',代表這是一個字串,每個 tag pattern 用空白隔開。那我們就可以這樣下 tag
obj.tag_with( 'abc def ghi')
取出這個 Model 所有的 tag

如果,要取出這個 Model 物件下面的 tag,那就
obj.tags
這是一個 tag 的 array ,裡面每個東西都是一個 tag 物件,如果想要取出這個 tag array 的每個 tag name,就這樣用
obj.tags.each { |tag| puts tag.name }
即可。

尋找同一個 tag 的所有 entry

如果要搜尋這個 Model 底下所有下這個 tag 的 entry,就這樣使用
obj.find_tagged_with('abc')
他會回傳同一個 Model array,裡面是有下這個 tag 的 Model 物件。

覆蓋掉 tag

如果想用新的 tag ,就直接用 tag_with 覆蓋掉即可
obj.tag_with('new tag')
即可

刪除其中一個 tag

只想刪除掉其中一個 tag ,那就用 ActiveRecord 裡面的 destroy 即可,下面例子就是刪除 tag id 等於 1的 tag ,但是不刪除其他 tag
obj.tags.each do |tag|
tag.destroy if tag.id == 1
end

這樣即可。

4/26/2007

JRuby 0.9.9 Release

好掉詭的版本編號,好像在倒數計時一樣。JRuby 0.9.9 release,主要的更新是在修正了很多 bugs ,並且又增加 40% 的 performance。對於 Rails 支援能力也變強了, Mephisto 可以在 JRuby 上面跑了

The JRuby community is pleased to announce the release of JRuby 0.9.9.

Homepage: http://www.jruby.org/
Download: http://dist.codehaus.org/jruby/

This release has largely been a stabilization release where we have spent
more focus on Ruby compatibility. We are gearing up for a 1.0 release. Here
are some of the more significant acheivements for 0.9.9:

Major compatibility and performance overhaul of String, Array, Hash
Many YAML and Marshalling issues have been fixed
Java Integration overhaul fixing many outstanding issues
180 Jira issues resolved
Several more bottlenecks removed
Rails applications like Mephisto and plugins like Goldberg are running without hitches
Performance has improved by 40% over 0.9.8 based on YARV benchmarks
With all the hard work done by Marcin Mielżyński and Bill Dortch we are adding
them as core committers. Their contributions have made a huge difference in
our progress as of late.
We also want to thank all people who hang out on IRC, triage/report/patch
issues, and communicate on our mailing lists. Their interest has really
helped shape JRuby into a better implementation.

Please take JRuby 0.9.9 for a spin and help us root out the last issues
before our 1.0 release!

4/24/2007

Net::HTTP 使用方式

在 UbiSunrsie 裡面介紹過的小小 Script 裡面有用到,就直接用範例來解釋一下使用方式。




GET
require 'net/http'
Net::HTTP.version_1_2
Net::HTTP.start('www.google.com.tw', 80) {|http|
response = http.get('/index.html')
puts response.body
}
基本上,Start一個 Net::HTTP Connection HOST,然後使用 http.get來抓取你要的頁面,最後用 response.body 印出來即可。

POST
require 'net/http'
Net::HTTP.version_1_2
url ='example.com'
action = '/cgi-bin/query'
Net::HTTP.start( url , 80 ) {|http|
response = http.post( action , 'var1=value1&var2=value2' )
response.body.each do |line|
puts line
end
}
稍微複雜一點的作法,除了 http.get 變成 http.post 之外,後面的 variable = value 的形式是採用 application/x-www-form-urlencoded 的形式傳遞出去的,大家應該都很熟悉。

如果不喜歡這樣,還有其他作法

require 'net/http'
require 'uri'
res = Net::HTTP.post_form(URI.parse('http://example.com'), {'var'=> 'value' , 'var2' => 'value2' })
res.body.each do |line|
puts line
end
end
好處是 value 傳遞是用 hash 方式來傳遞,比較 Ruby 化,壞處是比起 Net::HTTP.post_form ,我比較喜歡 http.post 這樣的形式。

4/22/2007

Ruby on Rails in UbiSurise 圓滿結束

感謝各位的支持,這次的 talk 在沒有人踢館,講的笑話都大家都捧場的情況下結束。兩個禮拜連續講兩場實在太累了,Blog更新也有點怠慢,先說聲抱歉。關於這次的 Demo 程式,我會整理一下看看要用啥方式 Release,而投影片在下面。







4/18/2007

REST 簡介

前言

寫這篇是因為我在 滿紙荒唐言,一把心酸淚 裡面有講到
我連REST 的好處都不甚了解,就大張旗鼓的吹 REST 的好?
所以我開始努力 survey REST 的東西,終於在 OSDC 主講 T
he furture of RoR - The new features in RoR 2.0。原本以為自己已經徹底瞭解 REST,但是隨著時間的逼近,我越來越發現「我不知道 REST 真正的好處」。最後,當我在 OSDC 講完之後,大家就七嘴八舌給我一些意見,越討論越發現我真是個笨蛋
在 OSDC 這種場合,我才是學生,場下聽的強者們才是我的老師
現在的我,有了大家的加持後,反而越來越有自信了點,我終於允許我在 Blog 寫有關於 REST 的文章。

REST is about Resource

在講 REST 之前,我們來看看Resource 。當你了解 Resource,那麼你就了解了 REST。我們來看看 Wikipedia 這張圖,這個三角形是在講解 Resource 最重要的三個東西,Nouns,Verb,Type。

Nouns

在網路上,每個URL帶給我們的意義其實就是代表各式各樣的資源(Resource),有些可能代表你的個人日誌,有些代表我出去玩的圖片,有些代表你最喜歡的歌手的音樂,有些代表你朋友搞笑的影片。Resource 裡面,Nouns 其實是一個 URL,他代表網路上面 Resource 的唯一的位址,同一個 resource 理論上來說,應該只有一個 URL 代表這個 Resource。用超過一個 URL 來代表同一個 Resource 是不夠漂亮的方式。

Type

而 Type 是什麼呢?我們剛剛講 URL 代表Resource 的位址。但是 Resource 其實跟大家想的有點不太一樣,resource 不是只代表一個檔案,他是代表一個概念。舉個例子,這張圖片
是 一張PNG圖片,裡面是我的 Email 。他的位置是在 http://www.thegiive.net-a.googlepages.com/image.png,但是如果我今天將這個 PNG 轉檔成為 JPG,他的位置就變成了 http://www.thegiive.net-a.googlepages.com/image.jpg。雖然兩者的 URL 不相同,但是對我們來說,PNG 跟 JPG 這兩張圖其實代表的意義是相同的,裡面的圖案就是我的 Email。

所以,儘管 Format 不同,他們依舊是同一個 Resource,而這些 Format 就是 Type

好, 這些東西大家應該都可以了解吧。我們問些奇怪的問題,假設今天有一個 XML file http://www.thegiive.net- a.googlepages.com/image.xml 這個XML檔案裡面記載著我的 Email 帳號
<xml>
<email>thegiive at gmail dot com</email>
</xml>
那麼這個 XML File 跟上面的圖,算是同一個 Resource 嗎?是的,雖然 XML 跟 JPG ,PNG 是完全不同的東西,但是他們都記載同樣的意義,那個意義就是我的 Email 帳號,也就是他們代表同一個 resource。

如果你能夠接受不同格式的檔案其實代表同一個 Resource 的想法,那麼今天這個網址
http://abc.com/blog/1
如 果以 HTML Format 來說,他可能是一個可以顯示在 Browser 上面的 HTML ,裡面記載這個 Blog 的內容。但是如果是 XML Format,他可能是一個記載這個 Blog 作者是誰的XML 。如果是以 RSS Format 來看,他可能是這個 Blog 的最新文章。但是儘管 Format 不同,內容不同,但是他們還是代表同一個 resource ,也就是這個 Blog。

在 Web 上面,Resource 是一個概念,而不僅僅是一個檔案。一個 resource 可能有各種不同的 Content Type,但是依舊代表同一個 resource。

Verbs

至於三角形的最後一角Verbs ,就是操作 Resource 的方式。我們該如何操作網路上各式各樣的資源呢?

Web 用宏觀來說,其實就是一個超級大的資料庫,裡面有各式各樣不同的 resource,每個 URL 都是指向各種不同的 Resource,我們每天上網,其實都是藉由 HTTP 來 request 各式各樣不同的 Resource。

既然 Web 根本就是一個網路上面 Resource 的資料庫,每個 Resource 都是跟資料庫裡面的 Data Entry 類似。那麼有用過資料庫的人都知道,我們每天對資料庫的每個 Data Entry 做四個動作,也就是經典的 CRUD(Create,Read,Update,Delete)。盡管 SQL 指令再多,其實我們對資料庫的操作,都超不出 CRUD 這四個動作。

那們,我們對 Web 上面的 resource,有沒有相對應的 CRUD 呢?

RFC 2616 裡面講到, HTTP 1.1 一共有八個 method(我在演講中提到只有四個 Method,是錯誤的,謝謝聽眾指正。),裡面有兩個 Method 是大家不可能不懂的
  • GET
  • POST
雖然 HTTP 定義八個 Method之多,但是大家在 Coding Web 時只使用這兩個 Method。
原因很簡單,因為最多人使用的 HTTP Client,也就是 Browser,只支援 GET 和 POST。所以,我們之前的 Coding,都是只用 GET 和 POST 來對 Web Resource 來做 CRUD。
CREATE:POST http://abc.com/users/create/
READ:GET http://abc.com/user/show/1
UPDATE:POST http://abc.com/user/update/1
DELETE:GET http://abc.com/user/delete/1
我們必須用部份的 URL 來表示我們對這個 Resource 的操作,這樣做也沒什麼不好,但是要知道 URL 其實是要來指定 Resource 位置的,URL 不是拿來表示 Verbs 的。而且這樣做等於我們使用四個 URL 來表示同一個 Resource,這跟一個 Resource 理論上來說只能有一個 URL 牴觸。

RFC 定義的 HTTP 1.1 裡面,其實有四個 Method 是直接 Mapping 到 CRUD 的,那就是 GET,POST,PUT,DELETE。既然已經 HTTP 1.1 已經有對應好的 Method,那為什麼我們要捨近求遠,硬是要使用 GET POST 來設計出 CRUD,為什麼不用原本就規定好的 Mapping 來做 CRUD 呢?

沒錯,這就是 REST。讓大家不需要再捨近求遠,不需要用非 Web Standard 的方式來做 Web Development。

REST 的詳細定義就請看 Wikipedia上面的介紹, 本人就不詳細敘述了。在定義上來說,REST 並不等於 HTTP,REST 是一種 Architectural Style,而非一種 Standard,所以 REST 並沒有 toolkit ,也沒有 W3C Spec。相對起來,HTTP 才是一種 Standard ,有相對應的 toolkit,W3C Spec。HTTP 是一種 REST 的實做,也是最成功的實做。但是我們以實用為主,並不是那麼在乎太過細部的差別,所以以後我不會太強調 REST 跟 HTTP 兩者意義上的區分。

搞清楚你的 Rails Version

目前使用 Rails 的網站,Version 分成好幾派。
  • 0.X
  • 1.0
  • 1.1
  • 1.2


Rails 0.X 的就是幾年前就開始在使用 Rails 的始祖,據我所知國內也是有網站還再用 0.X ,他們之所以死撐不換的原因只有一個,就是怕升級上去原本的 code 根本不相容。1.0 的時候我沒趕上,那可能要問一些長輩才知道 1.0 的模樣是怎麼樣子。

我進入 Rails 圈子剛好是 1.1 的剛剛出的時代,那時候 RJS 剛剛出來,大家叫好又叫座。我也因此全新投入了 Rails,1.1 的穩定度很高,速度也不賴。 Rails .12 是今年才出的,REST 加上 has_many :through 實在非常的吸引人,不過有 benchmark 表示 Rails 1.2 在速度上輸給 Rails 1.1,不過也沒輸太多。所以綜觀起來,要使用 Rails 1.1 或是 1.2 是要看你的需求而定的,沒有一定要使用那個版本的建議。


我現在手邊的 Project 都是用 1.1 ,而且絕大多數已經上線在跑的,短期間不太可能轉換到 1.2 。但是我一定會花很多時間在了解 1.2 的情況。也就是說,我希望能夠在我的機器上面裝 1.1 跟 1.2,有沒有辦法達成呢?

當然有,這很基本!!!
首先,你的 gem 已經安裝了你所要求的 Rails Version,像是我希望在我的機器上面可以自由使用 1.1 或是 1.2 的 Rails,所以我的 gem 安裝情況就是
rails (1.2.3, 1.1.6)
Web-application framework with template engine, control-flow layer,
and ORM.

問題來了,要怎麼一次安裝兩個以上的 version 呢?假設你的 Rails 已經安裝了 1.1.6 ,可是又要安裝 1.2.3 ,就這樣打吧。
sudo gem install -v=1.2.3 rails
當然,如果你想安裝的是最新的 Rails Release,那麼這樣也是可以的
sudo gem i rails
如此就可以在同一台機器上面安裝不同 version 的 rails 。

確定了你的 gem 已經安裝了多個 version,我們必須了較 freeze code 的概念,很多時候我們的某個 Project 的 code 是在某個版本的 rails 開發的。當rails 出了新的 release,原本run 好好的 code 就可能出現一堆 error,所以我們必須要在這個 Project 也包入 Rails 這個 version 的 code。使用方式如下
rake rails:freeze:edge TAG=rel_1-1-6
rake rails:freeze:edge TAG=rel_1-2-3
顧名思義,你可以再 TAG 下面指定你要的 Rails Version,這個指令會把 Rails 放入 vender/rails/ 底下,以後伺服器執行前,他都會去這個資料夾尋找,如果有 vender/rails/ 的資料夾,他就不會使用系統預設的 Rails version ,而是使用已經包在這個 Rails 資料夾的 version。

當然,如果我們將已經包好的 Rails Version 解除,重新使用系統預設的 Rails Version,就這樣打即可
rake rails:unfreeze
他做的事情其實就只是 rm -fr vender/rails/ 資料夾而已。

最後一點,如果你不確定你的 Rails Package 到底使用那個 Version 的 Rails ,你可以打入
ruby script/about

他會跟你講的一清二楚的。

4/16/2007

[Ruby] recursive lambda

[Ruby] recursive lambda

==本文連同引文同步載於 ptt Ruby 板、LightyRoR飽和脂肪星星之一角備份區)==

很抱歉最近狀況真的是相當糟糕,導致很多事情都沒做或是沒做好。雖然以後大概也不會比較好。這樣講講就沒關係嗎?當然不是,只是替自己找一點比較能安心的藉口吧。另外本文有任何錯誤歡迎指出。

==本文開始==

我一直覺得 Ruby 缺少一個類似 self 的東西,用來表達現在這個 function/method. 這個東西有什麼用呢?其實我也不知道有什麼用,就只是單純覺得好像少了這種東西。最直覺的例子,恐怕就是具有遞迴能力的 lambda function. 我曾在 ptt Ruby 板發過一篇文,講 quine(self-reproducing programs),後來我用了 Ruby2Ruby, 寫了像這樣的結果:(飽和脂肪星有該文的備份(通常連不上))

#!/usr/bin/ruby

require 'rubygems'
require 'ruby2ruby'

(a = proc {
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts
print("(a = ")
print(a.to_ruby)
print(").call")
}).call


最蠢的地方是明明都用 lambda(proc) 了,我卻還得把 lambda 的結果記起來留待以後使用。這樣實在是有點無趣。我希望我可以寫:

lambda{ print(this.to_ruby); print(".call") }.call

這樣不是帥氣多了嗎?於是我開始試著思索實作這東西的可能。接著我忽然想到,所謂 this 不正是指在 call stack 最上端的 function/method 嗎?因為當我們執行到這個 function/method 時,this 一定是指同一 function, 不可能忽然去指涉其他 function, 而另外一個 function 進 call stack 時,不把他解掉也不可能會執行到 this. 於是可以把 this 寫成一個 function, 不吃任何引數,回傳一個 Proc/Method 代表正在 call stack 頂端的那個 function.

而我記得 Ruby 是有方法可以去存取 call stack... 雖然好像是用很蠢的方法,也確實是有點蠢,但總之可以用模擬的。隨意 google 了一下,找到一個很簡單的方式,就是用 set_trace_func, 丟一個 callback 進去,於是 Ruby 在各個 function 間做動作的時候,都會呼叫這個 callback. 感覺就是效率會變狂差,不過呢,至少暫時是可以用的。

接著可以利用 Thread.current[:symbol] 來儲存 current thread call stack info, 任何 function call 時,push 資料進這個假的 call stack, function return 時,pop 資料出來。這樣就有一個很簡單的 call stack info 可以用了。

以下程式歡迎任意使用,licensed under Apache License 2.0, 複製到檔案用的話希望可以把以下這段 copy 到檔案最前面… XD

#    Copyright (c) 2007, Lin Jen-Shin(a.k.a. godfat 真常)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Kernel
# 由於 google 到的參考程式把抓 call stack 叫 invoker,
# 所以這裡沿用他的名字。有個常數比較容易看懂程式在做什麼。
INVOKER_EVENT = 0
INVOKER_FILE = 1
INVOKER_LINE = 2
INVOKER_MSG = 3
INVOKER_BINDING = 4
INVOKER_CLASS = 5

# -1 就是 top 的意思囉
def invoker levels = -1
st = Thread.current[:callstack]
# st 有可能是 nil, 如果 invoker 先被 call 到的話。
# 雖然我不知道什麼時候會發生這種事…。levels - 2 的原因是:
# 0(stack bottom) => function that you called(this is what we want)
# 1 => Kernel#invoker
# 2 => Array #[]
# 所以去掉額外不要的額外兩個資訊。
st && st[levels - 2]
end

def this
# 因為多 call 了 this, 所以要再多去掉一個額外資訊。
info = invoker(-2)
# 這邊我本來寫成 lambda 的形式,可以正確執行,但有一個狀況
# 卻是失敗的。就是 lambda{yield}.call{} 這樣是會 error 的 :(
# 試了多次還是找不到 Proc.call 吃 block({}) 的方法,只好改寫
# 成用 Method 的形式。不知為何,Method 就可以正確使用 block...
eval("self", info[INVOKER_BINDING]).method(info[INVOKER_MSG])
end
end

set_trace_func lambda{ |*args|
case args[INVOKER_EVENT]
# 可能的有 call 和 c-call, 都是 function
when /call$/
# 這邊我搞不清楚到底是誰會先被 call, 是 call 還是 return?
# google 來的是把初始化寫在 call 裡,可是我測試都顯示是在
# return 上,所以反而是 return 的地方需要初始化。或是乾脆
# 全部拉出來在最上面初始化也可以 :)
(Thread.current[:callstack] ||= []).push args
# 同上可能有 return 和 c-return
when /return$/
(Thread.current[:callstack] ||= []).pop
end
}


以上不管有沒有問題,都可以來看一下我額外寫的幾個 unit test, 參考一下幾個我目前想到的用法。

require 'test/unit'

class TestThis < Test::Unit::TestCase
def test_fact
assert_equal(120, fact(5))
assert_equal(3628800, fact(10))
# 試用 recursive lambda
assert_equal(5040, lambda{|n| return n*this[n-1] if n>0; 1}[7])
end
def fact n
# 恐怕是最常見的用法
return n*this[n-1] if n > 0
1
end
##
def test_pass_around
# 這邊流程可能很怪,因為只是我隨便寫的,單純測試正確性罷了。
assert_equal(method(:pass_around_forward), pass_around.call(lambda{|v| v}))
end
def pass_around mode = 'pass'
case mode
when 'pass'
pass_around_forward this
else
'value'
end
end
def pass_around_forward func
assert_equal('value', func['value'])
this
end
##
def test_with_block
# 同上,流程亂寫的,單純測試正確性。
with_block{|b| assert_equal('value', b['value'])}
end
def with_block mode = 'pass', &block
case mode
when 'pass'
block[this]
else
'value'
end
end
##
def test_more_args
# Proc 就是死在這個測試,block 展開怎麼做都失敗 :(
# 改成 Method 後這邊就可以通過測試了。
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5, &lambda{6})
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5){6}
end
def more_args mode, a1=nil, a2=nil, a3=nil, *as, &block
case mode
when 'get_this'
this
else
assert_equal(1, a1)
assert_equal(2, a2)
assert_equal(3, a3)
assert_equal(4, as[0])
assert_equal(5, as[1])
assert_equal(nil, as[2])
assert_equal(6, yield)
assert_equal(6, block.call)
end
end
end

最後,我可以把 quine 改寫成:

#!/usr/bin/ruby

require 'rubygems'
require 'ruby2ruby'
require 'invoker'

def f
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts("require 'invoker'")
puts
print(this.to_ruby)
print("\nf")
end
f


為什麼要用 def f; end 呢??這樣不就失去 this 的意義了??因為不曉得為什麼,Ruby2Ruby 跑 lambda + this 都會有奇怪的 runtime error, 而這個錯誤是來自 class RubyToRuby 的 rewrite_defn(exp), 最後的:「raise "Unknown :defn format: #{name.inspect} #{args.inspect} #{body.inspect}"」這我一時不知道要怎麼解決,不知道會是誰的錯,所以只好暫時用 def f; end 這種蠢方式了。

另外,還有一些無聊的花枝可以玩:

def just_print_it n
print n
this
end

然後就可以:

just_print_it(1)[2][3][4][5]

輸出:12345

def add_attr attr
($attr ||= []).push attr
this
end

add_attr('hello')['world']['and then?']['good-bye']

或是很簡單的流程控制:

def fight step = :ready
case step
when :ready
ready this
when :go
go this
when :finish
finish this
end
end

def ready callback
play_ready_animate
if blah
callback[:go]
else
callback[:finish]
end
cleanup_ready
end

def go callback
gogogo
if blah
callback[:finish]
else
callback[:ready]
end
cleanup_go
end


類推,我也不知道有什麼用,搞不好有什麼 cleanup 是要等所有的事都做完才能做?總而言之,就是這樣啦,沒什麼營養,但我卻覺得很重要的功能。在很多時候可以少打很多字。搞不好哪一天也會想到什麼真的很有價值的應用也說不定。總覺得真的有太多事是無心插柳柳橙汁。雖然大部份都來不及喝就乾掉了。

2007.04.16 godfat 真常

4/14/2007

Now in OSDC

我現在正在 OSDC 2007 in Taiwan,正在聽唐鳳講 JiFty。等等下午要講 REST on Rails,投影片在這裡

4/05/2007

Persistent Login on Rails

Rails 社群一向是使用最方便的 Session 來做 login,而且 Rails 的 Session 也實做的非常方便。不過有些時候 Session 也有不方便的地方,像是關掉 Borwser 就要重新 Login,如果我們希望能夠做到永久 Login ,那該怎麼搞呢?

我採用這個網頁的方式,他的好處是只在 cookie 存入一個隨機的 id ,並不會存任何資訊在 cookie 裡面。



1. 安裝 UUID 的套件
因為要隨機產生 id ,總是要一個亂數產生的套件,這裡我們就用原作者的選擇 UUID
gem i uuidtools

2. DB 裡面新增一個 Persistent Login 的 table
我們新增一個 table ,把他叫做persistent_logins,結構大概是 id , uid , user_id 當作 interger ,create_on 當作 datetime。user_id 代表相對應的 user table id ,uid 就是放到 cookie 裡面的隨機 id 。

3. 建立一個 PersistentLogin 的 Model
裡面的結構很簡單,之前說的 UUID 就是用在這裡,其實可以在這裡換另外一個亂數產生器。
class PersistentLogin < ActiveRecord::Base

belongs_to :user
before_create :assign_uid

private

def assign_uid
self.uid = UUID.random_create.to_s
end
end
3. 在使用者已經 login 成功的的地方放入下面的 code
 p = PersistentLogin.create(:user=>u)
cookies[:p_session_id] = {:value => p.uid, :expires => Time.now + 7.days}
在裡面,u 就是這個使用者的 user model ,他會在 cookie 裡面放 p_session_id 這個值,expire time 是 7天。

4. 在判斷是否 login 的地方加入這些 code

def get_current_user
if session[:user].nil?
if cookies[:p_session_id] && p = PersistentLogin.find_by_uid(cookies[:p_session_id])
session[:user] = c_user.id
end
end
end

5. logout 的地方加入這樣的code

def logout
session[:user] = nil

if cookies[:p_session_id] && p = PersistentLogin.find_by_uid(cookies[:p_session_id])
p.destroy
end

cookies.delete :p_session_id
end
這裡的code 相當的 rough ,但是卻很容易實做出來。

4/04/2007

Struct:簡單的宣告物件方式

在 PHP 裡面,很多時候我們會用到 Hash 來傳遞變數,像是 $something['abc']['def'] 之類的方式,或許這是一個在效率上面很好的實做方式,但是總是...怪怪的,不是那麼的優雅。在 Rails 裡面,難免會有 controller 傳遞變數到 view 裡面的時候,這個時候通常是用 flash 這個內定的 Hash 來傳遞,但是也會遇到相同的問題,就是 Hash 實在是不夠物件導向。

flash[:somthing][:abc][:def]

這樣的寫法到底是好還是不好呢?很多時候我很想說服自己這樣就好了,code work 即可,但是心中把這種事情物件導向化的想法一直沒變。所以我就開始 survey ,該怎麼作會比較 OO 呢?

我將訊息傳遞的 Object 宣告成一個 Model 上面好不好?code 很好看,可是心理總覺得小題大作了點,明明一個 Hash 就可以解決的事情,需要特別使用一個 Model 物件來寫嗎?有沒有辦法可以超快速的把訊息傳遞包成一個簡單的物件,而且裡面不需要 method ,只需要幾個 message 的 attribute 即可?

我看到了 Struct ,他的作法很簡單

宣告一個 class definition

class_def = Struct.new( :name, :address, :zip )


就是宣告一個 class 的定義叫做 class_def,裡面一共有 name ,address,zip 幾個變數。

New Object 

當我們需要 new 一個 object ,我們把他叫做 obj

obj = class_def.new

如此即可。以後就可以這樣使用

obj.name
obj.address
obj.zip

Constructor

一開始,所有的變數都是 nil,這是因為沒有 constructor去宣告初始值。在 Struct 裡面有辦法達成嗎?當然有,在 new 這個 object 時這樣作

obj = class_def.new( 'a' , 1 , '123')

即可。

我的使用方式

我常常這樣使用Struct 來做 message passing 的 object,一行即可做到 new 一個我自己定義的 message object

msg = Struct.new( :error , :alert , :warning).new

如此的寫法對我來說,比 flash[:error] 來的更為優雅。

這個東西與其說是技巧,不如說是一個物件導向的偏執分子的固執行為,沒什麼了不起。你依舊可以用 Hash 達成每一件 Struct 做的到的事情。

4/02/2007

Rails 1.1 跟 1.2 的 Performance 比較

之前有一個報導顯示, Rails 1.2 遠遠的比 Rails 1.1 來的慢,慢到快兩倍左右。
Results of RoR 1.2.1 shocked “a little”: falling of productivity in 2-4 times in comparison with 1.1.6 version
Stefan Kaes 做了一個 Performance Benchmark,打破了這個有點像謠言的 Benchmark。這裡的結果還是 1.2 還是比 1.1 來的慢,不過相差不遠。

裡面發現到幾點
  1. Rails 1.2 的 action cache 跟 1.1 比起來,還是相當程度的需要 performance optimize
  2. pstore 的 session container 效率實在不佳
  3. 當 Rails code base 越來越大的時候,效率似乎還是難免會下降(原因在於 GC 跟 Intpreter 的問題),一個良好的 vm 還是解決的最佳解