[Books] AIRの良書とFlexとRailsのクソ本

Tags: , , ,

どちらも同じ出版社の本だけど対照的。

  • Adobe Air in Action
    Adobe Air in Action

    AIRの良書。Flash, Flexに慣れ親しんだ人がAIRについてのみ知りたい場合などに最適。逆にFlashの知識が全くない状態でAIRをやってみようという場合には解読不能かも。Window, File system, Drag-and drop, SQLiteなど、AIR特有の機能に内容を絞っていて、しかもかなり原理的なところから説明しているので好感度が高い(FileStreamの進行状況を視覚化してあったり)。難点を挙げるとすると、これは出版社MANNINGの問題なんだけど、ここの本のレイアウト、文字スタイルが無駄に凝ってたり見出しごとにフォントかえてたりして見づらいんだよね。目がチカチカするっていうか。O’Reillyはそのへんさすがだと思う。

    » レーダーチャート評点

  • Flexible Rails: Flex 3 on Rails 2
    Flexible Rails: Flex 3 on Rails 2

    Flex + Rails。2008年に購入した本の中でぶっちぎり独走状態のクソ本。扱っている内容はFlex, Railsとも実践的で高度なのだが(Flex 3, Rails 2.0, RubyAMF, Cairngormなどを一緒に扱っている本はたぶん世の中にこれしかない)、著者の文章力がお粗末。自分の挙げた質問に脱線しまくったあげく見当違いな答えが書いてあったりして愕然とさせられる。要所要所でかいま見られる「この本への著者の愛」も読んでて非常に気持ち悪い。「TVゲームが超絶うまい小学生に書かかせたゲーム攻略本の文章」っていうとイメージ湧くかな。この本のせいで出版社のMANNINGのイメージがよろしくなくなった。

    » レーダーチャート評点

E4Xで取得する方がAMFで取得するより5倍早い

Tags: , ,

Railsからの応答をE4Xで受信した場合とAMFで受信した場合を比較した。使ったのはidとnameからなる1000行のMySQLデータ。Flexからリクエストしてから応答までの時間を測定したところ、AMFの方が5倍も遅くなって困惑した。環境はFlex SDK 3.0, Ruby 1.8.6, Rails 2.1, MySQL 5.0.45で全て同じローカルマシン内。

Rails側

RubyAMFをインストールしたRailsプロジェクト内にUserモデルとUsersControllerを作成。UserモデルはMySQLのusersテーブル(id, name)とシンクロ済み(テストデータはgeneratedata.comで作成)。UsersControllerに以下のようにindexを記述。

class UsersController < ApplicationController

  def index
    @users = User.find(:all)
    respond_to do |format|
      format.xml {render :xml => @users} # For HTTPService
      format.amf {render :amf => @users} # For RemoteObject
    end
  end
end

AMFの結果をArrayCollectionで簡単に受け取れるようrubyamf_config.rbを一部修正。ただしこの設定はRemoteObjectに対して有効で、NetConnectionではArrayCollectionが使えないため無効。

require ‘app/configuration’
module RubyAMF
  module Configuration
    ClassMappings.assume_types = true
    ClassMappings.use_array_collection = true
  end
end

Flex側 (HTTPService - E4X)

load()とonResult()までの時間を10回測った。測定時間はTextAreaに、受信内容はDataGridにそれぞれ表示。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:HBox width="100%" height="100%">
        <mx:DataGrid id="dg" width="50%" height="100%">
            <mx:columns>
                <mx:DataGridColumn dataField="id"/>
                <mx:DataGridColumn dataField="name"/>
            </mx:columns>
        </mx:DataGrid>
        <mx:TextArea id="loadText" width="50%" height="100%"/>
    </mx:HBox>
    <mx:Button label="Load" width="100" height="30" click="load(event)" />

    <mx:Script>
        <![CDATA[
            import mx.rpc.http.HTTPService;
            import mx.rpc.events.ResultEvent;
            import mx.rpc.events.FaultEvent;
            import flash.utils.getTimer;

            private var _start:uint;
            private var _str:String = "";

            private function load(e:Event):void{
                var req:HTTPService = new HTTPService();
                req.url = "http://127.0.0.1:3000/users.xml";
                req.resultFormat = HTTPService.RESULT_FORMAT_E4X;
                req.send();
                req.addEventListener(ResultEvent.RESULT,onResult);
                req.addEventListener(FaultEvent.FAULT,onFault);
                _start = getTimer();
            }

            private function onResult(e:ResultEvent):void{
                dg.dataProvider = e.target.lastResult.user;
                _str += (getTimer() - _start).toString() + " msec\n";
                loadText.text = _str;
            }

            private function onFault(e:FaultEvent):void{
                loadText.text = e.fault.faultDetail;
            }
        ]]>
    </mx:Script>
</mx:Application>

Flex側 (RemoteObject - AMF)

MXMLの骨格はHTTPServiceを使う場合と同じ。スクリプト部分とservices-config.xmlの設定は以下の通り。

スクリプト部分

<mx:Script>
    <![CDATA[
        import mx.rpc.remoting.RemoteObject;
        import mx.rpc.events.ResultEvent;
        import mx.rpc.events.FaultEvent;
        import flash.utils.getTimer;

        private var _start:uint;
        private var _str:String = "";

        private function load(e:Event):void{
            var ro:RemoteObject = new RemoteObject();
            ro.source = "UsersController";
            ro.destination = "rubyamf";
            ro.index();
            ro.addEventListener(ResultEvent.RESULT,onResult);
            ro.addEventListener(FaultEvent.FAULT,onFault);
            _start = getTimer();
        }

        private function onResult(e:ResultEvent):void{
            dg.dataProvider = e.result;
            _str += (getTimer() - _start).toString() + " msec\n";
            loadText.text = _str;
        }

        private function onFault(e:FaultEvent):void{
            loadText.text = e.fault.faultString;
        }
    ]]>
</mx:Script>

services-config.xml (コンパイラオプションに追加)

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="remoting-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="rubyamf">
                <channels>
                    <channel ref="ch-rubyamf" />
                </channels>
                <properties>
                    <source>*</source>
                </properties>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition id="ch-rubyamf"
            class="mx.messaging.channels.AMFChannel">
            <endpoint uri="http://127.0.0.1:3000/rubyamf/gateway"
                class="flex.messaging.endpoints.AMFEndpoint" />
        </channel-definition>
    </channels>
</services-config>

結果

HTTPService - E4X

load()からonResult()までの時間測定は右テキストエリア内(msec)。10回測定でだいたい0.5-0.6秒。ちなみにRESULT_FORMAT_OBJECT(ActionScript Object)で受信すると0.7-0.8秒ぐらいだった。

RemoteObject - AMF

HTTPServiceの場合と同じ測定で一回あたり約3秒。

AMF、通信設定が面倒なくせにすげー遅い。Ajax and Flex Data Loading Benchmarks | James Ward - RIA Cowboyの結果と違いすぎる。どっか間違ってる?

[Flex][Rails]Railsがアンダースコアを勝手にハイフンに変える

Tags: ,

例えばデータベースにfirst_name, last_nameというコラムを持つusersというテーブルがあって、Railsでデータベースの内容をto_xmlで呼び出すと、以下のようにfirst_nameとlast_nameのアンダースコアを勝手にハイフンに変える。

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user>
        <first-name>Colin</first-name>
        <last-name>Moock</last-name>
    </user>
</users>

Flexでこのxmlデータのノードを受け取る場合

//Data set from Rails
var user:XML;

trace(user.child("first-name"));//Colin

とするか、Rails側で勝手にハイフンに変えないようにする。

@user = User.find(params[:id])
render :xml => @user.to_xml (:dasherize => false)

SVNリポジトリを使ってRailsアプリケーションを安全に開発 at RailsPlayground.com

Tags: ,

注意: 書かないと確実に忘れてミスりそうなのでメモ。当方SVN初心者。もっと手順の少ない楽な方法があれば是非教えてください。

やりたいこと

ローカルのRailsアプリケーションの変更をリモートサーバー上のRailsアプリケーションに「安全に」反映させたい。

Railsの性質上、ローカル - リモートサーバー間の直接の書き換えは超絶危険なので、中継地点としてSVNリポジトリを利用する。これにより、ローカルの変更はSVNリポジトリへ別(サブ)バージョンとして保管、実際のリモートサーバー上での運営はSVNリポジトリから適切なバージョンを拾ってくることで反映させる。つまり手元で変ちくりんな修正を行ってしまっても古いバージョンが保管されているのでリセット可能。かなり大胆なweb運営ができる。

簡単な環境説明

RailsPlayground.comは名前の通り、Ruby on Railsアプリケーションが簡単に使えるのが売りの海外のレンタルサーバー。例えばさくらインターネットだとRailsは手動でインストールできるけど、FastCGIが使えないので動作が遅いらしい(例えばがんちゃんのブログ: さくらのレンタルサーバでRuby on Railsをうごかしてみた…らかなり遅いかも…)。日本国内だとRailsが標準装備のレンタルサーバーは値段がはってしまうようなので、月額$5のRailsPlaygroundを借りてみた。海外だと他にもBluehostTextdrive(現Joyent?)等が有名みたいだけど少し高い。

RailsPlayGroundの$5プランは破格なんだけど使用できるサーバーがApacheオンリーのようなので、Lighttpd等を複数使いたい場合は上のプランを選ばないといけない。あとRailsPlayGroundは契約フォームまわりがすっごくうさんくさかった。User IDが6文字までとか、いきなりクレジットカード番号いれさせられたり、なんか諸々。性能やら手続きやらについてはRailsPlaygroundの申し込み方法 (Ruby on Rails)あたりがわかりやすいかも。サブドメイン、データベースが作成し放題なので、例えばxreaみたいにスパムサイトの温床になりそうなので少し気がかりだけど、今のところ快適に動作している。

SVNリポジトリ設定

  1. RailsPlaygroundにSVNで管理したい旨をメールで要請(要請しないと使えない)。24時間サポートらしくすぐ専用のURLが送られてくる。アクセス先(IDとPW入力)でリポジトリスペースとアカウント作成などを行う。ここではリポジトリ名をrailsappとしておく。SVNリポジトリの保管先はこんな感じになる。
    http://YOURACCOUNT.svnrepository.com/svn/railsapp
    

    YOURACCOUNT部分はサーバー管理者が適当に与えてくれる。

  2. RailsPlaygroundサーバースペースにRailsアプリケーション(以下railsapp)を作成(ドキュメントルートより上)。理由はrailsappを作成する際、Apacheで動作するよう.htaccessをRailsが勝手に作成してくれるから。Apacheでちゃんと動作するように.htaccessを自分で作成できるならこの手順は不要。Apacheサーバーで運営する予定のない人にも不要。
  3. 先ほど作成したrailsappプロジェクト全部をローカルに保存。実際に欲しいのは.htaccessファイル「のみ」なので、事前にローカルで作成したRailsアプリケーション内に.htaccessだけコピーしてもってきてもよいと思う。
  4. ローカルのrailsappプロジェクト全部をSVNリポジトリにインポート。ローカルのrailsappディレクトリ内より
    svn import -m "MESSAGE" . http://YOURACCOUNT.svnrepository.com/svn/railsapp
    

    MESSAGE部分はお好みで(”First import”など)。

  5. インポートが完了したら手元にSVNリポジトリからのコピーを持ってこないといけない(リポジトリとシンクロさせるため)。とりあえずローカルのrailsappを別ディレクトリに移すなどする(ここではrailsappディレクトリと同じ階層にrails_backとして保存)。
    mv /path/to/railsapp /path/to/railsapp_back
    
  6. あらためてリポジトリよりチェックアウト。コピーしたい場所より
    svn co http://YOURACCOUNT.svnrepository.com/svn/railsapp
    

    これでSVNリポジトリrailsappとローカルrailsappがシンクロする。ローカルrailsappを変更してSVNリポジトリへコミット(ci)すると別バージョンとして保存されるはず。

  7. 次にSVNリポジトリのrailsappをRailsPlaygroundのリモートサーバー上へコピーする。これも基本はローカル - SVNリポジトリの場合と同じように、SVNリポジトリ - リモートサーバー間でシンクロさせてやる必要がある。RailsPlaygroundの自分のドメインへアクセス、sshで
    ssh YOURID@YOURDOMAIN.COM
    

    YOURIDとYOURDOMAIN.COMはRailsPlaygroundの契約IDと設定ドメイン。

  8. ドキュメントルートより上にSVNリポジトリをチェックアウト。手順2ですでにrailsappを作成している場合は全部削除。かわりにSVNリポジトリより呼び出す。
    svn co http://YOURACCOUNT.svnrepository.com/svn/railsapp
    

    これでリモートサーバーとSVNリポジトリがシンクロする。必要なときに必要なバージョンをSVNリポジトリから拾ってくればよい。バージョンの選び方等はSubversionの使い方を参照ください。

  9. RailsPlaygroundサーバー上railsapp/publicのシンボリックリンクをドキュメントルート配下に設置。例えば
    ln -s ~/railsapp/public ~/public_html/railsapp
    

    これでhttp://YOURDOMAIN.COM/railsappにアクセスするとSVNリポジトリの内容を反映したRailsアプリケーションが表示されるはず。Railsアプリケーションをドキュメントルート以下に配置すると色々不都合があるためこのような回りくどい処理を行う。

基本の作業手順

ローカル - SVNリポジトリ、SVNリポジトリ - リモートサーバーがシンクロしていることが前提。

  • SVNリポジトリより必要なバージョンをチェックアウト
    svn co http://YOURACCOUNT.svnrepository.com/svn/railsapp
    
  • ローカル作業後、SVNリポジトリへコミット。メッセージはお好みで。
    svn ci -m "MESSAGE" http://YOURACCOUNT.svnrepository.com/svn/railsapp
    
  • RailsPlaygroundサーバーへアクセス
    ssh YOURID@YOURDOMAIN.COM
    
  • SVNリポジトリよりリモートサーバーへチェックアウト
    svn co http://YOURACCOUNT.svnrepository.com/svn/railsapp
    
  • web上に反映される。
  • Apache + Rails + Flex: FlashVarsを使う場合

    Tags: ,

    データベースに格納された本のスコアデータをxmlに整形してFlexチャートに読み込ませたい。実際のデモはBook reviewを参照ください。デモの場合、各ページごとにFlexに読み込ませたいデータが異なるのでFlashVarsを用いてswfファイル外部からコントロールしてやる。

    Radar chart

    Rails側

    スコアデータ5種類用意。特定のbook idのスコアデータをFlexで読み込みやすいようにxmlデータで出力させる。Flex側で極力整形を避けたかったので、Rails側で以下のようなxmlデータを作るURL(例えば、:controller => ‘book’, :action => ‘create_xml’, :id => @book.id)を作成しておく。具体例はFlex + Ruby on Rails + MySQL 連携メモを参照ください。

    <?xml version="1.0" encoding="UTF-8"?>
    <grades>
        <grade>
            <label>Easy to read?</label>
            <score>9</score>
        </grade>
        <grade>
            <label>Good contents?</label>
            <score>8</score>
        </grade>
        <grade>
            <label>Latest version?</label>
            <score>7</score>
        </grade>
        <grade>
            <label>Reasonable price?</label>
            <score>10</score>
        </grade>
        <grade>
            <label>Recommend to people?</label>
            <score>6</score>
        </grade>
    </grades>
    

    各本詳細ページ上にswfファイルを埋め込み、その本のスコアデータXMLを出力するURLをFlashVarsで指定してやる(最後の一行部分)。

    AC_FL_RunContent(
        "src", "path/to/swf_file",
        "width", "300",
        "height", "220",
        "align", "middle",
        "id", "swf_id",
        "quality", "high",
        "bgcolor", "#ffffff",
        "name", "swf_name",
        "allowScriptAccess","sameDomain",
        "type", "application/x-shockwave-flash",
        "pluginspage", "http://www.adobe.com/go/getflashplayer",
        "FlashVars", "url=path/to/<%= @book.id %>"
    );
    

    FlashVarsに送るURL部分を

    "Flashvars", "url=<%= url for :controller => ‘book’,
                    :action => ‘create_xml’, :id => @book.id %>"
    

    のように書いてもよいが、Apacheの場合、url_forの出力がRailsアプリケーションディレクトリ(railsapp)からのパスになるので注意が必要。例えば@book.id=5だと

    //Apacheの場合
    /railsapp/book/create_xml/5
    
    //Lighttpdの場合
    /book/create_xml/5
    

    となる。この辺の話は [Rails] Rails の動作環境と Location について - プログラミングは素晴らしいが詳しい。

    Flex側

    FlashVarsで送信された変数を受け取る。Flash(ActionScriptクラス)の場合と異なるので注意必要。

    //Flex Applicationの場合
    var params:Object = Application.application.parameters;
    trace(params.url);//"path/to/xml_data"
    
    //Flashの場合
    var params:Object = this.loaderInfo.parameters;
    trace(params.url);//"path/to/xml_data"
    

    Flex内部のArrayCollectionに格納するまでの流れは以下の通り(抜粋)。完全なソースはこちらを参照ください。

    import mx.rpc.http.HTTPService;
    import mx.rpc.events.ResultEvent;
    import mx.collections.ArrayCollection;
    
    var params:Object = Application.application.parameters;
    var req:HTTPService = new HTTPService();
    req.url = params.url;
    req.send();
    req.addEventListener(ResultEvent.RESULT, onResult);
    
    function onResult(e:ResultEvent):void{
        var ac:ArrayCollection = e.result.grades.grade;
        //データ処理に続く
    }
    

    追記: 2008/08/08

    チャート部分をFlexからActionScriptのみで作成したところ、ファイルサイズが236kb -> 4kbになった。

    Next Page →