GORMでのコンポジション

リファレンスの内容そのままですが、同じ項目群を複数持つだとか、DB上の項目が多いからグルーピングしたいといった時にコンポジションが使えます。この場合テーブル上は1テーブルで表現されますが、アプリケーション側からは対応するクラスに分割して扱えます。

class Person {
    Address homeAddress
    Address workAddress
    static embedded = ['homeAddress', 'workAddress']
}
class Address {
    String country
    String state
    String city
}

embeddedというところにコンポジションのプロパティを指定します。スキーマは以下の様に、コンポジションとなるプロパティ名を元にしてプレフィックスが設定されます。

    create table person (
        id bigint generated by default as identity,
        version bigint not null,
        home_address_city varchar(255) not null,
        home_address_country varchar(255) not null,
        home_address_state varchar(255) not null,
        work_address_city varchar(255) not null,
        work_address_country varchar(255) not null,
        work_address_state varchar(255) not null,
        primary key (id)
    );

ちなみにAddressの定義をPersonと同じ(person.groovy)に定義している点がポイントで、ドメインディレクトリに独立して定義するとAddressテーブルができてしまうのを回避しています。同じファイルに書くのが嫌な場合はsrcディレクトリ配下に置くこともできます。

constraintsもドメイン同様に記述すればOKです。

class Address {
    String country
    String state
    String city
    static constraints = {
        country maxSize: 3
    }
}

errorsの中に入るプロパティのキーはコンポジションのプロパティ名をプレフィックにして.でつないだものになります。

@TestFor(Person)
class PersonSpec extends UnitSpec {

    def "constraintsのテスト"() {
        given:
        mockForConstraintsTests(Person)

        and:
        Address homeAddress = new Address(country: "yyyyy", state: "xxxxx", city: "zzzz")
        Address workAddress = new Address(country: "yyyyy", state: "xxxxx", city: "zzzz")

        when:
        def person = new Person(homeAddress: homeAddress, workAddress: workAddress)
        person.validate()

        then:
        person.errors['homeAddress.country'] == 'maxSize'
        person.errors['workAddress.country'] == 'maxSize'
    }

}