2008-02-05

Rails+WebORBがものすごく遅い時

Ruby 1.8.6のビルド時に pthreadsが組み込まれていると、なぜか RailsでWebORBを動作させた時にものすごく遅い。
ldd /usr/bin/ruby してみて libpthread.soがリンクされていたらそういう状態。
Portageならば emergeするときに USE="threads" があるとそうなる。

vmstatを見たところ、pthreadがリンクされている場合は systemが特にCPU時間を使っている(userはあまり変わらない)ようだ。
Rubyでは、pthreadの有無で動作を分けている箇所は eval.c に集中しているみたいだが、ソースをチラ見したところで原因になりそうなところがどこかはわからなかった。

とにかく、Rubyの動作が遅くて systemにCPU時間が取られているようなら pthreadがリンクされていないバイナリへ取り替えることを試してみた方がいいかもしれない。

YARVなら(Giant Lockで排他的にしか実行されないとはいえ最初からネイティブスレッドを使っているため)こういう問題なさそうだなあ。はやく Ruby1.9で Railsが動くようになればいいな。(いまは動かないよね?)

ラベル: ,

2008-01-10

An alternative of BlazeDS - Rails and WebORB

Railsに WebORBをプラグインすると、Flexの mx:RemoteObject から呼び出し可能なサービスを Rubyで記述することが出来る。

基本的な構図



Flex(クライアントアプリケーション)側は、通信クライアントとなるオブジェクトとして mx:RemoteObject を使用する。

Rails側は、WebORBをプラグインすることによって生成されたコントローラ weborb を通信のエンドポイントとし、クライアントからの呼び出しを受け付ける。このweborbコントローラが、呼び出し対象となるサービスへFlexアプリケーションからのリクエストをディスパッチする。ここでいうサービスとは、app/services に配置された Rubyオブジェクト。

Rails側


コマンドは LinuxなりMacなりでの例。Windowsは知らん。
特に MacOS X 10.5の場合は RubyもRailsも最初から入っているので迷うことはまずないだろう。

# Railsアプリケーションを生成
rails myrailsapp

# WebORBをプラグイン
cd myrailsapp
./script/plugin install http://themidnightcoders.net:8089/svn/weborb

# サービス(リモートオブジェクト)となるクラスを作成する
# app/services/ に クラス名と同じ名前の .rbファイルを配置することで
# WebORBのディスパッチ対象となる
vi app/services/MyService.rb

〜内容〜
class MyService
def say_hello
# ここではもちろん ActiveRecordも使える
# HTTP Cookieによるセッションにアクセスする必要がある場合は
# RequestContext.get_session
# を使うことが出来る

"Hello, World!"
end
end
# Railsを起動(ポート3000番でHTTPの待ち受けになる)
./script/server &

Flex側

<mx:RemoteObject id="srv" destination="MyService"
endpoint=
"http://localhost:3000/weborb/"/>
<mx:TextInput text="{srv.say_hello.lastResult}"/>
<mx:Button label="Say Hello" click="srv.say_hello()"/>


たやすい事この上ない。

WebORBと似たようなことが出来るものとして RubyAMFもある。こちらは Railsのコントローラに :amf というレンダラを提供するもので、WebORBと比べるとより Railsのアプリケーション作法に則ったものだが、個人的には WebORBのほうが気に入っている。ただし実行速度は RubyAMFの方が良いらしい(手元では未確認)。

メモ

Flexの通信は全て非同期オペレーションである。上の例では srv.say_hello() でリモートの say_helloメソッドをコールしているが、srv.say_hello()の戻り値は "Hello, World!" にはならない。メソッド呼び出しはバックグラウンドで処理され、サーバから結果が返り次第 srv.say_hello.lastResult にセットされるのである(上の例では mx:TextInputからそれを参照している)。

メソッド呼び出し結果の着信をトリガにして何らかの処理を行いたい場合は、イベントリスナの登録によって行うか mx:RemoteObjectの中に mx:method要素を明示的に定義し、result属性にイベント処理の内容を記述することになる。

昔からのプログラマは非同期通信に慣れているだろうが、Webから入門した新しいプログラマは Ajaxのために XHRをいじり倒した経験があるのでもない限り馴染みが薄く戸惑うかもしれない。だが Flexでは同期通信が出来ない。信じがたいかもしれないがこれは本当だ。Adobeのフォーラムか何かでこんなやりとりを見かける。

「同期通信をしたいんだけど、どうしたらいい?」
「同期のことは忘れようよ」

ラベル: ,

2007-10-07

ActionWebServiceと BigDecimal

Apache CXF + Aegis Bindingでは Webサービスのオペレーション引数・返値に java.math.BigDecimal型を使うことが出来る。
Railsの ActionWebServiceはどうかと思って調べたら、intか floatしかない。オーノー

ブログや掲示板やSNSみたいなおもちゃならintがあれば十分なわけなのでともかく、業務システムの場合しばしば「小数を」「ユーザの常識により期待される形で」「わりと多くの箇所に」表示する必要があるわけで、それに floatを用いるのは結構きびしい。
(rubyでは名前こそFloatであるものの実装は doubleであるらしいが、精度の高さは問題ではない)

銅線 計19.99999999kg 入荷済みとか画面に表示されたら普通困るよね。
かといって0.000000001とか(桁数適当。念のため)足してから文字列表現に直す、っていう処理を各所にばらまくという非生産的な仕事はなるべく作りたくない。
データベースからNUMERIC型のフィールドを読み込む時に floatを使うとデータベース側で設定した有効桁数より下の方には適当な値が入ってたりするし。

悲しいけどこれ、仕様なのよね。たまにバグだって騒ぐ輩が沸くみたいだけど(さらにバグfixだって言って意味不明な仕様変更をする開発者もいるみたいだけど)。

あるていどfloatの性質をわかっている者が実装をするなら対処できるかもしれないが、途中から実装を他人任せにする案件ではカオスの元凶としかいいようがないので、こういうところでは最初から BigDecimalを使っておきたい。

しかしながら、railsのリポジトリを眺めてみたところ trunkでは :decimal で BigDecimal型が使えるようになっているようだったので、とりあえず今だけ我慢すればいいかということで終了。

ラベル:

2007-09-26

Rails, ActiveRecordのメモ

$ ./script/generate model クラス名(名称単数形)

で、app/models/クラス名.rb の他に マイグレーションファイル(テーブルの作成や進化を記述するファイル)db/migrate/001_create_名称複数形.rb が作成される。これがcreate table文の元になる。ファイルを開き、self.up内で create_tableメソッドに渡すコードブロック(パラメータt)にカラム数分の t.columnを書く。

t.column :名前, :型, {オプションs}

型は :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean あたりが使える。

オプションは :limit=>カラム長, :default=>デフォルト値, :null=>null許容か否か, :precision=>数値の精度, :scale=>数値のスケール

(明示的に書かなければ)idというカラムがオートナンバーで勝手に付き、これが主キーになる。

書き終わったら

$ rake db:migrate

とすると、development環境のデータベースにテーブルが作成される。このテーブルを削除するには rake db:migrate VERSION=0 とすれば良い。

テーブルを変更する時、中身をドロップしたくない場合は ./script/generate migration modify_名称複数形 のように migrationを generateしてやると、バージョン番号のインクリメントされたマイグレーションファイルが作成されるので、self.upには create_tableの代わりに add_columnなどといったアップグレード差分を記述し、self.downには drop_tableのかわりに remove_columnなどのダウングレード差分を記述する。

マイグレーションファイルで使える主な記述は下記のとおり。

create_table :テーブル名, {オプション}
drop_table :テーブル名
rename_table :旧テーブル名, :新テーブル名
add_column :テーブル名, :カラム名, :型, {オプション}
remove_column :テーブル名, :カラム名
rename_column :テーブル名, :旧カラム名, :新カラム名
change_column :テーブル名, :カラム名, :型, {オプション}
add_index :テーブル名, :カラム名, {オプション}
remove_index :テーブル名, :カラム名

add_columnのオプションは :null=>false など
add_indexのオプションは :unique=>true など

Bookクラスを例に取ってActiveRecordで可能なオペレーションのいくつかをメモ。

・条件に該当する最初のレコード(Bookオブジェクト)を得る
Book.find(:first, :conditions=>["title=?", "ツァラトゥストラはこう言った"])

・レコードを作成する
Book.create(:title=>"FlexとRails") # id列には勝手に連番がつく

・条件に該当する全てのレコード(BookオブジェクトのArray)を指定のオーダーで得る
Book.find(:all, :conditions=>['price < 1000'], :order=>"issue_date DESC")

本番環境にテーブルを作成するには RAILS_ENV=productionを指定して db:migrateを呼び出す。

$ rake db:migrate RAILS_ENV=production

ラベル:

2007-09-23

RailsでWebサービスを作成し、FlexからSOAPで呼び出す(Rails編)

Railsには ActionWebServiceというものが用意されていて、SOAPやXML-RPCで呼び出せるいわゆる Webサービスを簡単に作ることができる。

ActionWebServiceで作成された Webサービスは、Flexに用意されている WebServiceクラスから簡単に利用することが出来る。この時用いられるプロトコルは SOAPとなる。

「SOAPの Sが Simpleの Sだというのは悪い冗談だ」と巷では評判だが、ことミドルウェアがよく整備されており自分がそれをただ使えば良いだけの立場でいる場合においてはさして問題ないと感じる。

FDSや RED5, S2Flex, RubyAMFといった AMFベースのミドルウェアでサービスを実装した場合、バイナリプロトコル故に少ないオーバーヘッドでのリモート呼び出しが可能な反面、それと組み合わせる RIAのプラットフォームは当然のことながら Flex(Flash)に限定されてしまうわけだが、.NETや Silverlightといった他のリッチUIプラットフォームや、サーバ間でのRPCなども視野に入れたいのであれば、(さしあたり Flexをターゲットとするにせよ)プラットフォーム非依存のプロトコルである SOAPを選択することも理に適っているだろう。

前口上は終了。というわけで、まず実際に Rubyで Webサービスを実装してみる。

ここでは、helloという名前の適当なWebサービスを作ることにする。雛形は script/generateで作れる。

$ ./script/generate web_service hello

で、app/apis/hello_api.rb と app/controller/hello_controller.rbが作成される(test/functional/hello_api_test.rbも)。

hello_api.rbの中に HelloApiなるクラスがあるので、所定の書式で helloサービスに持たせたいメソッドの定義を追加する。このクラスに記述された内容は Webサービスのインターフェイス定義として作用することになり、WSDLにも反映される。

api_method :メソッド名, :expects=>[引数定義], :returns=>[返値定義]

引数は{名称シンボル=>型シンボル}の形式で、カンマ区切りで複数持つことができる。
返値は型シンボルだけの記述でも良い。

型を示すシンボルとしては
:string, :int, :bool, :float, :time, :datetime, :date, :base64
が使える。そのほか、構造を持ったデータを引数や返値に使いたければこれらのシンボルの代わりに ActionWebService::Structや ActiveRecord::Baseを継承するクラスを指定することも出来る。
また、型指定子を []で囲むとArrayの意味になる。([:string]は StringのArray)
構造は入れ子に出来るため、階層構造を持ったデータの入出力も可能である。

# Helloという Webサービスのインターフェイスを表現するクラス
class HelloAPI < ActionWebService::API::Base
# nameという文字列引数を取り、文字列を返す say_hello メソッドの定義
# Railsのお節介により、外からは SayHelloという名前で呼び出されるようになる。
# (お節介を抑制するには inflect_names false とする)
api_method :say_hello, :expects=>[{:name=>:string}], :returns=>[:string]
end

次に実装を行う。生成された hello_controller.rbには普通に Railsを使うときと同様空の Controllerクラスが置かれているので、先の HelloApiクラスで定義したメソッドをこの中に実装する(直接コントローラクラスに実装を記述しない方法もある。必要なら Delegated dispatching, Layered dispatchingの情報を検索されたし)。

# Helloという Webサービスを実装するコントローラクラス
class HelloController < ApplicationController
# メソッドsay_helloの実装。
def say_hello(name)
# 名前を聞いて、こんにちは、○○さん。って言うだけ。
"Hello, " + name
end
end

Webサービスを実装したら Railsを起動してクライアントからの呼び出しに備える(Railsの最も簡単な起動方法:./script/server を実行すると 3000番ポートでHTTPの接続待ちになる)。
/コントローラ名/service.wsdl で WSDLへアクセス出来るので、SOAPクライアントから hello Webサービスを利用するにはまず http://my-rails-host:3000/hello/service.wsdl をロードさせれば良い。WSDLにはサービスのエンドポイントとなる URIや各メソッドの詳細など、実際のサービス呼び出しに必要な全ての情報が書かれている。

参考文書:http://manuals.rubyonrails.com/read/chapter/67

Flex編へ続く

ラベル:

2007-09-11

Railsで、rxmlテンプレートを使わずに XMLをレスポンスする

テンプレート app/views/コントローラ名/アクション名.rxml を使わずに XMLを返すアクションの例。
RIAのサーバとして Railsを使う場合、ロジックとUIを分離する必要がない(UIがない)ので、このようにテンプレートを使わずインプレースでレンダリングを行っても良いはずだ。
  def list
xml = Builder::XmlMarkup.new(:indent=>2)
render :xml => xml.results {
xml.item do
xml.id(1)
xml.name("Konata Izumi")
end
xml.item do
xml.id(2)
xml.name("Kagami Hiiragi")
end
}
end

上記アクションの実行結果は下記のとおり。Content-Typeは application/xml となる。
<results>
<item>
<id>1</id>
<name>Konata Izumi</name>
</item>
<item>
<id>2</id>
<name>Kagami Hiiragi</name>
</item>
</results>

ラベル:

Railsで、ActiveRecordは使わないけど database.ymlを使う

諸般の事情で ActiveRecordを使わず SQLでデータベースにアクセスする場合 DBIを使うことになると思うが、データベースの接続設定くらいは database.ymlを参照したい。

というわけで、下記のような Moduleを ApplicationControllerに Mix-inしてやることにした。
require 'yaml'
require 'dbi'

module WithDBI
@@dbconfig =
YAML::load(File.open("#{RAILS_ROOT}/config/database.yml"))[RAILS_ENV]
@@drivernames = {
"postgresql"=>"Pg",
"mysql"=>"Mysql",
"oci"=>"OCI8"
} # ActiveRecordのadapterと DBIのドライバ名を対応づけるマップ

def with_dbi
dbname = @@dbconfig["database"]
username = @@dbconfig["username"]
password = @@dbconfig["password"]
host = @@dbconfig["host"]
adapter = @@dbconfig["adapter"]
# encodingを処理する方法不明...多分DBIでは吸収されずDBMS依存

connstr = "DBI:" + @@drivernames[adapter] + ":" + dbname
if host != nil then
connstr += ":" + host
end

DBI.connect(connstr, username, password) {|dbh|
dbh['AutoCommit'] = false
dbh.transaction {|dbh|
yield dbh
}
}
end

end

アクションメソッドの中からはこんな風に使う
def list
with_dbi { |dbh| # DBI::DatabaseHandle
@results = dbh.select_all("select id,name from people")
# @resultsには DBI::Rowの Arrayが入る
}
end


DBI::DatabaseHandleについての詳しい情報が何故かネット上で見つけにくいので、ついでにメモしておく。

stmtは SQL文、bindvarsはバインド変数(可変長引数)。プレースホルダは ? 文字。

do( stmt, *bindvars )
SQLを実行し、処理された行数を返す。insertとかupdateに使う。

select_one( stmt, *bindvars )
SQLを実行し、結果セットの1行目を示す DBI::Rowを返す。結果が一行しか返らないことがわかっている select文向き。

select_all( stmt, *bindvars )
SQLを実行し、DBI::Rowの Arrayを返す。それほど多くない行数の結果を返す select文向き。

execute( stmt, *bindvars ) {|statement_handle| aBlock}
SQLを実行し、ハンドル(DBI::StatementHandle)を持ってコードブロックを実行する。多くの行を返す select文向き。

DBI::StatementHandleは each {|row| aBlock } で1行ずつrowをfetchしてコードブロックを実行。

参考文献: DBI Interface Specification Version 0.2.2 (Draft)

ラベル:

2007-09-10

Railsで、RIAからのCRUDファンクション呼び出しを処理するコントローラの雛形を生成する

Railsでは、データベースエンティティに対するCRUD操作を行うための最低限の Web UIを生成する仕組み(scaffold)があるが、RIAからコールするためにはレスポンスタイプが text/htmlでは困るし、scaffoldをそのまま適用するには対象となるテーブルが ActiveRecordの期待する構造や命名規約を持つことが要求されるため、レガシーデータを取り扱う場合には却って手間をかける事になりかねない。

ここでは、scaffoldでの全自動生成を諦め、せめてコントローラとビューの雛形だけでも機械生成してみることにする。

$ script/generate controller 名前 list show create update destroy

で、
http://railshost:3000/名前/list - 一覧
http://railshost:3000/名前/show - 詳細
http://railshost:3000/名前/create - 作成
http://railshost:3000/名前/update - 更新
http://railshost:3000/名前/destroy - 削除

の各アクションを持つコントローラ
app/controllers/名前_controller.rb
と、それぞれのビューとなる
app/views/名前/(list|show|create|update|destroy).rhtml
ファイルが生成される。(アクション名は scaffoldを参考にしている)

"名前" には、(Rails的な規約に従うならば)操作対象となるエンティティ名の複数形を用いる。

rhtmlファイルは rxml にリネームする(残念ながら、最初から rxmlを生成してくれるようなジェネレータオプションを見つけられなかった)。これによって、text/htmlのかわりに application/xmlの文書を呼び出し元へレスポンスすることができる。rxmlファイルはeRuby的な書式ではなく Builder::XmlMarkupを使用したちょっとDSLっぽいプログラムコードで記述する。

各アクションもビューも中身は空なので自分で書く必要があるが、通信相手が RIAの場合は UI要素を全く記述する必要がないのでいくぶんか楽だろう。

ラベル:

2007-09-06

Railsとマルチスレッド

Railsは Thread awareではないので、concurrentなrequest processingをしたければプロセスを複数起動するしかない。
ということが最近わかった。つまりcometのようなものを実現したければ、おびただしい数のプロセスを起動するか、かなりアクロバティックなことをして単一プロセスでリクエストの同時処理を実現しなければならない。

高速とうたわれる Ruby製 Webサーバである Mongrelでも、rails用に使うときは同時に複数のリクエストを処理できないので、rails込みの状態でベンチマークを取るとあまり良い結果にならないかもしれない。

ラベル:

2007-09-02

Railsと Oracle

大抵 Rails入門の記事は MySQLが対象になっているので Oracle用の使い方をメモしておく。

まず Oracle Clientが必要
Oracle (Instant) Clientを入れて SQL*Plusが動くような状態になったら、ruby-oci8 を入れる。Gentooなら emerge ruby-oci8 で良い。

ここでは personオブジェクトを格納するテーブル people を作り、それに対するCRUD操作を提供するWeb画面を作る例を示す。
(ちなみにクラス名は単数形、テーブル名は複数形。普段英語を使い慣れていない向きには「どっちが複数だっけ?」と悩むこともしばしばだが、Rails(ActiveRecord)の「規約」はそういうことになっている。bookクラスのオブジェクトを格納するテーブルは booksね)

なお Railsアプリケーションの雛形は railsコマンドで既に出来ており、そのディレクトリの中にいるものとする。

SQL*Plusでデータベースに接続し、テーブルを作る。

create table people (
id number(10) primary key,
name varchar(255)
);
create sequence people_seq;

「idと名の付く列はオートナンバーの適用されるキー列であり、順序から値が移入される。順序名はテーブル名に_seqを付けたもの」というActiveRecord(+Oracle)の「規約」のようである。この割り切りがCoCというやつか。
Oracleデータベースにオートナンバーの処理を行わせるにはトリガーの作成が必須だが、ActiveRecordは自分で順序からID列へ値を移入するため、この場合はデータベース側にトリガーを作るべきでない。

app/models/person.rbというファイルを下記の内容で作成。personは単数形、personは単数形・・・ぼそぼそ

class Person < ActiveRecord::Base
end

とりあえず、中身の記述無し。普通に Javaの考え方だと、エンティティクラスを作るときにはこのテーブルに一体どんな列が含まれているのかまで書いてやらなければならないのだが、ActiveRecordsでは実行時に勝手にDBへ問い合わせて内容を決定できるので、これでも動作する。これもDOAというのだろうか。

conf/database.yml というファイルにデータベースの設定を書き込む。この設定ファイルには「開発用DB(development)」「テスト用DB(test)」「本番用DB(production)」の設定をそれぞれ書くことが出来る。
繰り返しになるが世の中にある Railsの情報はほとんど MySQL向けに書かれているため、Oracleの場合は少し違うことを書かなければならない。

development:
adapter: oci
username: scott
password: tiger
host: mydbhost/XE

adapterが ociになること。database指定がない(OracleとMySQLの違いをわかっている人なら理由は自明だろう)こと。hostは TNS名もしくはホスト名/サービス名の形式を取ること。

上記の設定が済んだら、generate scaffold を実行し、personオブジェクト用のCRUD操作画面を自動生成する。

$ ruby script/generate scaffold person




これで peopleというコントローラが作成され(こっちは複数形)、Webからデータの一覧や詳細を見たり、作成したり、編集したり、削除したりできるようになった。一覧表示のURIは (railsアプリケーションのルート)/people/list になる。

あと、OracleのNLS_LANGが UTF8になっているなら、config/environment.rbに $KCODE = 'u' の1行を追加しておいたほうがいいと思う。

ラベル: ,