integral - new o/r mapper for common lisp
DESCRIPTION
A short introduction about a new O/R Mapper for Common Lisp named "Integral" and it's inside.TRANSCRIPT
New O/R Mapper for Common Lisp
株式会社はてな id:nitro_idiot
2014/01/23 Lisp Meet Up presented by Shibuya.lisp #13
自己紹介•深町英太郎
• id:nitro_idiot
•株式会社はてな勤務
• Webアプリケーションエンジニア
•ブログ「八発白中」 blog.8arrow.org
作ったもの
• Clack
• Caveman
• ningle
• CL-TEST-MORE
• CL-DBI
• CL-Project
• Shelly
• Lesque
• Quickdocs
• Integral
See 8arrow.org
O/R Mapper
• O/Rマッパー知ってますか
• DBのデータをオブジェクトとして扱う
• ObjectStore知ってますか
• ↑と同じだけど永続化の概念がある
O/R Mapper
• O/Rマッパー知ってますか
• Postmodern, CLSQL
• ObjectStore知ってますか
• Elephant, AllegroCache
O/R Mapper
• O/Rマッパー知ってますか
• Postmodern, CLSQL
• ObjectStore知ってますか
• Elephant, AllegroCache
例: Postmodern(defclass country ()! ((name :col-type string :initarg :name! :reader country-name)! (inhabitants :col-type integer :initarg :inhabitants! :accessor country-inhabitants)! (sovereign :col-type (or db-null string) :initarg :sovereign! :accessor country-sovereign))! (:metaclass dao-class)! (:keys name))
例: Postmodern(defclass country ()! ((name :col-type string :initarg :name! :reader country-name)! (inhabitants :col-type integer :initarg :inhabitants! :accessor country-inhabitants)! (sovereign :col-type (or db-null string) :initarg :sovereign! :accessor country-sovereign))! (:metaclass dao-class)! (:keys name))
CREATE TABLE country (! name TEXT NOT NULL,! inhabitants INTEGER NOT NULL,! sovereign TEXT,! PRIMARY KEY (name))
例: Integral(defclass country ()! ((name :col-type string :initarg :name! :primary-key t! :not-null t! :reader country-name)! (inhabitants :col-type integer :initarg :inhabitants! :not-null t! :accessor country-inhabitants)! (sovereign :col-type string :initarg :sovereign! :accessor country-sovereign))! (:metaclass <dao-table-class>))
Integralでもほとんど同じ。 :metaclassを指定する
メタクラス?
•クラスのクラス
•クラス定義時に<dao-table-class>のインスタンスが作成されてcountryクラスになる
•指定しないとstandard-classのクラスになる
メタクラス?
• (Integralでは) 何に使っているか
•自動的に <dao-class> を継承させる
•スロットのクラスを指定できる
•通常はstandard-direct-slot-definition
•その他、定義・再定義時の処理など
メタクラス?
•まあ、変なクラスを作れるってことです。
例: Postmodern
•話はPostmodernにもどる
例: Postmodern
• defclass に (:metaclass dao-class) をつける
•カラムの型を指定できる
• PRIMARY KEYを指定できる
一見、良さそうに見える
既存のORMの問題
•こんなスキーマを開発初期から定義できるわけねーだろーが!!
(defclass country ()! ((name :col-type string :initarg :name! :reader country-name)! (inhabitants :col-type integer :initarg :inhabitants! :accessor country-inhabitants)! (sovereign :col-type (or db-null string) :initarg :sovereign! :accessor country-sovereign))! (:metaclass dao-class)! (:keys name))
既存のORMの問題
•最初にCREATE TABLE文を流してしまうと、クラス定義を変更したいときに困る
•カラム追加したくなったら?
•カラムのデータ型変えたくなったら?
• Postmodernではテーブルの再作成しかない
Integral
• New O/Rマッパー
• Postmodernの持つ問題の解決
Integral
•クラス定義を変更したらDBスキーマにも変更を反映する
= マイグレーション
マイグレーション
(defclass user ()! ((name :col-type text! :initarg :name))! (:metaclass <dao-table-class>))
マイグレーション
(defclass user ()! ((name :col-type text! :initarg :name)! (profile :col-type text! :initarg :profile))! (:metaclass <dao-table-class>))
マイグレーション
(migrate-table 'user)!;-> ALTER TABLE `user` ADD COLUMN `profile` TEXT AFTER `name`;!;=> NIL
(defclass user ()! ((name :col-type text! :initarg :name)! (profile :col-type text! :initarg :profile))! (:metaclass <dao-table-class>))
マイグレーション
(defclass user ()! ((id :col-type serial! :primary-key t)! (name :col-type (varchar 64)! :initarg :name)! (profile :col-type text! :initarg :profile))! (:metaclass <dao-table-class>))
(migrate-table 'user)!;-> ALTER TABLE `user` DROP COLUMN `%oid`;!; ALTER TABLE `user` MODIFY COLUMN `name` VARCHAR(64);!; ALTER TABLE `user` ADD COLUMN `id` SERIAL NOT NULL PRIMARY KEY FIRST;!;=> NIL
オートマイグレーションモード
• *auto-migration-mode*をTにすると発動
•クラスを再定義するたびにmigrate-table
•開発時に超便利
マイグレーションの実装
•泥臭い
•普通にDBスキーマ引いてきてスロットと比較しているだけ
• DBの種類を見て、ValidなSQLを吐く
• ALTER TABLEの実行順序も考慮
オートマイグレーションモードの実装
• initialize-instance と reinitialize-instance の :after で *auto-migration-mode* の値を見て T なら migrate-table 走らせるだけ
Integral
•クラス定義 → DBスキーマ の追随の話をしましたね。
•既にあるテーブルと一緒に使うにはどうしたらいいの???
Integral
• DBスキーマ → クラス定義 の追随もできるよ
:generate-slots
(defclass user () ()! (:metaclass <dao-table-class>)! (:generate-slots t))
!
!
DBからスキーマ定義を取ってきてスロットをつっこむ
:generate-slots
(defclass user () ()! (:metaclass <dao-table-class>)! (:generate-slots t))
!
!
DBからスキーマ定義を取ってきてスロットをつっこむ
(make-instance 'user :name "Eitarow Fukamachi")!;=> #<USER %oid: <unbound>>
:generate-slots
(defclass user () ()! (:metaclass <dao-table-class>)! (:generate-slots t))
!
!
DBからスキーマ定義を取ってきてスロットをつっこむ
class User < ActiveRecord::Base!end
:generate-slots
(defclass user () ()! (:metaclass <dao-table-class>)! (:generate-slots t))
!
!
DBからスキーマ定義を取ってきてスロットをつっこむ
class User < ActiveRecord::Base!end
なんか似てますね
:generate-slots の実装
•定義したときはスロットは空
•最初にmake-instanceする直前にDBスキーマを取ってきてスロットをつっこむ
•定義時にやらない理由は、定義時にDB接続がある保証が無いから
:generate-slots の実装
•既存のクラスにスロットを追加するのは意外と面倒
• c2mop:ensure-class-using-class を使って上書きする
•このときスロット名が同じものが既に定義されてると死ぬのでちゃんとマージする
CRUD
• Create: (save-dao (make-instance ‘user :name “Eitarow”))
• Read: (select-dao ‘user (where (:= :name “Eitarow”)))
• Update: (save-dao user-obj)
• Delete: (delete-dao user-obj)
CRUD
• update-daoやdelete-daoのとき実行されるSQLはUPDATE文とDELETE文
• WHERE句が一意である必要がある
• (でないと関係ない列まで変更する)
•通常はPRIMARY KEYを使う
CRUD
• Integralでは、PRIMARY KEYの定義が無いときは自動で%oidというPRIMARY KEYを付与
•後でPRIMARY KEYが定義されるとマイグレーションでDROP COLUMNされるから別に問題ないよね
暗黙の継承<dao-table>
• select-dao, save-dao, delete-dao は <dao-table> のメソッドとして定義されている
• Postmodernでは定義クラスが単一のクラスを継承しない (metaclassのみ)
•テーブルクラスに共通のメソッドを定義できない
共通のクラスを継承する利点
•たとえばRailsのScaffoldみたいな機能
•テーブルクラスからHTMLのformを吐くみたいな機能が作れる
•他バリデーション機能とか
Postmodern vs Integral
Postmodern Integral
対応DB PostgreSQL MySQL, PostgreSQL, SQLite3
マイグレーション なし あり
自動PK なし あり
スロット自動生成
なし あり