職場実習19日目

今日も引き続きGrails作業

来週の月曜日にレビューを行う予定なので、それまでにサイトの外観までサクッと作る。

カテゴリーをツリー化するためのドメインクラスのツリー構造

ドメインクラスでやりたい関連付け

  • 作品ドメイン
    • 上位カテゴリー(シリーズ、ジャンル)を1つまで選べるようにする(その際、上位カテゴリーをひとまとめにして扱う必要がある)
    • 下位カテゴリーを1つも持たないようにする
  • シリーズドメイン
    • 上位カテゴリー(ジャンル)を1つまで選べるようにする
    • 下位カテゴリー(作品)を0〜複数個持つ事ができるようにする
  • ジャンルドメイン
    • 上位カテゴリーを1つも持たないようにする
    • 下位カテゴリー(シリーズ、作品)を0〜複数個持つ事ができるようにする(その際、ひとまとめにして扱う必要は無い)
  • 作成者ドメイン
    • 他のアイテム(ジャンル、シリーズ、作品)は必ず1人の作成者に所有されなければならない
    • 他のアイテム全ての中から0〜複数個持つ事ができるようにする(その際、ひとまとめにして扱う必要は無い)

これらの関連付けは、「アイテム」ドメインを作り、そのサブクラスとして「作品」と「カテゴリー」ドメインを作り、「カテゴリー」のサブクラスとして「シリーズ」と「ジャンル」を作ると実現できる。

ドメインのツリー構造図
[アイテム](*..1)[作成者]
  ├─────┐
[作品](*..1)[カテゴリー]
          ├───────┐
        [シリーズ](*..1)[ジャンル]

実際のクラスは以下のようになる。

// アイテムドメイン(ssbbs.Item)
package ssbbs

class Item {
    static mapping = {
        id generator:'uuid.hex', 
        params:[type:'string']
    }
    String id
    String title
    def belongsTo = [author:Author]
    String text
    Date dateCreated
    Date lastUpdated
    
    static constraints = {
        title (nullable:false, blank:false)
        author (nullable:false)
        dateCreated (display:false)
        lastUpdated (display:false)
    }
    
    String toString() {
    	return title
    }
}
// 作品ドメイン(ssbbs.Story)
package ssbbs

class Story extends Item {
	String id
	def belongsTo = [category:Category]
    static constraints = {
        category (nullable:true)
        text (nullable:false, maxSize:4000, blank:false)
    }
    

    String toString() {

    	return "${title}"

    }
}
// カテゴリードメイン(ssbbs.Category)
package ssbbs

class Category extends Item {
	String id
	static hasMany = [stories:Story]
    static mapping = {
        id generator:'uuid.hex', 
        params:[type:'string']
    }
}
// シリーズドメイン(ssbbs.Series)
package ssbbs

class Series extends Category {
	String id
	def belongsTo = [genre:Genre]
    static constraints = {
        genre (nullable:true)
    }
    
    String toString() {
    	return "Series - ${title}"
    }
}
// ジャンルドメイン(ssbbs.Genre)
package ssbbs

class Genre extends Category {
	String id
	static hasMany = [serieses:Series]
    static constraints = {
    }
    
    String toString() {
    	return "Genre - ${title}"
    }
}
// 作成者ドメイン(ssbbs.Author)
package ssbbs

class Author {
    static mapping = {
        id generator:'uuid.hex', 
        params:[type:'string']
    }
    String id  // ID
    String name
    String spell
    String loginId
    String loginKey
    String gender
    String email
    Date birthDay
    String address
    String telNo
    String celNo
    String hpUrl
    String comment
    Date dateCreated
    Date lastUpdated
    
    static hasMany = [items : Item]
    
    static constraints = {
        name (sizeMax:20, nullable:false, blank:false)
        spell (sizeMax:40, nullable:false, blank:false)
        loginId (size:8..20, nullable:false, blank:false, unique:true)
        loginKey (size:8..20, nullable:false, blank:false)
        gender (inList:["man", "woman"], nullable:true)
        email (email:true, nullable:false, blank:false)
        address (maxSize:40, nullable:true)
        telNo (matches:"\\d{2,4}-\\d{2,4}-\\d{4}", nullable:true)
        celNo (matches:"\\d{2,4}-\\d{2,4}-\\d{4}", nullable:true)
        hpUrl (url:true, nullable:true)
        comment (maxSize:400, nullable:true)
        dateCreated (display:false)
        lastUpdated (display:false)
    }
    
    String toString() {
    	return name
    }
}

作成者とアイテムの1対多の関係に起因する問題

試しに3人ほど作成者を登録し、それぞれの作成者でいくつかのアイテムを作り、複雑に関係を絡ませた後、作成者の1人を登録抹消した時に起こる道連れ削除を未然に防ぐ方法を考える。
起きてほしい事とそうでない事を整理する。

  • 起きて欲しい事
    • 作成者を登録抹消した後、作成者が投稿したアイテムだけを一緒に削除する
  • 起きて欲しくない事
    • 作成者の登録抹消により削除されたカテゴリーの中に含まれる他の作成者による投稿アイテムが巻き添えで削除される

登録抹消した作成者の投稿したカテゴリーに含まれていた他の作成者によるアイテムは、仮に道連れ削除されないとするなら、自ずとどのカテゴリーにも含まれなくなる。ドメインの定義で上位カテゴリーがNULLになるのを許可しているが、belongToとhasManyの関係を定義した事で、動作上はSQLCASCADE制約と同じになってしまう。つまり、上位カテゴリーが削除される直前に、NULLに設定してしまう以外に道連れ削除を防ぐ手立ては今のところ見付かっていない。
できればドメインクラスで定義したいので、Grailsをもっと深く勉強し、道連れ削除が起こらないような制約文もしくはキーワードを探そうと思う。

i18nによるキーワードの自動置き換え

g:messageタグを使い、コード属性を指定する事で、i18nフォルダのmessages.properties内に記述したコードマップで対応する文字列に置き換わって表示される。デフォルトはmessages.propertiesだが、日本語環境ではmessages_ja.properties内に記述されたコードが、対応する文字列に置き換わるので、各国の言語環境に応じたmessages_??.properties内にそれぞれの言語を書けば、自動で各国語に翻訳され、国際化に対応できる。
せっかくなので、ページ内の意味があるメッセージを全てg:messageタグとコードで書いてしまおうと思う。