2007-09-30

サイトリニューアル

BiND for WebLifeというソフトを買ってみたので、サイトをリニューアルしてみた。
iD for WebLifeは面白いソフトだった。
これは、実用的なソフトだと思う。

新しい STBBS.NET オフィシャルサイトへ

2007-09-29

サーバアカウントについて

アカウントの期限が切れており、なおかつ下記のいずれかに該当する場合、アカウント整理の対象となっております。
・使用されていないと見られる場合
・期限切れから長期間経過している場合

復帰の必要がある場合は、メンバーページ へログインして状況をご確認下さい。

ラベル:

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-24

Flexと xsd:date型

Flexが Webサービスを呼び出すとき、xsd:date型を引数に要求されると Dateオブジェクトの UTC表現を元に生成した日付文字列をサーバへ送信する。

参考文献:Flex 2: Hotfix 2 Webservice serialization changes

これだとロンドンより東に住んでいる我々にとって困ることがひとつある。mx:DateChooserや mx:DateFieldでユーザに選択させた日付の1日前の日付がサーバへポストされてしまうのだ。FlexのDate型は日付+時刻+タイムゾーン型なのだが、DateChooserで取り扱う場合時刻部分を 00:00:00.000、タイムゾーンをローカルに固定して「日付型」がわりにしているためだろう。

ソレジャコマルヨチミィー

Webサービス側の入力データ型を xsi:dateTimeにしてやれば、Flexからは日付+時刻+タイムゾーンがセットでサーバへ送信されるのでサーバ側できちんとタイムゾーンを解釈してくれれば問題ないはずなのだが、データベースとの絡みで簡単にはそうしてもらえないこともあり(スキーマ側で求められているのはDATE型だったりするし、ローレベルのDBドライバがタイムゾーンを正しく処理できるとは限らないのだ!)如何ともしがたい。仕方ないので送信前にUTCとのオフセット分だけ Dateオブジェクトの値をずらしてやるというアドホックなソリューションでしのいでみた。

date = new Date(
dateChooser.selectedDate.time
- dateChooser.selectedDate.timezoneOffset * 60 * 1000
);

ひでーなこりゃ。

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


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

Railsで(別にRailsじゃなくてもいいんだけど)実装した Webサービスを呼び出すアプリケーションを Flexで作ってみる。

先の記事で作成した hello Webサービスは、名前を聞いてこんにちは○○さんというメッセージを返す SayHelloというメソッドを持っている。これに対する呼び出しを行う最小の Flexアプリケーションは下記の通り。

この程度なら mx:Scriptブロックを記述する必要もない。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout=
"absolute">
<mx:Label x="10" y="10" text="Webサービス(SOAP)呼び出し君"/>
<mx:Label x="10" y="36" text="お名前"/>
<mx:TextInput id="yourName" x="53" y="34"/>
<mx:Button x="221" y="34" label="送信"
click=
"helloService.SayHello.send()"/>
<mx:Text x="10" y="62" width="257" height="33"
text=
"{helloService.SayHello.lastResult}"/>
<mx:WebService id="helloService"
wsdl=
"http://my-rails-host:3000/hello/service.wsdl">
<mx:operation name="SayHello">
<mx:request>
<name>{yourName.text}</name>
</mx:request>
</mx:operation>
</mx:WebService>
</mx:Application>

mx:WebService (id:helloService)のインスタンス化が行われる際、自動的に指定のWSDL(http://my-rails-host:3000/hello/service.wsdl)がロードされ、Webサービスのエンドポイント(ひらたくいうとアクセス先URL)やメソッドの詳細といった情報が調査される。

ユーザーは自分の名前を TextInputフィールド(id:yourName)へ入力し、送信ボタンを押す。すると Flexのランタイムは入力された内容 (yourName.text で参照される) を name引数として用い、helloサービスの SayHelloメソッド( helloService.SayHello という名前で参照できる mx.rpc.soap.Operationオブジェクト )を SOAPでコールする(sendメソッド)。呼び出しは は非同期で行われるため、sendメソッドは即時に制御を戻す。

リクエストがサーバへ飛び、結果が返ってくるとそれは helloService.SayHello オブジェクトの lastResult プロパティに格納される。結果表示用の mx:Text オブジェクトは helloService.SayHello.lastResult を参照しているため、呼び出し結果が着信すると同時に文字列がそこへ表示されることになる。

lastResultはSOAP呼び出しの返値にふさわしい型のオブジェクトとなる。メソッドの返値が文字列(Rails側での :string) であれば String型だし、配列であれば Array型、構造体であれば Object型が用いられ動的プロパティで構造体の各メンバが表現される。

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編へ続く

ラベル:

WN-G54/R3と Nintendo DS

実家用にWN-G54/R3というアイ・オー・データのルータを6980円で買ってきた。
最初は Nintendo DSでちゃんと使えていたのに、ある時からDHCPがうまく出来なくなって DS側でエラーコード 52000が出るようになってしまった。ファームウェアを1.02から1.03に更新したタイミングが境目かもしれない。
仕方ないので DHCPを使わず色々手動で設定したら接続できた。

以下、メーカーのサイトから引用。
---
■Ver.1.02 → Ver.1.03(2007/09/11)
・IPアドレス自動取得の環境で、IPアドレスが取得できない場合があった件を修正しました。
---
あー。エンバグの香りがすごくするぞ。

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-08

Ruby-OCI8 on Intel Macは不可

Macで一通り Rubyの開発環境を整えようと思ったんだけど、Mac OS X版の Oracle ClientはPPC用しか提供されておらず、Intelバイナリの Rubyから Oracleを使うことが出来ない。
(当然だが x86とPPCを混在させてダイナミックリンクをすることが出来ないため)

どうしても使いたければ、PPC版 Rubyを用意して Rosettaで実行するしかないみたいだ。

/usr/bin/rubyは Universal Binaryなので、
$ /usr/libexec/oah/translate /usr/bin/ruby
とすれば ruby自体は Rosetta動作させることが出来るようだけど(?)、子プロセスの動作までには影響してくれないようで、Ruby-OCI8のビルドに失敗するのは変わらず。

【2008-5-6 追記】
http://www.oracle.com/technology/software/tech/oci/instantclient/htdocs/intel_macsoft.html
いつのまにか、Intel Mac用 Oracle Instant Clientが出ていた。これから試す。

2007-09-07

ActiveMQをすぐに使う

ActiveMQは、Apacheの JMSサーバ。

ダウンロードしたバイナリのアーカイブを展開して、bin/activemq スクリプトを起動すると
TCPポート61616番でメッセージブローカが待ち受けを開始する。

JMSのクライアントは apache-activemq-*.jarをクラスパスに追加し、ActiveMQConnectionFactory (implements javax.jms.ConnectionFactory) を使ってメッセージブローカへの接続を得る(brokerURLプロパティには接続先を示す文字列をセットすること)。Springなら下記のように。

<bean id="connectionFactory"
class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

後は Queueなり Topicなり好きな方法でメッセージを交換すれば良い。

2007-09-06

Railsとマルチスレッド

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

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

ラベル:

2007-09-04

Flexの技術デモ

mixiにも書いたけれど。

http://chat.stbbs.net

これを作りながら確認したこと
・Flexでの非同期通信とUI周りをいろいろ
・Rubyの記述力を再確認..

2007-09-03

Xen domU on Linux 2.6.23

Linux 2.6.23には Xenの Paravirtual環境に対応するためのコードがマージされる。つまり2.6.23以降は domUで常に最新のカーネルが使えるようになるということだ。
実際 9/1に公開されたばかりの Linux 2.6.23-rc5にはそれらしきものが入っているのを確認したのだが、i386用のみで x86_64用はまだのようだった。
Xen-develメーリングリストでも8月上旬頃に「x86_64まだ?」「頑張ってる最中だよ」というやりとりがあった(このころはrc2)

ラベル:

spawn-fcgiで標準入出力用FastCGIサービスを独立プロセス化する

Rails含め、多くの場合 FastCGIはCGIの延長線上にある使われ方をする。つまりWebサーバによってforkされ、リダイレクトされた標準入出力を使ってサービスを提供する。この時、プロセスのライフサイクルは Webサーバによって管理される。ただのCGIと違うところは、いちどforkされたプロセスが再利用されることである。これによって fork/execのオーバーヘッドが除かれ、結果として "Fast"が実現する。

一般的な FastCGIに対する認識はここまでに尽きることが多い。

だが、FastCGIはWebサーバと切り離され独立したプロセスとして立ち上げることも出来る。その場合、Webサーバとの通信には TCP又は UNIX Domain Socket(名前付きパイプ)が使われる。この通信のために FastCGIプロトコルというれっきとした通信プロトコルが存在する。
(実際には、WebサーバからforkされるタイプのFastCGIアプリケーションも内部的にはこのプロトコルでWebサーバと通信しているのだが)

独立したプロセスとして構築された FastCGIサービスには次のようなメリットがある。
・任意のユーザ権限によるサービスの提供が可能
・リクエストの同時処理をマルチスレッドで実現できる
・Webサーバと別のホストでサービスを動作させることでリソースの分散が可能(TCPの場合のみ)

しかし、このタイプのFastCGIアプリケーションを開発するには CGIの延長線上よりは若干ローレベルな APIを駆使する必要があり、既存の(標準入出力をベースとした)コードを対応させるのは難しい。

いつものごとく前置きが長くなったが、spawn-fcgiは標準入出力ベースで作成された FastCGIアプリケーションを独立プロセス型に変換するラッパーである。spawn-fcgiは lighttpdのオマケとして収録されている。

$ spawn-fcgi -f myfcgiapp.fcgi -p 8000

上記コマンドで、(Apacheなりlighttpdなりから forkされる前提で作られた)mycfgiapp.fcgiを TCPポート8000番で待ち受けるデーモンとして起動させることが出来る。

他の主なオプションは下記のとおり。
-s <path> ...UNIXドメインソケットの指定
-P <path> ...デーモン化したfastcgiプロセスのPIDを記録するファイル
-n ... forkしない(プロセスをバックグラウンド化しない)
-c <dir> ...chroot環境で実行(root権限でspawn-fcgiを実行する場合のみ)
-u <user> ...指定したユーザの権限でサービスを実行(同上)
-g <group> ...指定したグループの権限でサービスを実行(同上)

Railsの dispatcher.fcgiも spawn-fcgi経由で独立プロセス化できる。そのためのスクリプトscript/process/spawnerが用意されている。

注意したいのは、spawn-fcgiで独立プロセス化された FastCGIアプリケーションはシングルプロセスシングルスレッドなので、それ単体ではリクエストの同時処理ができないことだ。Railsのspawnerは spawn-fcgiを別々のポート番号で複数起動することで多重化入出力に対応しようとするが、Webサーバ側をそれに対応させるためには適切にロードバランス設定をしてやる必要がある。lighttpdならきっと出来るんだろうし、Apache2.2でもmod_proxy_fcgiとProxyロードバランス機能の組み合わせで出来るだろう。たくさん同時実行させようとすると、多分すごく面倒だろうけど。

いずれにせよ、最もスループットの高いFastCGIアプリケーションを構築するには FastCGIのローレベルなAPIを使ってマルチスレッドでリクエストを処理するようなプラットフォームが必要であり、spawn-fcgiはそれに対応できるまでの代替措置でしかない。Railsに関しては単独でマルチスレッドの FastCGIサービスとして動作出来るようになるのが理想だが、これは ruby-fcgiがローレベルなFastCGI APIを Pure Rubyでしか現状では提供していないことや、もともと Railsがマルチスレッド対応でないことなどから実現するとしても随分先になりそうだ。

2007-09-02

OpenJPAは永続クラスをエンハンスしなくても動作する

OpenJPA 0.9.7までは、エンティティクラスに対して-javaagent の指定によるランタイムエンハンス又はantタスクやコマンドラインによるビルドタイムエンハンスをしないと永続APIが使えなかったが、OpenJPA 1.0.0からはエンハンスをしなくても大丈夫になった。
但し、よく読んでいないが制約はあるらしい。また、エンハンスなしで OpenJPAを使う場合、永続クラスには引数無しのコンストラクタが必要で、それがないとエラーになる。

ラベル:

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行を追加しておいたほうがいいと思う。

ラベル: ,

Gentooに Oracle Clientをインストールする

Oracleアプリケーションを Javaで開発する分には、大抵 Type4(thin)ドライバで何とかなるのでネイティブの Oracle Clientを入れる必要がないのだが、ネイティブアプリケーションや Railsから Oracleにアクセスしたい場合どうしても Oracle Clientは必要となる。

Gentooの Portageには Oracle Instant Client があるので、emergeですぐにインストール出来る・・・といいたいところだが、プロプライエタリなソフトウェアなのでバイナリを Oracleのサイトからダウンロードして /usr/portage/distfiles に配置してやる必要がある。

# emerge oracle-instantclient-sqlplus

それらのファイルを配置せず上記 emergeコマンドを実行すると、入手先やファイル名などを指示するメッセージが出力され emergeが一度停止するので、言われたとおりのファイルを入手・配置して再度 emergeをする。

ORACLE_HOMEの設定は自動的に行われるので、emergeが済んだらすぐに SQL*Plusを使ってテストが出来る。
前にも書いたことがあるが、最近の Oracle Clientは tnsnames.oraにTNS名を定義しなくても直接 DBサーバへ接続できるのでそのあたりの手間は随分省けるようになった。
下記は mydbhost 上で動作している Oracle Expressに対して SQL*Plusで接続を行う例。
(なお Oracle Expressのインスタンスはサービス名が常に xe となっている)

$ sqlplus scott/tiger@mydbhost/xe

ただ、NLS_LANGの設定は自動的には行われないので、必要なら /etc/env.d/50oracle-instantclient-basic ファイルに
NLS_LANG=japanese_japan.utf8
などの設定を追加してやること。

ラベル: ,

2007-09-01

すぐに始める Spring + OpenJPA 1.0.0

クラスパスに追加したもの

commons-collections-3.2.jar
commons-lang-2.2.jar
commons-logging-1.0.4.jar
openjpa-1.0.0.jar
geronimo-jpa_3.0_spec-1.0.jar
geronimo-jta_1.1_spec-1.1.jar
serp-1.13.1.jar

他にも要るかもしれないが、なんにせよ OpenJPAの配布物に含まれている。
あれ、0.9.7の時はgeronimo-j2ee-connectorも必要だったような気が・・・まあいいか。

Java VMに与えたオプション

-javaagent:path/to/openjpa-1.0.0.jar

Eclipseの場合は Run.. から Arguments → VM arguments: に書く。

このオプションを与えることによって、永続クラスをOpenJPA用にエンハンスする処理が自動で行われる。
(Hibernate-JPAの時は何もしなくても勝手にやってくれていた気がする)
他にも、classファイルを静的にエンハンスする方法(ビルドタイムエンハンス)などがある。

Springの Bean定義ファイルに追加したもの

(dataSourceの定義は既にあるものとする)

<bean id="entityManagerFactory"
class=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"/>
</property>
</bean>

(0.9.7の時にはつけていたかもしれない)loadTimeWeaverプロパティは、1.0.0ではつけないこと。さもないとランタイムエンハンスに失敗する?

クラスパス以下に作成したファイル

META-INF/persistence.xml

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="適当な名前" transaction-type="RESOURCE_LOCAL"/>
</persistence>

JPAの入門記事などでは、persistence.xml内にデータソースなど様々な設定を書き込むように解説されているのが普通だが、Spring LocalContainerEntityManagerFactoryBeanを使って EntityManagerFactoryを定義する場合にはそうする必要がない。
但し、永続クラスのリストをpersistence-unit要素内に持たないと OpenJPAのランタイムエンハンサが全てのクラスをエンハンス対象かどうか調べなければならないためクラスのロードがやたらと遅くなるので、上記の例では省略しているものの必要な永続クラスの数分だけ class 要素を並べるのだけはやっておいた方がよい。

JPAを使用するコードの例

EntityManager em = entityManagerFactory.createEntityManager();
// 主キー指定でDBから行をフェッチし、MyEntityのインスタンスとして得る
MyEntity e = em.find(MyEntity.class, 123);
// ...など、いろいろ処理
em.close(); // 使い終わったら片付ける
MyEntityはDBのテーブルに対応づけられるべくアノテーション漬けにされた永続クラス。

その他

永続クラスのキーになるクラスは、下記の条件を満たしていないとOpenJPAでは使えないことに注意。

equals()が実装されている
hashCode()が実装されている

永続クラス本体側では、キーとなるフィールドに@Idアノテーションが付いていること。

RDBMSの種類は設定で教えてあげなくてもドライバの種類を見て勝手に判断するようだ(少なくとも PostgreSQLではそうだった)

ラベル: ,