Grailsのサンプルアプリ

Seleniumの機能を色々試そうと思い立った。
そうなると、テスト対象の手ごろなアプリを作成する必要がある。
そういう訳でGrails2.0.0.RC1で作ってみた。

機能は、ローン計算アプリ。
ドメインは、Borrower(借り手)、Loan、Detailの3つ。LoanがDetailを集約する関係にある。

ソースはこんな感じ。
◆◆Borrower
BorrowerはLoanと今のところ結びついてない。これは将来的に関連を持つか、都度検索して引っ張ってくるかをするつもり。(要するに手抜きです。)
ドメイン

package finance

class Borrower {
    String code
    String name
    String description

    static constraints = {
        code(unique:true, nullable:false)
        name(nullable:false)
    }
}

■コントローラ(静的Scaffold)

package finance

class BorrowerController {
    def scaffold = true
}

◆◆Loan
ドメイン

package finance
import groovy.transform.ToString
import static java.util.Calendar.*

@ToString(includeNames=true)
class Loan {

    String loanType // Flat , InterestOnly(現在は手抜きなのでFlatしか対応していない)
    Long principal
    Double rate
    Date startDate
    String contractNumber
    Integer time
    static transients = ['interest']
    
    // 複数の明細を集約する
    static hasMany = [details:Detail]
    
    static constraints = {
        time(blank:false, max:60)
        contractNumber(unique:true, nullable:false)
    }    

    /**
     *  明細の利息額を集計し返却する
     */
    def Long getInterest() {
        Long interest = 0L
        for (Detail detail : details) {
            interest += detail.interest
        }
        return interest
    }

    /**
     *  明細を生成する
     */
    def createDetails() {
        println "Callback Loan#createDetails"
        
        Long amount = calculatePayAmount()
        println "amount : ${amount}"
        Long balanceOfPrincipal = principal
        details = new ArrayList()

        for (i in 1..time) {
            def detail = new Detail()
            detail.collectionDate   = utils.CalendarUtil.addMonth(startDate, (i-1))
            detail.contractNumber   = contractNumber
            detail.time             = i
            detail.interest         = (long) Math.floor(balanceOfPrincipal * rate / 1200)
            detail.principal        = amount - detail.interest
            detail.save()
            details.add(detail)
            balanceOfPrincipal -= detail.principal
        }
    }
    
    /**
     *  元本、金利、回数から1回当りの金額を算出する
     */
    def private Long calculatePayAmount() {

        Double monthlyRate = (rate / 1200)
        Double paymentRate = 0.0
        Long amount = 0L
        paymentRate = monthlyRate * (1 + monthlyRate) * Math.pow((1 + monthlyRate), (time - 1)) / (Math.pow((1 + monthlyRate), time) - 1)
        amount = (long) Math.floor(principal * paymentRate)
    }

}

■サービス層
インスタンス生成時にドメインロジックを呼び出したいので、サービス層を作成した。ドメインのCallback機能を用いても良かったのだが、気分の問題と言うことで)

package finance
import org.springframework.transaction.annotation.Transactional

@Transactional
class LoanService {

    def create(Loan loanInstance) {
        println "LoanService#create ${loanInstance}"
        loanInstance.createDetails()
        loanInstance.save()
        if (!loanInstance.validate()) {
            // println "E R R O R ! ! ! "  エラー処理は超テキトー!
            throw new Exception("Loan is not valid", loanInstance.errors)
        }
    }
}

■コントローラ
(動的scaffoldで生成し変更した。変更部分のみを記載)

〜略〜
// フィールドとして、サービスを追加する。(これだけでインジェクションしてくれるので、便利な世の中になったもんだ)
LoanService loanService

〜略〜
    def save() {
        def loanInstance = new Loan(params)

        // Message passing for Service Layer.
        try {
            loanService.create(loanInstance)
        }
        catch(e) {
            render(view: "create", model: [loanInstance: loanInstance])
            return
        }

        flash.message = message(code: 'default.created.message', args: [message(code: 'loan.label', default: 'Loan'), loanInstance.id])
        redirect(action: "show", id: loanInstance.id)
    }

◆◆Detail(これはドメインだけでコントローラは作ってない)

package finance

class Detail {
    Date collectionDate
    String contractNumber
    Integer time
    Long interest
    Long principal
    
    static constraints = {
    }
}

Grails自体の使用方法は思い切って省略・・・。