3/30/2007

Aptana 跟 RadRails 整合時間表

自從 RadRails 跟 Aptana 整合之後,新的主控者 Aptana 出了一點小錯誤,DNS 指定好像出了點錯誤,一直連不上 RadRails 官方網站,要上 RadRails 請先用 http://www.aptana.com/radrails 這個 Link。言歸正傳,Aptana已經提出了整合的時間表,他們希望四月就出 Boundle Download,五月就會做出 RHTML 整合到 Aptana ,真是令人興奮。



April

Bundled download. Users will be able to easily download a combined Aptana + RadRails, or the seperate plugins.


May

RHTML Integration. The Aptana HTML editor will support adding inline Ruby script with code assist. Other bug fixes and updates.


3/28/2007

Fragment Cache Part 2

上次介紹了如何使用最基本的 Fragment Cache,現在來加上一些小小的小 trick。上次介紹在 View 裡面要這樣使用Fragment Cache


<% cache do %>
我們要 cache 的 content
<% end %>
Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action.cache 這個檔案裡面。假設上面的 action 是在 lala controller 的 haha action ,那們上面的寫法跟 下面的寫法效果是一樣的
<% cache(:controller => 'lala' , :action => 'haha' ) do %>
我們要 cache 的 content
<% end %>
簡單講,就是最上面的 cache do 寫法算是預設值,寫不寫好像沒差,當這個頁面只有這裡需要 cache 時,這樣寫就很方便。不過如果這個 haha action 會依照後面接的 id 的不同顯示不同的內容時,你可以很直覺的加上 :id 這個選項
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] ) do %>
我們要 cache 的 content
<% end %>
Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.cache 這個檔案裡面。簡單講,又多了一層以 action 為名的目錄,目錄裡面每個 id 都有自己的 cache file。這樣的作法可以簡單依照變數的不同區分 cache file 。

不過?

但是老實說,到目前為止,這個 fragment cache 用途還是不大。我們一個 Action 只能使用一個 Partial Cache,那如果同一個頁面有兩個以上的地方要 cache 呢?Partial Cache 的概念就是一個網站有很多個 block 可以被許多頁面共用。假設某個頁面有兩個部份要 cache ,一個是 RSS ,另外一個是 Info 這個地方。那我們依照 Rails 聖經本的方法,使用 part 變數供我們使用
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' ) do %>
我們要 cache 的 rss content
<% end %>
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' ) do %>
我們要 cache 的 info content
<% end %>
如此,相關的 Cache 會產生在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss.cache 還有 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=info.cache 這兩個 file 裡面。仔細看就知道,他的命名機制就是 id. part=part_name.cache 這樣的寫法。很簡單吧。

到現在 Partial Cache 才從還好變成好用的階段。read_fragment 或是 exprie_fragment 都是使用
:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' 
這樣的命名機制來操作,不難吧。

還有嗎?

到現在為止,我還沒看過 :part 還不夠的情況,不過如果真的覺得用 controller , action , id , part 都不夠區分你的 cache ,還有最後一招
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' , :part2 => 'rss'
, :part3 => 'rss' .... ) do %>
我們要 cache 的 rss content
<% end %>
簡單講,其實 :part 只是聖經本這樣寫,所以在 Rails Fragment Cache 機制裡,id 以後的 fragment 命名方式不限制,而且數量不限制,只要你不嫌打字太累,你大可以用幾百層去命名你的 cache 。出來的 cache file 命名規則是這樣 ailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss&part2=rss&part3=rss.cache。總之隨便你怎麼命名 :p

3/27/2007

class 與 instance

不知道為何,最近不管是私事或公事似乎都很多。造成的結果就是很多事不知道該從何處著手比較好。也許短時間內沒辦法寫什麼新東西也說不定,所以我又拿舊文章出來改了。這次講的是 Ruby 的物件系統。不過呢,由於這篇原本是回答別人的問題,因此有些地方講得不是很齊全,有些也講得比較偏。找到時間後,會再重新整理一次,現在就先這樣吧

編輯筆記:後面一段無關的刪去了,並補充了一小段。

==

在看 Ruby 的 class 與 instance 之前,先來看所謂 prototype-based language 是怎麼樣的東西,當然是舉大家最耳熟能詳的例子,ECMAScript(即 Javascript)

所謂 prototype-based 的意思是,沒有 class 的概念,所有的一切都是 instance, 產生東西一律使用 clone 的手法,從 prototype clone 出來。

在 ECMAScript 裡,要這樣操作 prototype:

=begin 例子

// 產生一個 function object, 會輸出「我是 ooo」
function say(){ print('I am ' + this) }

// 產生一個 function object, 拿這當 Duck 的 prototype
function Duck(){}

// 讓 Duck prototype 產生一個成員,也就是讓 say 變成他的 method
Duck.prototype.say = say;

// 定義 toString 讓 say 使用
Duck.prototype.toString = function(){ return 'Duck' }

// 假設現在有一個讓某東西說話的 function
function say_hello(who){ who.say() }

// 於是我們可以這樣呼叫 say_hello
say_hello(new Duck)

=end 例子

new Duck 會去尋找 Duck 的 prototype, 然後 clone 一份該 prototype 後傳回。所以 say_hello 的 who 會是一份 Duck 的複製,執行 say 則會輸出:

I am Duck



ok, 回到 Ruby. 雖然說 Ruby 被分類成 class-based, 但事實上,everything(ok, almost) is an object in Ruby, 就算是 class, 他其實本質上也是某個 instance, 是 Class 的 instance.

class A; end
a = A.new

a 是 A 的 instance, 所以 a 的 class 是 A.

a.class # A

A 是 Class 的 instance, 所以 A 的 class 是 Class

A.class # Class

其實,我覺得可以把這個 Class 視為某種 meta-class, 即 class 的 class, 如果我們要把 A 當嚴格 class 的話。但如果我們依然把 class 當 instance 看的話,當然,Class 本身其實也是一個 instance, 他是他自己的 instance.

Class.class # Class

有趣的是,這樣寫的話:

Class.object_id == Class.class.object_id

答案是:true.
Class.class 傳回來的,其實就是 Class, 也就是,他是他自己的 instance. 換句話說,其實 Class 是所有的 class 的 class.

A.kind_of? Class # true
Class.kind_of? Class # true
A.class # Class
Class.class # Class
A.class.object_id == Class.object_id # true



(btw, 其實更妙的是:

Module.kind_of? Class # true # Module 是 Class 的 instance
Class.kind_of? Module # true # Module 是 Class 的 superclass
Module.new.kind_of? Class # false # Module 的 instance 不是 Class



再加上 Object 會更複雜,可以試著畫畫看物件結構)

但是回想一下,一般我們是怎麼定義 class 的?

class A; end

其實,我個人會說這是一種 syntax sugar, 因為更合於 Ruby object system 的定義方式,應該是這樣:

A = Class.new
A.send(:define_method,
:say_hello,
lambda{ puts "Hello from A's instance." })

a = A.new
a.say_hello # Hello from A's instance.



由於 define_method 是 private 的,所以要用 send 去呼叫。其第一參數是你所要回應的 message symbol, 第二參數是 Proc/Method/Block 都可,將會成為該 method 的 body.

也就是說,其實你寫

class A
def say_hello
puts "Hello from A's instance."
end
end



對於 A 來說,他是先從 Class 產生一個實體(instance),然後將 A 這個「常數」指向那個實體,再對 A 呼叫 define_method, 把 say_hello 變成 symbol, 將 Block 變成該 method 的 body.

哪一個比較容易寫?當然是後者,畢竟那是大家都很習慣的模式,簡潔易懂。所以我會說那種寫法其實在某種程度上來說,是 syntax sugar... 而 Ruby 其實也是用 prototype 建出其 class 體系,這樣應該算是 prototype-based 還是 class-based, 看倌認為哩?

2007.02.08

補充:

有在寫 Ruby 的人,應該都知道 class 的名稱一定要大寫開頭。事實上,這只是一種延伸規則,並不是基本的規則。依照:

a = Class.new

仍然是可以得到一個不是大寫開頭的 class. 大寫開頭的 identifier 在 Ruby 是一個更簡單的概念:「常數」(constant)。也就是說,其實每一個 class name 都是一個常數,一個 constant variable refer to a Class's instance, 一個指向 Class 實體的常數,就跟一般的變數沒兩樣。所以我們可以寫:

def make klass
klass.new
end

這個 functiona 接受一個參數,這個參數的 type 的必要條件是實作 new method, 而任何一個 Class instance 都符合這個條件。

make(Array).push(123)

把 Array 這個 instance 丟給 make 這個 function, 回傳就是一個 Array instance.(global function 也值得探討,不過還是留待下次吧 :p)

那麼為什麼要讓 Class instance 成為一個常數?其實這是很顯而易見的,如果你把原本定義好的 class 整個變成其他人了,例如:

Array = String

這樣意義在哪裡?以後寫 Array.new 產生的就不是原本的 array, 而是 string 了。所以讓 class 成為一個 constant, 並要求其開頭字母是大寫,並非單純只是為了命名習慣的問題,背後是還有牽扯到其他因素的。

我覺得 Ruby 有趣且厲害並迷人的地方就在於,他不單單只是有強大的威力在內,還額外提供了許許多多方便使用的 syntax sugar, 使任何人都能快速上手,也能快速做到任何他想做到的事情。而當你需要更強大的表達能力時,也能立刻拋棄 syntax sugar, 從最原始的 Ruby 道理開始寫起程式。也就是說,Ruby 提供了各種不同的寫程式角度,大家都可以去找他喜愛的角度去使用。個人認為,Lisp 會不及 Ruby 方便,最主要的差別就在這裡了。(當然,我並不熟 Lisp, 所以這句話恐怕是大有問題在…。)

2007.03.27 godfat 真常

延伸閱讀

Fragment Cache Part 1

之前介紹過 Page Cache,那是一個將所有動態內容都變成靜態頁面,藉由不經過 CGI ,來達到最大覆載度的技巧。但是他的應用性不夠廣泛,僅限於下面的用途


  1. 流量超級大的頁面
    通常是 index page,一個網站可能有幾百個頁面,但是通常 index page 一個頁面就佔了三到五成的流量,而且 index page 通常每個網站都是一個小時,了不起 15 分鐘更新一次,這個時候用 index page 可以大幅度增加網站覆載度。
  2. 頁面修改的頻率遠遠小於讀取的頻率
    例如 Blog 系統,我可能一天寫一篇文章,但是一天讀取我的 Blog 的人可能有幾千個,這個時候為什麼每次讀取頁面的時候都得去 DB 撈資料,組合,Render 出來呢?還不如用靜態頁面儲存,然後每次修改頁面時都重新產生新的 HTML 即可
但是,如果要利用 Page Cache 做到某些部份的功能實在有點麻煩。像是在同一個頁面裡面,有些地方是很少機會修改,希望能夠 Cache 起來,但是有些地方修改頻率超頻繁,不能夠用 Page Cache 來做。這個時候,我們就可以使用 Partial Cache 的方式來加快速度,在Rails 裡面 Partial Cache 叫做 Fragment Cache。

使用方式如下

在 View 裡面

選定要 Cache 的部份,將他用 cache block 包起來
<% cache do %>
我們要 cache 的 content
<% end %>
如此,當我們用 production mode 的時候,你就會發現會出現 tmp/cache/你的controller/你的action.cache 這個檔案,也就是 cache 的內容。並且 reload 的時候,那個 block 的 content 都是不變的。

理論上 cache 是 work 的,但是如果你去翻 log 時,你會發現 db 一樣會去做相關的 operation。原因是因為我們並沒有告訴 Rails ,Controller 裡面某段 code 是已經 cache 好的東西,不需要去執行。我們都知道 db 通常是最大的 bottleneck ,如果 cache 沒辦法避免 db operation,那這樣的 cache 也僅僅省去了 render 的時間,並沒有太多幫助。

在 Controller 裡面

為了避免這樣,我們要在 controller 裡面指明,當 Fragment Cache work 時,某些 operation 是可以不用執行的。
unless read_fragment :action => '這個action的名字'
跟 cache 有關的 operation
只要有 cache ,就不需要去執行的 code.....

end
如此如果已經 cache 過了, Rails 就會避開這段 code 的執行。

要如何 Expire Cache

Expire Cache 也是相當的方便,在 controller 裡面使用 expire_fragment 即可。
expire_fragment :action => '這個action的名字'
以上都是最最最基本的 Fragment Cache 的機制,但是如果只知道這些東西,根本做不了太多事情,我們下次來談談比較實用的 Fragment Cache 機制。

Ajax auto-update

Ajax 跟 Ruby on Rails 的整合已經眾所皆知,今天心血來潮,想來實做 digg-spy 形式的資料更新。但是我的需求是每隔一段固定的時間更新,而不是一直 append 新的訊息。在參考了幾個網站後,驚訝的發現,這種 auto-update 的 Ajax 怎麼可以這麼簡單(請用 thegiive 的口氣自行想像)。

*** 斜體字為你尚未命名或已經命名好的名稱 ***

[summary] 我們得準備:
  • 一個 controller 裡面的新的 action,假設該 action 名稱為 action-name,該 controller 名稱為 controller-name
  • 一份 RJS template,置放在 /app/views/controller-name/action-name.rjs。
  • 一份 partial template,假設置放在 /app/views/controller-name/_show_topics.rhtml。
  • 在適當的地方加入 javascript。


step 0:
首先在預設的 layout 中掛入 prototype 等 JavaScript。寫法如下:

<%= javascript_include_tag :defaults %>

其中 :defaults 會將 /public/javascripts/ 中預設的 JavaScript 都掛載上來。如果不想全部掛上來的話,可以改寫成 :script-name

step 1:
接著在你的 controller 裡面寫入一個新的 action。假設我們想要每分鐘更新文章列表:

def update_topics
# 取出最新的 forum 文章,get_all_topics 請自己在 model / controller 撰寫
@all_topics = get_all_topics(10) # hard code 是不好的,小朋友不要學。也可以順便做錯誤處理。
end

step 2:
接著是你必須建立一個檔案 /app/views/controller-name/action-name.rjs,在這個 RJS template 裡面使用 prototype helper 裡面的 methods 來與瀏覽器溝通。內容如下:

page.replace_html 'update-topics-div', :partial => 'show_topics', :object => @all_topics
page.visual_effect :highlight, "update-topics-div"

我們用了 replace.html 來幫我們做取代的動作,如果是 digg-spy-style 的話,通常我們會用 insert.html,有空再來寫。值得注意的是,在這個檔案裡面無須使用 <%%> 來內嵌,直接撰寫即可。update-topics-div 這邊代表的是欲取代的 html element id,show_topics 則是欲取代的 partial template(注意 template 的命名有 prefix _,render 時無須 prefix _,也無須副檔名),再利用 :object 將變數 binding 到 template。visual_effect 效果我就不介紹了。

step 3:
當然你要有輸出的 view,我們假設是 /app/views/controller-name/another-action-name.rhtml。部份的片段應該有:

...
<%= render :partial => 'show_topics' %>
...

而我們的 template _show_topics.rhtml 假設長成這個樣子:

<div id='update-topics-div'>
<ul>
<% @all_topics.each{ |topic| %>
<li><%= topic.title %></li>
<% } %>
</ul>
</div>

step 4:
最後我們挑選一個適當的地方加入 JavaScript,什麼叫適當的地方呢?因為有些 template 會共用的關係,如果把 JavaScript 加入到共用的 template,會出現 RJS 找不到 html element id 的錯誤,所以說得擺在適當的地方,當然跟瀏覽器的支援程度也有關係。假設找到了適當的地方,則我們加入 JavaScript 如下所示:

var updateInterval = 60 * 1000; // 每分鐘 update 一次
var timer;

function goUpdateTopics() {
timer = setInterval('updateTopics()', updateTopicsInterval);
}

function updateTopics() {
// url 記得寫對!
url = "/controller-name/action-name";
new Ajax.Request(url, {
asynchronous: true,
method: "get",
});
}
goUpdateTopics();


Well, 經過這四步驟,你應該已經可以快樂地使用 Ajax 整合在 Rails 裡所提供的功能與樂趣。:)

延伸閱讀

3/24/2007

一個接近夢想的境界

A:我們該如何跑 Ruby on Rails 呢?
B:簡單,將你的 Rails App JAR 起來,然後 upload 到 tomcat ,就大功告成了。

這句話在Stockholm Rails/Ruby Group meeting 的時候爆出來,就顯得真的很夢想無限呀。
You JAR up your rails app, upload it to TomCat and your Rails app is deployed. It just works.
當然目前 JRuby Team 還沒 release 1.0(1.0 的目標就是 JVM 上跑 Rails),還得花點時間等這一天,不過,或許這一天真的快了也說不定。

3/21/2007

Gentoo 上面跑 DRB 可能出現的問題

有時候在 Gentoo 上面使用一些有關 DRB 的東西,像是 Breakpoint Server ,或是 gem_server。通常會遇到兩種很奇怪的 Error


  1. Exception `SocketError’ at /usr/lib/ruby/1.8/drb/drb.rb:840 - getnameinfo: ai_family not supported
  2. /usr/lib/ruby/1.8/drb/drb.rb:840:in `getaddrinfo’: getnameinfo: ai_family not supported (SocketError)
這問題的解法我一直到看到這個網頁才瞭解,是因為在 Gentoo 上面編 Ruby 時需要啟動 ipv6。不要問我為啥,事情就是這樣 。So, 解決方式很簡單,/etc/portage/package.use 裡面加一行 dev-lang/ruby ipv6。然後重新安裝 Ruby 即可。

3/20/2007

Rails 的 Rake Task List

今天有人問到怎麼清 log ,結果我一直記得 Rails 的 Rakefile 可以幫你 log,不過還是忘記怎麼打了,查了一下發現只要 rake log:clear 即可。

在 Rails 裡面,預設了很多 rake 指令可以幫你輕鬆達成許多任務。這裡列出所有的 Rails Rake Task List。基本上,要叫出 Task List 只要在 Rails 資料夾下面打 rake --task ,或是 rake -T ,他就會自動幫我們叫出 Rails 下面所有的 Rake Task List。

DB 處理方面的 Task
  1. rake db:fixtures:load
    # Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y
  2. rake db:migrate
    使用 Database 的 migrate 功能,他會把 db/migrate 裡面寫的 script 寫入到 DB 裡面。
  3. rake db:schema:dump
    將 DB 的 Schema Dump 到 schema.rb 這個檔案裡面
  4. rake db:schema:load
    將 schema.rb Load 進入 DB 裡面
  5. rake db:sessions:clear
    假設你的 Session 是利用 CGI::Session::ActiveRecordStore 存在 DB 裡面的時候,這個指令可以清理裡面Session 的 Table
  6. rake db:sessions:create
    當你要使用 ActiveRecordStore 來存放 Session 時,你可以用這個指令來 create Session DB
  7. rake db:structure:dump
    將 DB 的 Schema Dump 到 SQL format 的檔案
  8. rake db:test:clone
    # Recreate the test database from the current environment's database schema
  9. rake db:test:clone_structure
    # Recreate the test databases from the development structure
  10. rake db:test:prepare
    # Prepare the test database and load the schema
  11. rake db:test:purge
    # Empty the test database
自動建立 RDoc
  1. rake doc:app # Build the app HTML Files
  2. rake doc:clobber_app # Remove rdoc products
  3. rake doc:clobber_plugins # Remove plugin documentation
  4. rake doc:clobber_rails # Remove rdoc products
  5. rake doc:plugins # Generate documation for all installed plugins
  6. rake doc:rails # Build the rails HTML Files
  7. rake doc:reapp # Force a rebuild of the RDOC files
  8. rake doc:rerails # Force a rebuild of the RDOC files

清理 log file
  1. rake log:clear
    清除 log file
Rails 版本 Freeze 跟 Update
  1. rake rails:freeze:edge
    把這個 Rails Package 的 version lock 到最新的 version,也就是會 copy 一份Rails包到 vernder/rails/ 資料夾底下,以後跑這個 Rails Package 就不使用系統內部的 Rails Version,而是使用 vender/rails/ 底下的 version。
    # Lock to latest Edge Rails or a specific revision with REVISION=X (ex: REVISION=4021) or a tag with TAG=Y (ex: TAG=rel_1-1-0)
  2. rake rails:freeze:gems # Lock this application to the current gems (by unpacking them into vendor/rails)
  3. rake rails:unfreeze
    如果之前使用過 rake rails:freeze:edge ,但是如果你後悔了,想要重新啟用系統裡面的 Rails Version,那就用這個指令將 vender/rails 裡面的東西清掉。
    # Unlock this application from freeze of gems or edge and return to a fluid use of system gems
  4. rake rails:update # Update both configs, scripts and public/javascripts from Rails
  5. rake rails:update:configs # Update boot/config.rb from your current rails install
  6. rake rails:update:javascripts # Update your javascripts from your current rails install
  7. rake rails:update:scripts # Add new scripts to the application script/ directory
code state
  1. rake stats
    Show 出目前 App Code 的狀況
Unit Testing 方面
  1. rake test # Test all units and functionals
  2. rake test:functionals # Run tests for functionalsdb:test:prepare
  3. rake test:integration # Run tests for integrationdb:test:prepare
  4. rake test:plugins # Run tests for pluginsenvironment
  5. rake test:recent # Run tests for recentdb:test:prepare
  6. rake test:uncommitted # Run tests for uncommitteddb:test:prepare
  7. rake test:units # Run tests for unitsdb:test:prepare
清理 tmp/ 底下的暫存檔
  1. rake tmp:cache:clear
    將所有 tmp/cache/ 裡面的東西都清理掉
  2. rake tmp:clear
    將所有 tmp/ 裡面的東西都清理掉
  3. rake tmp:create
    建立 tmp/ 資料夾,順便建立 tmp/sessions/,tmp/cache/,tmp/sockets/
  4. rake tmp:sessions:clear
    將所有 tmp/sessions/ 裡面的東西都清理掉
  5. rake tmp:sockets:clear
    將所有 tmp/sockets/ 裡面的東西都清理掉

3/19/2007

ActiveRDF - putting semantic web on rails (1)

由諸多的現象我們可以推測,在 Social Web 的時代中 Ruby on Rails 這個 web development framework 不但嶄露頭角,甚至形成了大量的助力。我們能透過 Ruby on Rails 輕鬆地建置 tagging system,我們能透過 Ruby on Rails 建立社群的交流平台,我們能透過 Ruby on Rails 建立人事時地物的複雜關係,甚至透過 Ruby on Rails 來建置 Web Service 的環境與應用。我們甚至可以說,在 Web 2.0 的時代與 Ruby 社群蓬勃發展的現在,Ruby on Rails 幾乎不留遺憾。

我們並不清楚 Semantic Web 是不是 Social Web 的下一世代,不過 Semantic Web 確實有較多數人認同是 web 3.0。Semantic Web 是什麼呢?簡單的說,在 web 上的資源隱藏有語意的註記,透過這些語意的註記,將可以被機器處理。譬如一個簡單不過的例子,在 web 上有兩筆資料,『A 與 B 的結婚紀念照片』、『C 在 blog 中揭露了他的父親是 A』,則機器透過這兩筆資料上面隱藏語意的註記,再加上人們加諸給它的家庭的定義(或稱 ontology),則機器將可以運算出『C 的母親為 B』這樣的推論來。

我很難想像現存的 WWW 這麼龐大的資料如何被加入語意的註記,於是我非常能夠認同 Semantic Web 必須也得依靠眾人的智慧來成就,因為那些語意的註記絕對不是幾個團體能夠負擔,當然也很難達到自動的語意註記。

扯得有點遠了,回到這篇的主題來。我們知道 W3C 制定出了 RDF 與 OWL 等來描述 ontology 的框架 / 語言來。前陣子 DERI 裡面幾個研究人員幫 Ruby on Rails 寫了 ActiveRDF 來方便 Ruby on Rails 加入一些語意的支援。我們知道 ActiveRecord 扮演著 Object-Relational Mapping 的橋樑,將 Relational Database 裡面平面的表格透過 code generation 建構為具有階層關係的物件。而 ActiveRDF 則是可以將 RDF(S) 裡面的 Triple 關係建構為具有階層關係的物件。顯然地,作者群的開發時,野心並沒有大到連 reasoning、validation 或 constraint 這些機制都支援,正如同 Ruby on Rails 的精神之一;只扮演好自己該扮演的角色就足夠了。


延伸閱讀


3/18/2007

SWIG/Ruby

以下文章最早發表於 2006.08.06, ptt Ruby 板。由於這裡的版面跟 BBS 有極大的差異,所以稍微調整了一下排版。不過程式碼的部份可能很難調整到方便閱讀,這點也就請見諒了。

http://www.swig.org/

SWIG is an interface compiler that connects programs written in C and C++ with scripting languages such as Perl, Python, Ruby, and Tcl.

也就是,讓 C/C++ 與 scripting language 溝通的介面產生器。目前支援 13 種程式語言(含非 scripting language):

* Allegro Common Lisp
* C#
* Chicken
* Guile
* Java
* Modula-3
* Mzscheme
* OCAML
* Perl
* PHP
* Python
* Ruby
* Tcl

之前我測了一些 Ruby 與 C++ 的溝通方式,以下將簡單介紹一下測試的方法。首先先看到使用方式,我想這個應該是最重要的部份,畢竟如果使用不便,那其他的也不用再多說什麼了…。




以下這個程式是一個簡單的 type wrapper,(傳值版)由 template parameter 指定型別,data() 當 getter, data(type) 當 setter. 另外在 c'tor 和 d'tor 中插入 IO 來追蹤物件的生成與摧毀:
// in Wrapper.h

#include <iostream>

template <class Data>
class Wrapper{
public:
Wrapper(Data new_data): data_(new_data){
std::cout << "C++: Wrapper is created, which is '"
<< data_ << "' .\n";
}
~Wrapper(){
std::cout << "C++: Wrapper is decayed, which is '"
<< data_ << "' .\n";
}
Data data() const{ return data_; }
void data(Data new_data){ data_ = new_data; }
private:
Data data_;
};

有了這個 .h 檔的介面後(這裡同時也包含實作),接下來我們需要的是一個由 swig 產生的膠水,將 C++ 介面與 Ruby 介面混合,暫時命名為:Wrapper.i, 詳細做法等一下我們再來看。總之最終我們將產生一個動態連結檔,也就是一個 Ruby 的 module, 叫 Wrapper. 不過需要一提的是,由於 Ruby 本身不支援 template, 所以我們在 Wrapper.i 中必須明確指出我們需要什麼型別,否則 Ruby 會無法使用。(更明確來講,純 template 根本無法 compile)

所以在 Wrapper.i 中,裡面有一行是這個:

%template(Integer) Wrapper<int>;

這句話的意思是,將 Wrapper<int> 這個型別取作 Integer. 於是 Ruby module 做好後,可以使用 Integer 這個型別。




雖然我中間還有很多測試,不過直接看目前的最後結果吧。寫一個 Ruby 程式,內容如下:
#!/usr/bin/ruby
# in test.rb

require 'Wrapper' # 讀入剛剛做好的 Wrapper module

class Test < Wrapper::Integer # 繼承 C++ 寫好的 Wrapper<int>

def initialize(new_data) # c'tor 其實可以省略,因為可以使用
super(new_data) # C++ 中寫好的 Wrapper
end # 這裡只是展示 super 是可以用的

def plus(num)
data(data + num)
data
end

end

# 正式測試開始

fat = Test.new(123) # 建構出 Test 物件,傳入 123 當引數
puts fat.plus(7) # output: 130

fat.data(456) # 設值為 456
puts fat.plus(4) # output: 460

ok, 可是原本插入 C++ 的 c'tor 中的 IO 呢?整個程式的輸出結果是這樣:

C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .

也就是說,C++ 的 c'tor 與 d'tor 有被確實喚起。這樣說不定可以替 Ruby 實作出 d'tor...不過這是題外話。

另外,剛剛程式中的 fat.data 與 fat.data(456) 確實是喚起正確的 getter 與 setter, 可是 Ruby 沒有 overload? 我沒有實際去看,但我猜他內部大概是這樣實作的:
def data(*args)
case args.size
when 0 then return self.data_a
when 1 then return self.data_b(args[0])
end
end

所以如果你想要重新定義其中一個 overloaded method, 做不到…可能必須找到他實際的名字才有辦法。




那麼我們再來看到由 C++ 呼喚 Ruby... 否則不就太寂寞了嗎?基本上 Ruby 本身就提供了良好與 C 溝通的機制,照用不就好了?是這樣說沒錯,可是說真的我很討厭 C 的介面…囧rz
常常會要你記下一堆有的沒的,感覺很討厭。所以我覺得需要一個 C++ 的 wrapper, 一個可以輕鬆呼叫 Ruby 的方式。後來我找到這個:

http://www.sourcepole.com/sources/software/c++ruby/

不過這個東西實在是寫得不好,沒有處理 finalize 的部份。不知道還有沒有其他類似的 wrapper 可以用,所以我就暫時用這個來改。改寫他 singleton 的實作,還有記得在 d'tor 中呼叫 ruby_finalize();

最後的結果是,我可以這樣寫:
#include "rubyeval.h" // 就是上面抓來的那個
#include <ruby.h> // embedded ruby 必要的東西,去 ruby-lang 就可以抓

int main(){

RubyEval& ruby = RubyEval::instance();
ruby.eval("require 'Wrapper'"); // 直接用字串執行 Ruby
// 這邊是含入剛剛做好的 C++ => Ruby mod
// 當然這東西要先 compile 好

ruby.eval("fat = Wrapper::Integer.new(123)"); // 直接建立物件
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 123

ruby.eval("fat.data(456);"); // 設值 456
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 456

ruby.run_file("test.rb"); // 直接執行 ruby 程式
}

那個 test.rb, 就是上面寫好拿來測試 Ruby 呼叫 C++ 的程式。於是整個輸出結果就是:

C++: Wrapper is created, which is '123' .
123
456
C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .
C++: Wrapper is decayed, which is '456' .

第一行是從 C++ 建構的 Integer; 123, 456 則是 cout 輸出的。第四行的 123 則是 test.rb 產生 class Test < Wrapper::Integer 那個。130, 460 則是 Ruby 的 puts 產生出來的。

最後兩個 decayed 則是 Ruby 的 gc 正確在程式結束時摧毀物件輸出的順序剛好跟 c'tor 反過來,一切正常。




最後就來看怎麼實現這個的。Wrapper.i 是這樣寫的:

%module Wrapper

%{
#include "Wrapper.h"
%}

%include "Wrapper.h"
%template(Integer) Wrapper<int>;

這些語法請參考 SWIG 的網站,那邊都有詳細說明。甚至是 C++ 的多重繼承,在 Ruby 中也能使用。當然多少可能會有點限制,但似乎可以實現一定的功能。

下指令:
swig -c++ -ruby Wrapper.i
這樣就可以以 Wrapper.i 這個介面檔實作出由 Ruby 溝通 C++ 的程式。那個程式會叫做 Wrapper_wrap.cxx, 也就是 YOUR_NAME_wrap.cxx

再來就是將所有的程式打包成一個動態連結檔了,所有的檔案是:Wrapper.h (包含實作), Wrapper_wrap.cxx (SWIG 的膠水)。方便的做法是使用 Ruby 的 lib, 叫 mkmf
寫一個 Ruby 程式叫 mkmf.rb, 內容是:

require 'mkmf'
$libs = append_library($libs, "stdc++")
create_makefile(ARGV[0])

這邊是由於我個人方便,所以這樣寫的。第一行含入 mkmf, 第二行是因為我用到 C++ 標準函數庫(cout),所以必須連結 stdc++ 才行(我的系統是 GCC, VC++ 的話我不清楚)接著由 create_makefile 產生出我要的 makefile, 名稱由 cmd line 輸入。這邊我是輸入 Wrapper.(btw, 有人知道 stdc++ 可否動態連結嗎?)

接著就可以由 make 將 Wrapper.h 和 Wrapper_wrap.cxx 合併做出 Wrapper.so(我想 VC++ 系統應該會做出 Wrapper.dll 之類的)




最後要提的是,整個程式的執行環境。由於我是在 cygwin 下作業的,所以獨立執行這些程式需要的是:

cygwin1.dll 1.78 MB
cygruby18.dll 703 KB
cygcrypt-0.dll 6.5 KB

我想如果在 windows 下由 VC++ compile 的話,應該就只會需要 Ruby interpreter 的 dll 檔(這裡是 cygruby18.dll)。不過我就沒有做這一步的測試了,留給讀者當作練習吧 XD

(忽然心血來潮所以整理了這些東西)

2006.08.06 godfat 真常

延伸閱讀