継承関係の中でのイベントの扱い
GORM は、オブジェクトの生成・更新・削除・読込み(ロード)に関するイベント処理をドメインクラスに適切なクロージャを記述することで登録する事ができます。但しそれぞれについて直前・直後の両方をもれなく扱える分けではなく、次の表の通りです。
イベント | 記述するクロージャ |
---|---|
新規にオブジェクトを DB に保存する直前のイベント | beforeInsert |
DBに存在するオブジェクトを更新する直前のイベント | beforeUpdate |
DBに存在するオブジェクトを削除する直前のイベント | beforeDelete |
DBに存在するオブジェクトを読込んだ直後のイベント | onLoad |
※ Hibernate Events Pluginを使うと前後もれなく扱えるようになります。
記述例は次のようになります。
Grailsのリファレンスから引用:
class Person { Date dateCreated Date lastUpdated def beforeInsert = { dateCreated = new Date() } def beforeUpdate = { lastUpdated = new Date() } }
この例は、Person クラスのオブジェクトに対して、次の二つの処理を行っています。
・ 新規に生成したオブジェクトを DB に保存する直前に dateCreated という Date 型の属性にその処理時点での Date 値を設定する
・ オブジェクトを更新する直前に lastUpdated という Date 型の属性にその処理時点での Date 値を設定する(オブジェクトの更新が行われる度に Date 値の更新を行っている)
ここで、次のような二つのドメインクラスが存在した場合を考えてみます。
class A { Date dateCreated } class B extends A { Date lastUpdated }
クラスBは、クラスAを継承しています。
クラスAは、生成された時点での Date 値のみを属性として扱うクラスです。
クラスBは、生成と更新、両時点での Date 値を属性として扱うクラスです。
これらの属性をイベント処理で扱う場合、次のように記述する方法を思いつかれるかもしれません。
class A { Date dateCreated Date lastUpdated def beforeInsert = { dateCreated = new Date() } } class B extends A { Date lastUpdated def beforeUpdate = { lastUpdated = new Date() } }
このコードは上手くイベント処理が出来ます。
少し発展させてみます。
クラスAとクラスBが、次のようなドメインクラスであったとします。
class A { Date dateCreated Date lastUpdated } class B extends A { String ipAddress }
クラスAは、生成と更新、両時点での Date 値を属性として扱うクラスです。
クラスBは、生成と更新、両時点での利用者のIPアドレスを属性として扱うクラスです。
これらの属性をイベント処理で扱う場合、次のコードではクラスBは正常にdateCreated と lastUpdatedのDate 値が設定されません。
※ RequestUtils.getIp() は、利用者のIPアドレスを取得できるものとします。
class A { Date dateCreated Date lastUpdated def beforeInsert = { dateCreated = new Date() } def beforeUpdate = { lastUpdated = new Date() } } class B extends A { String ipAddress def beforeInsert = { ipAddress = RequestUtils.getIp() } def beforeUpdate = { ipAddress = RequestUtils.getIp() } }
DBを覗いてみると、クラスBの dateCreated と lastUpdated には Date 値ではなく null 値が設定されます。
こうなってしまうのは、サブクラスでスーパークラスと同じ名前のクロージャを記述した場合、サブクラス側の処理においてスーパークラスのクロージャは呼び出されないからです。
これをどのように解決するか考えた結果、次のような方法を思いつきました。
class A { Date dateCreated Date lastUpdated def beforeInsert = { onBeforeInsert(delegate) } def beforeUpdate = { onBeforeUpdate(delegate) } protected def onBeforeInsert(obj) { obj.dateCreated = new Date() } protected def onBeforeUpdate(obj) { obj.lastUpdated = new Date() } } class B extends A { String ipAddress protected def onBeforeInsert(obj) { super.onBeforeInsert(obj) obj.ipAddress = RequestUtils.getIp() } protected def onBeforeUpdate(obj) { super.onBeforeUpdate(obj) obj.ipAddress = RequestUtils.getIp() } }
この方法の場合、クラスBはクラスAのクロージャがそのまま呼び出されます。
そして、クラスBのonBeforeInsert(obj) が呼び出されるとクラスAの onBeforeInsert(obj) が呼び出され、その後自身が持つコードが処理されます。
このコードは、上手く動きます。
SpaceCardの継承関係を持ったドメインクラスはこの方法で記述しています。
もっとスマートな方法があれば良いのですが。