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自体の使用方法は思い切って省略・・・。