#はじめに
ふと思い立って SQLite3 と ORM で RDBMS に入門してみようかと思い、Pharo + Glorp で遊んでみました。そのとき、いくつかうまくいかなかったところがあったのでメモを残しておきます。Glorp は @mumez さんの Seaside へ GO!! の「3.1 Glorp の特徴」の記述が簡潔でわかりやすかったです。
念のため、手元の環境は Windows 8.1 です。
#チュートリアル
これ、PDF版もあるのですが、なぜか内容の一部が省かれているので、ここに示したリンク先の HTML 版がよさそうです。
sqlite3.dll のインストール
sqlite.org のダウンロードページ から 32 bit 版の sqlite3.dll をもらってきて、Pharo.exe と同じフォルダに置きます。
Pharo を起動し、デスクトップクリック → Save as... で Glorp チュートリアル用に名前を付けてイメージを保存しておきます。
Glorp と SQLite3用ドライバーのインストール(58.3. Glorp Packages~58.4. Glorp with UDBC / SQLite3)
チュートリアル「58.4. Glorp with UDBC / SQLite3」の記述に従って Playground 等で以下の式を順次 Do it(ctrl + d) で評価して、ドライバーもインストールします。(念のため、Gofer it からピリオドまでがひとつの式です)
Metacello new
smalltalkhubUser: 'DBXTalk' project: 'Garage';
configuration: 'GarageGlorp';
version: #stable;
load.
Gofer it
smalltalkhubUser: 'TorstenBergmann' project: 'UDBC';
configuration;
load.
(Smalltalk at: #ConfigurationOfUDBC) loadBleedingEdge.
Gofer it
smalltalkhubUser: 'DBXTalk' project: 'Garage';
configurationOf: 'GarageGlorp';
load.
#ConfigurationOfGarageGlorp asClass project stableVersion load.
Gofer it
smalltalkhubUser: 'DBXTalk' project: 'Glorp';
package: 'Glorp-SQLite3';
load.
GlorpSQLite3CIConfiguration new configureSqlite3.
GlorpDemoTablePopulatorResource invalidateSetup.
#ドライバーの動作確認(60.2. Database Accessor~60.3. The Login)
ちょっと前後しますが Person クラス等を作り込む前に、先にドライバーの動作を確認しておきましょう。
login := Login new
database: UDBCSQLite3Platform new;
host: '';
port: '';
databaseName: 'glorpbook.db'.
accessor := DatabaseAccessor forLogin: login.
accessor login.
(accessor basicExecuteSQLString: 'SELECT 3+4') rows first asArray first.
accessor login が何事もなく、最後の式の Print it (ctrl + p) で評価して 7 を返してくれればOKです。ここらへんでいったん、デスクトップクリック → Save でイメージ(環境)を保存しておくとよいと思います。
#シンプル版 Person クラスの定義(59. Person: Our First Example Class)
デスクトップクリック → System Browser でクラスブラウザを開き、下のコードペインに下式をペースト後 Accept (ctrl + s) してコンパイルします。パッケージ、タグなどの作成はクラスブラウザが package: 'Glorp-Book' から判断して勝手にやってくれるので不要です。
Object subclass: #Person
instanceVariableNames: 'id firstName lastName birthDate'
classVariableNames: ''
package: 'Glorp-Book'
id 以外のアクセッサーの定義は、コピペが面倒なので、クラス一覧ペインの Person の右クリック → Refactoring... → Class Refactoring ... → Generate Accessors ... から Person>>id と Person>>id: のチェックを外してから Accept で自動的に作成してもらうとよいです。なおこのアクセッサーの自動定義を含め初回のメソッド定義時にユーザーの名前を求められるので、適宜登録しておきます。
残りの initialize だけ追加で定義してやります。次の式をシステムブラウザのコードペイン(下の枠内)や Playground などにペースト後選択し Do it (ctrl + d) で評価してやれば、面倒なプロトコル設定も込みで自動的に設定できます。
Person compile: 'initialize
super initialize.
birthDate := ''1/1/1970'' asDate' classified: 'initialization'
チュートリアルからのコピペでメソッドを定義する場合は、Person >> までは不要です。また、ペースト&アクセプトする前に、messageSelectorAndArgumentNames とコードペインに表示されていることを確認してください。このメソッドテンプレートは、プロトコル一覧ペイン内の no messages もしくは -- all -- などをクリックするか、あるいはセレクター(メソッド名)一覧ペイン内の適当なメソッド(あれば)をクリックで選択後、再びクリックして解除すれば現われます。
コピペ元のコードのインデントなどが気になる人は、いったんペースト → Accept (ctrl + s)でコンパイル後、コードペインの右クリック → Format でフォーマットを整えてから改めて Accept (ctrl + s) しなおすとよいでしょう。
#デスクリプターシステムの定義(60. Defining a DescriptorSystem)
クラスはクラス一覧ペインをクリックしてから、メソッドは GlorpBookDescriptorSystem >> よりあとを(messageSelectorAndArgumentNames を呼び出した)コードペインにペースト後、Accept (ctr + s) してコンパイルます。
#セッションの開始とテーブルの作成(60.4. Session、60.7. Creating Tables)
session := GlorpBookDescriptorSystem sessionForLogin: login.
session accessor login.
session login ではなく session accessor login とするのがミソのようです。
あとはテーブルの作成のため、次の式を Do it (ctrl + d) で評価します。
session createTables
※ テーブルなどのデータベースの状態は、Firefox の SQL Manager などのツールで手軽に確認できます。
以降はサンプルの式を、値を返すものは Inspect it (ctrl + i)、データ操作だけのものは Do it (ctrl + d) して、適宜 SQL Manager などで結果を確認しながら の手前まで進めてゆけます。
#サンプルの拡張(62. Extending our Basic Example~62.3. Mapping Declarations)
本編ではここから Person を作り込んでいくのですが、既存の Person とは細かいところがいろいろ変わっていて面倒なので、いっそ思い切って既存の Person クラスは削除(右クリック → Remove...)してしまい、改めて再定義するのがよさそうです。
その Person の新しい定義ですが、例で使用しない tags というインスタンス変数がある一方で、必要な email が欠けているので次のような定義に変えておかないとあとでエラーになります。
GlorpBookObject subclass: #Person
instanceVariableNames: 'firstName lastName birthDate addresses invoices email'
classVariableNames: ''
package: 'Glorp-Book'
また、GlorpBookDescriptorSystem>>#tableForINVOICEITEM: の定義も記述のままだと動かないので、次のように変更します。以下、プロトコルは面倒なので指定せずに進めます。デフォの as yet unclassified にカテゴライズされますが、余力があれば適宜、initializing、accessing、tables、descriptors、class models などといったプロトコルを作ってメソッドを振り分けておくと、あとでコードを読むときに便利です。
GlorpBookDescriptorSystem >> tableForINVOICEITEM: aTable
| invoiceField |
(aTable createFieldNamed: 'id' type: platform serial) bePrimaryKey.
invoiceField := aTable createFieldNamed: 'invoice_id' type: platform integer.
aTable createFieldNamed: 'description' type: (platform varchar: 150).
aTable createFieldNamed: 'price' type: platform integer.
aTable createFieldNamed: 'position' type: platform integer.
aTable
addForeignKeyFrom: invoiceField
to: ((self tableNamed: 'INVOICE') fieldNamed: 'id').
Person 他、各クラスの定義と同時に右クリック → Refactoring... → Class Refactoring ... → Generate Accessors ... でアクセッサーの自動生成もお忘れなく。
#サンプルデータ(62.4. Sample data)
サンプルデータを追加したりする前にテーブルを再編成しないといけないのですが、すでに glorpbook.db に作ったテーブルは破棄して作り直しておきましょう。
session dropTalbes; createTable
※ session recreateTables という便利なメソッドがありました。^^;
なお、サンプルスクリプトの途中に出てくるメソッド定義のうち、
InvoiceItem class >> description: aString price: aNumber
^self new
description: aString;
price: aNumber;
yourself
は InvoideItem クラスのメタクラスへのメソッド定義(クラスメソッドの定義)を意味します。Class スイッチを押してクラスサイド(メタクラスのブラウズ)にモードを切り替えてからペースト&アクセプトしてください。当該クラスメソッドの定義が済んだら、Class スイッチを再び押して、インスタンスサイド(クラスのブラウズ)に戻しておくのもお忘れなく。
トランスクリプトに出力するスクリプトは、トランスクリプトを自動で開かないので、デスクトップクリック → Tools → Transcript か、ctrl + o → (続けて) ctrl + t で呼び出しておくか、スクリプトの冒頭か最後に Transcript open. を追加して評価します。
(session read: Invoice) do: [:each |
Transcript
show: each person firstName, ' ', each person lastName;
show: ': ', each totalPrice printString;
cr.
].
Transcript open
#クエリーオブジェクト(62.5. The Query object~62.6. Attributes of queries)
最初の例が間違っていて面食らいますが、次のようにすれば問題ありません。
(SimpleQuery read: Invoice) executeIn: session
同じ動作をする
session read: Invoice
と紛らわしいですね。
63. Advanced topics 以降はまだ読み進めていませんので、おいおい試してみてなにかひっかかるポイントがあれば、そのときにまたあらためて。