高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

如何在 Rails 裡使用 AMF

前言

常聽很多朋友會問:「flash要怎麼樣跟資料庫串接?」。

答案其實很簡單:「不行!目前flash沒辦法直接與資料庫串接」。

那到底別人是怎麼做的? 為什麼他們的Flash可以由後台管理而且更新資料? 其實運作流程是這樣:

Flash(*.swf) <–> Server端程式(ASP, ASP.NET, PHP..etc) <–> DB

SWF透過HTTP GET/POST的方式送給中間的程式,透過中間的程式,Flash才得以從資料庫中取出/寫入資料。

其中SWF與server端程式溝通的格式,有簡單的純文字組合,或是用JSON或XML來包裝資料,再餵給SWF,最後再呈現在Flash上。特別是XML對AS3還滿友善的,可以簡單的就取出指定節點的資料。

而今天要提的 AMF(Action Message Format),其實做的工作跟上面的JSON或XML差不多,只是它的格式是binary的。

AMF的實作,各家程式語言或Framework都有類似的實作品 像是PHP就有AMFPHP/Zend_Amf,Python有PyAMF,.NET有FluorineFx,Ruby的話則有RubyAMF

不過不管是哪家實作的AMF,流程上都差不多:

  1. SWF連上指定的Gateway。
  2. 呼叫/執行Gateway上掛載的service,並把所需的參數以AMF格式傳給它(如果有的話)。
  3. 執行結果回傳。

最近手邊有個案子正好是用Rails寫的,剛好有用上AMF,就趁這個機會寫一下心得,免得自己以後忘記(其實在Rails裡面,用respond_to直接render產出xml或json也還滿方便的..)

環境

  • Mac OX 10.6
  • Rails 2.3.5
  • Ruby 1.8.7
  • RubyAMF 1.6.5

安裝

就不多做Rails的介紹了,直接開個空白的Rails Project來做示範:

> rails rubyamf_demo
> cd rubyamf_demo

接下來,安裝RubyAMF。網址:https://github.com/victorcoder/rubyamf_plugin

個人比較建議直接用script/plugin來安裝,簡單方便。當然如果要自己下載打包檔再手動放進來也ok的。

> script/plugin install git://github.com/victorcoder/rubyamf_plugin.git

沒問題的話,這個動作就會幫你把RubyAMF安裝在vender資料夾裡了。

image

其實這個安裝的過程中,除了把相關檔案裝到vender裡之外,背後有幫忙做了一些事你可能需要知道的:

1. app/controllers/ 裡多了一個rubyamf_controller.rb

這是整個RubyAMF的對外窗口,gateway就是寫在這裡了。

2. config/ 裡多了一個 rubyamf_config.rb

RubyAMF的設定檔,打開這個檔案應該可以看到許多註解說明,可依情況及個人使用習慣做調整

3. config/initializers/mime_types.rb多加了一行

Mime::Type.register "application/x-amf", :amf

到時候可以像 render :text => "hello"一樣,直接用render :amf => "hello"來輸出

4. config/route.rb多了一行路徑設定

map.rubyamf_gateway 'rubyamf_gateway', :controller => 'rubyamf', :action => 'gateway'

gateway到時候的位置就是http://127.0.0.1:3000/rubyamf/gateway

5. public/ 資料夾多了一個 crossdomain.xml

常用AS在串接外部資料的人應該知道這是幹嘛的了,預設是全開

<allow-access-from domain="*" />

如果有需要調整可直接動手修改。接下來試著啟動server,看看能不能正常運作:

> script/server

沒問題的話,接著開瀏覽器看看http://127.0.0.1:3000/rubyamf/gateway/。如果你看到一個黑色的畫面,中間放著一個RubyAMF的logo:

image

恭喜你,目前這樣就算是把RubyAMF安裝起來了。接下來,就要開始準備寫service上去了

實作

Rails部份:

先建立一個model,待會我們會用來取出/寫入資料用的:

> script/generate model book author:string content:text

目前只放了author跟content兩個簡單的欄位(for demo purpose, Model部份沒有特別做驗證)。

> rake db:migrate

為了省去另建資料庫的麻煩,這裡直接使用預設的SQLite做為資料庫。table建立後,先塞一筆測試資料進去:

> script/console
>> Book.create(:author => "eddie", :content => "this is a RubyAMF demo")

再來新增一個controller,裡面放一個hello_world這個action:

> script/generate controller amf_test hello_world

在hello_world這個action裡,我們加一行:

1
2
3
def hello_world
  render :amf => "Hello AMF"
end

大部份網路上看到的範例都是用Flex當範例,不過我個人比較偏好Flash。以下我就用Flash當做範例示範(其實沒太大差別啦,純粹個人喜好)。

Flash部份:

檔案:amf_hello.fla

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var nc:NetConnection = new NetConnection();
nc.objectEncoding = ObjectEncoding.AMF3;
nc.connect('http://127.0.0.1:3000/rubyamf/gateway');

var responder:Responder = new Responder(onOK, onErr);
nc.call('AmfTestController.hello_world', responder);

function onOK(res:Object):void
{
    trace(res);
}

function onErr(res:Object):void
{
    trace("Error!");
}

其中比較需要注意的是nc.call那一段,直接呼叫ControllerName.ActionName就行了。

按下Ctrl+Enter之後,應該就可以看到”Hello AMF”的字樣了,代表你的SWF已經可以成功從AMF Gateway讀資料回來了。如果這裡有發生錯誤,可能檢查一下是不是有打錯字,或是server忘了啟動。

接下來,我們試著送資料給Gateway,讓它寫入資料庫之後再回傳目前資料庫裡的書總共有幾本。

這次我們先做Flash端的介面,我放了一個按鈕跟二個輸入框,instance name分別取名為add_btnauthor_txtcontent_txt

檔名:amf_addbook.fla

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
add_btn.addEventListener(MouseEvent.CLICK, click_handler);

function click_handler(evt:MouseEvent):void
{
  var nc:NetConnection = new NetConnection();
  nc.objectEncoding = ObjectEncoding.AMF3;
  nc.connect('http://127.0.0.1:3000/rubyamf/gateway');

  var amf_object:Object = {
    'author': author_txt.text,
    'content': content_txt.text
  };

  var responder:Responder = new Responder(onOK, onErr);
  nc.call('AmfTestController.add_book', responder, amf_object);
}

function onOK(res:Object):void
{
  trace("目前共有" +res +"本書");
}

function onErr(res:Object):void
{
  trace("fail");
}

其實只是把呼叫的部份放到click handler裡,並且在裡面做了一個object,並且在呼叫的時候:

1
nc.call('AmfTestController.add_book', responder, amf_object);

把它當做參數傳出去。

接下來,在Rails裡,我們要來加一個add_book這個action,準備接收資料:

1
2
3
4
5
6
7
8
def add_book
  if is_amf
    Book.create(:author => params[0][:author], :content => params[0][:content])
    render :amf => Book.count
  else
    render :text => "error"
  end
end

這裡可以用is_amf來檢查傳進來的是不是AMF,傳進來的參數可用 params[0] 取得。

接著執行Flash,沒問題的話,當每次按下Add Book按鈕時,它就會把author跟content資料寫入DB,並回傳目前總筆數。

以上為展示目的,都沒有加資料的驗證,所以就算空白資料也可以送出。

以上是一些個人小小的心得,供大家參考囉

相關網站:

原始檔下載(fla + rails project):

http://nayumi.myweb.hinet.net/downloads/sample.zip

update: RubyAMF專案已由原本的google code移至github

2009 PTT Flash 版聚

image

今年八月底聚集了一些對Flash有熱好的朋友們,讓大家互相認識一下平常在版上互動的人長什麼樣子,也順便透過幾個專題演講來分享一下心得。

活動照片:https://picasaweb.google.com/108852467279874647445/PTTFlash

這應該算是PTT Flash版第二度的板聚(上一次是在2008年的八月,已經相隔一年了)。

Flash在台灣一直比較少見有專門大型的研討會,比較大型的大多是伴隨產品上市的發表會一起辦。

雖然我們這個不是官方舉辦或贊助的活動,但我還是很羨幕能有像Adobe MAX或是Flash on the Beach那樣大型的研討會。

希望有一天能在台灣也能看到純粹技術發表的研討會 :)

至於我們這個PTT Flash Workshop,我想希望可以把這樣的聚會變成固定舉辦的活動,也許可以辦在寒、暑假,然後再來去借個比較大一點的場地來辦party好了。

影片

Jones - 製作自己專屬的Facebook應用程式

Cjcat - Stardust Particle Engine

高見龍 - MVC

戰利品

image

難得給自已排了一段時間出去玩,這趟去英國溜躂,音樂劇Les Miserables是心靈上最值回票價的體驗,當然物質上的一些戰利品也是不可少的啊(其實根本就是去買玩具的吧)

1. 電話亭存錢筒:

是鐵製的,拿在手上挺有份量(存錢這是一定要的啊)

image

2. 小羊鑰匙圈

這個是在湖區買的小羊鑰匙圈,造型真是超可愛

image

3. 懷舊寶箱

這是在二手店看到的小寶箱,不知道為什麼我看了就覺得很愛

image

4. 超難拼圖

號稱世上最難的拼圖,雖然只有529片,但拼起來像是4,000片?!

image

5. Doctor Who模型

大概二、三十公分高,人物部份要自已著色,Tardis在啟動的時候會發出跟影集一樣的聲音。

image

6. Daleks

有看Doctor Who的人一定知道這像垃圾筒的機器人是誰啊….

image

而且壓它還有音效的咧,真讚!

7. 其它

每個景點專屬的名信片當然也是不可少的(本來要寄的,但最後都捨不得只寄了一張最不喜歡的出去了..)

另外還有個搖控的小直昇機(給我爸玩的)

結論:

要努力工作,趕快把錢賺回來了..!!!

上台講話

Oops! 發現最後一篇blog竟然已經是去年的事了!! 是時間過太快還是我太懶了?

最近因為幾個案子在趕著要上線,不久前的OSDC.tw沒辦法全程參加,只好挑自已喜歡的Session參加(ericskGAEO比我想像中的還好用)。

以前某個我不喜歡的老闆曾跟我說過,做presentation就像表演一樣。

近來我自告奮勇的找了一些對flash有興趣的朋友們每週來聚會,每次約2小時,我教大家怎麼來學 ActionScript3.0,沒另外收費(只需各自分擔場地費,我還太嫩,還不到那種敢收學費的等級)。

常聽到「這個東西我懂,我只是不會講而已」,但我一直相信,真的懂的人一定是講得出來的;不會講不代表真的不會,只是還沒能完全透徹,某個環節沒學好,講不出來。

在台上講話實在是件不容易的事,一來得先克服緊張感,二來得讓自已的腦袋保持清醒。

當然我也沒這麼佛心,開這樣的課當然是有目的:

  1. 透過在台上講課,讓自已對AS3更熟悉。
  2. 訓練自已講話的技巧,練習怎麼掌握台下的反應(可能講太快或一下子講太難)。
  3. 透過每週準備課程,讓自已不會跟AS3離太遠。

越是複雜的技術,要做這種大眾化的教學困難度也越高,像是知名的flash totur Lee Brimelow 用影音的方式來教學,清楚又易懂。

最近在路邊牆上貼的一段話:「有困難是能力不夠,有麻煩是方法不對」。看來,這條路還相當漫長 :)

Doctrine, the PHP ORM Framework

image

Doctrine是一個在PHP上的資料庫ORM(Object Relational Mapper),最近手邊的幾個小案子透過Doctrine來實作。ORM的確是讓開發者少寫不少的SQL查詢,但相對的效能一定是比簡單的SQL要來得 差,我想魚跟熊掌要兼得並不容易,便利跟效能之間總有個天平讓開發者來自已調整,所以ORM好不好用就見人見志囉。

一、前置作業:

1. 系統需求

依官網上面提到的,Doctrine需要PHP 5.2.3以上的版本,除此之外就不用再另外安裝其它的套件或函式庫即可運作,不過如果要使用PDO(PHP Data Object)的話,則可能會需要另外安裝PDO驅動程式。

2. 檢查PDO驅動程式

如果你要用PDO的話,想要知道你的主機上是不是有安裝你所需要的PDO驅動程式,只要一行簡單的PHP程式放到主機上執行:

1
<?php phpinfo(); ?>

如果你有看到一段PDO的資訊,那差不多應該就是有安裝了,再確認一下是不是有你要的版本,以及是否啟用即可。

二、取得原始檔

有幾種取得Doctrine的方法:

  • SVN(Subversion)
  • Pear
  • Zip-package

如果你熟悉SVN的操作的話,這應該是最簡單使用的了。如果你只是想試玩一下Doctrine的功能的話,你可以下載官網上打包好的Sandbox套件來玩。我個人比較喜歡官方打包好的壓縮檔 :)

什麼是Sandbox套件?

Doctrine網站的下載區有貼心的打包了免設定的方便套件,直接下載、解壓縮後,就可以拿來試玩了。這裡面包括了一些範例schema檔案以及可以匯入的資料檔,讓你可以很快的就可以開始體驗Doctrine的優點。

三、開始第一個專案

在Doctrine裡,Doctrine_Record是最基本的元件。資料庫裡的每個資料表都會有一個對應的Doctrine_Record類別。這個類別是以Active Record這個設計模式實作的,所以在其它語言像是在ROR(Ruby on Rails)上才看得到方便功能,在Doctrine裡也可以使用到了,即使是複雜的資料庫操作,透過這個類別也可以簡單的完成。

在使用Doctrine_Record來建立資料表時,如果沒有特別指定Primary Key的話,Doctrine會自動幫你加一個叫做id的主索引欄位(自動編號)。接下來你需要做的事情,就是建立一個繼承自 Doctrine_Record類別的子類別,然後透過setTableDefinition()以及hasColumn()就可以來建立你的資料表。

為了單純一些,我們這裡只先用一個表格做範例(多個表格之間的關聯性操作,例如一對一、一對多、多對多等關係,再另外開幾篇再寫)。假設我們現在要建立一個 叫做member的會員資料表,這個資料表裡會有id、username、password以及建立日期這幾個欄位,我們可以這樣做:

檔案:models/Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
     class Member extends Doctrine_Record
     {
         public function setTableDefinition()
         {
             // 預設將會使用跟"user"做為資料表的名稱(同名)
             // 並且會自動加一個欄位id的主索引鍵
             $this->hasColumn('username', 'string', 30);
             $this->hasColumn('password', 'string', 30);
         }

         public function setUp()
         {
             $this->actAs('Timestampable');
         }
     }

除了手工打造模型類別外,你也可以使用YAML格式的檔案來建立資料表的schema檔案,底下這個範例的設定跟上面這個類別是一樣的意思,而且也可以透過Doctrine的一些指令來互相轉換。

檔案:schemas/schema.yml

1
2
3
4
5
Member:
  columns:
    username: string(30)
    password: string(30)
  actAs: [Timestampable]

這裡要注意的是,YAML的縮排是有意義的,所以要特別注意縮排的層級。你可以用一行簡單的語法就可以把YAML檔案(.yml)轉換成模型類別(.php):

檔案:createMode.php

1
2
3
<?php
     require_once 'config/doctrine_config.php';
     Doctrine::generateModelsFromYaml('schemas/schema.yml', 'models');

執行之後看一下models資料夾有一個自動產生的generated資料夾(如下圖),你會看到有一個Member.php以 及BaseMember.php。其中,Member.php是可以讓你自己定義一些功能用的,而BaseMember.php裡的內容則是 依據YAML的內容幫你寫好的。(要特別注意BaseMember類別會在每次執行轉換的時候重新產生一次,而Member類別只會產生一次)。

image

我們現在有一個透過YAML建立的Doctrine_Record類別,接下來就可以把它匯入資料庫來建立資料表了。

檔案:modelToDatabase.php

1
2
3
4
<?php
     require_once 'config/doctrine_config.php';
     // 建立資料表
     Doctrine::createTablesFromArray(array('Member'));

這樣就搞定了!看一下資料表的樣子:

image

如前面說的,它會自動加上id這個欄位,而Timestampable則會自動幫忙加上created_atupdated_at這兩個欄位。

現在我們有一個資料表以及一個對應的模型類別可以讓我們來練習資料的基本操作了(新增、修改、刪除及查詢)。

新增:

檔案:testInsert.php

1
2
3
4
5
6
7
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $myTable->username = 'eddie';
    $myTable->password = 'mypassword';
    $myTable->save();

資料表欄位的指定,除了上面這種物件式的存取方式外,也可以使用陣列的方式:

1
2
3
4
5
<?php
  $myTable = new Member;
  $myTable['username'] = 'eddie';
  $myTable['password'] = 'mypassword';
  $myTable->save();

不用寫什麼insert..into..,只要一個save()就完成了! 回頭看一下資料表就會發現,有一些神奇的事情它幫忙做了。上面這段程式碼執行後,member資料表會長得像這樣:

image

修改:

假設剛剛寫入的那筆資料的id編號是1號,更新方法是先找到它,然後一樣用save()來更新:

檔案:testUpdate.php

1
2
3
4
5
6
7
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $record = $myTable->getTable()->find(1);
    $record->password = 'mynewpassword';
    $record->save();

結果如下:

image

你會發現,password欄位變成”mynewpassword”之外,它的更新時間(updated_at)也跟著更新了。

刪除:

跟更新差不多,也是先找到那筆資料後,再給它一個delete()

檔案:testDelete.php

1
2
3
4
5
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $myTable->getTable()->find(1)->delete();

查詢:

假設我要找出id編號是1號的資料,可以這樣做:

1
2
3
<?php
  $myTable = new Member;
  $result =$myTable->getTable()->find(1);

除了透過find方法外,Doctrine還有它自已一套叫做DQL的語法,使用方法大致如下:

檔案:testDQL.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $record = $myTable->getTable()->find(1);
    $record->password = 'mynewpassword';
    $record->save();
    $result = Doctrine_Query::CREATE()
                ->select('*')
                ->from('Member')
                ->where('id = ?', 1)
                ->fetchArray();

    print_r($result);

印出來的結果如下:

image

上面的DQL相當於底下的SQL:

1
select * from member where id = 1;

其實DQL的細節也可以另外來寫一篇 :)

結論

老實說,如果只是簡單的查詢,Doctrine寫起來不見得比原本的SQL快,而且效能又比較差一些,但如果是比較複雜的查詢,透過Doctrine包裝好 的一些方法,可以不用去想那些單引號、雙引號的問題外,也可以安全的避開可能的危機(例如SQL Injection) ,就如同最前面說的,便利跟效能,我會比較偏向便利的這一方。

更多的細節,可參考Doctrine官網手冊,寫得滿詳細的。

上面的程式碼我也有打包了一份供大家參考囉 :)

檔案下載

使用前請先看一下config/doctrine_config.php裡面的資料庫相關設定。

相關連結