「Grailsをマスターする:Grailsのイベント・モデル」を読みながら動かしてみる
Grails をマスターする: Grails のイベント・モデルの記事に沿ってGrailsの習得を進めます。
id:torutk:20090726の続きです。
ビルド・イベント
記事の記述にある、GRAILS_HOME/scripts/Clean.groovyを実際に見ると、内容が違っています。Grails 1.1で変更になったと思われます。
- grails-1.1.1/scripts/Clean.groovy の内容
includeTargets << grailsScript("_GrailsClean") setDefaultTarget("cleanAll")
grailsScript("_GrailsClean")は、別なスクリプトファイルを呼び出すのだろうと想像し、同ディレクトリを見ると、_GrailsClean.groovyというファイルがありました。その中には、
target ( cleanAll: "Cleans a Grails project" ) {
clean()
cleanTestReports()
}
という内容があり、この部分は記事にある記述とほぼ同じです。記事では、続いてcleanターゲットを追いかけているので、同じ_GrailsClean.groovyファイルに記述されているcleanターゲットを見てみると、
target ( clean: "Implementation of clean" ) {
depends(cleanCompiledSources, cleanWarFile)
}
となっています。記事とは異なり、eventの呼び出しがありません。探してみると、_GrailsClean.groovyの最初の方に以下の記述があります。
includeTargets << grailsScript("_GrailsEvents")
これが、eventの呼び出しに関係するかは追いきれていません。
なお、プロジェクトディレクトリのscripts/Events.groovyを作成して記事にあるコードを記述すると、イベント時に呼び出しが行われるのはgrails-1.1.1でも同じでした。
ブートストラップイベント
記事のリストとgrails-1.1.1で生成されるgrails-app/conf/BootStrap.groovyの内容が少し変わっています。
class BootStrap { def init = { servletContext -> } def destroy = { } }
initメソッドに引数が増えています。
記事に従って、この2つのメソッドにprintln文を追加します。
class BootStrap { def init = { servletContext -> println "### 開始します" } def destroy = { println "### 終了します" } }
記事に従って、対話モードでサーバを起動・終了します。
trip-davisworld$ grails interactive :(中略) -------------------------------------------------------- Interactive mode ready, type your command name in to continue (hit ENTER to run the last command):
コマンドを入力するよう促されるので、run-appと入力します。
run-app Running script /home/toru/grails-1.1.1/scripts/RunApp.groovy Environment set to development Running Grails application.. ### 開始します Server running. Browse to http://localhost:8080/trip-davisworld -------------------------------------------------------- Application loaded in interactive mode, type 'exit' to shutdown server or your command name in to continue (hit ENTER to run the last command):
サーバが起動する際、先ほど追加したprintln文による表示が行われました。
次にサーバを終了します。exitと入力します。
exit Stopping Grails server... ### 終了します -------------------------------------------------------- Command [RunApp completed in 36455ms -------------------------------------------------------- Interactive mode ready, type your command name in to continue (hit ENTER to run the last command):
対話モードによる起動・終了は、JavaVMを起動しっぱなしにするので、コマンドからgrails run-appでサーバを起動するより早く上がります。開発時には、頻繁にサーバを再起動するので、この方法を使うとストレスが少なくなります。
grails interactiveでの疑問
- run-appではdevelopment modeになるが、production modeで実行するには?
ブートストラップ中に処理を入れる
Hotelクラスを作成して起動時にデータベースレコードを挿入するという流れですが、前回までのレガシーデータベースマッピングではなく、Grailsのドメインクラスから自動生成する方法を使うので、いったん新しいプロジェクトとして起こします。
trip-plannerアプリケーションの退避と新規生成
今あるtrip-plannerアプリケーションはcleanコマンドで生成物を削除してから、ディレクトリ名を変えておき、新たにtrip-plannerアプリケーションを生成します。
work$ cd trip-planner trip-planner$ grails clean : trip-planner$ cd .. work$ mv trip-planner trip-planner.bak work$ grails create-app trip-planner :(中略) Installing plug-in hibernate-1.1.1 You currently already have a version of the plugin installed [hibernate-1.1.1]. Do you want to upgrade this version? (y, n) y [delete] Deleting directory /home/torutk/.grails/1.1.1/projects/trip-planner/plugins/hibernate-1.1.1 [mkdir] Created dir: /home/torutk/.grails/1.1.1/projects/trip-planner/plugins/hibernate-1.1.1 [unzip] Expanding: /home/torutk/.grails/1.1.1/plugins/grails-hibernate-1.1.1.zip into /home/torutk/.grails/1.1.1/projects/trip-planner/plugins/hibernate-1.1.1 Executing hibernate-1.1.1 plugin post-install script ... Plugin hibernate-1.1.1 installed Created Grails Application at /home/torutk/work/trip-planner work$ cd trip-planner trip-planner$
ここで、過去に同じアプリケーション名を作成していると、上記にあるように、[hibernate-1.1.1]プラグインを上書きするか聞いてきます。とりあえずyを入力してみると、$HOME/.grailsの中にプロジェクト名のディレクトリが作られて、その中に何かが展開されているようです。
プロジェクトディレクトリ以外にも必要なものがあると、バージョン管理等で問題が出るので、ためしに作成したディレクトリを別な場所にコピーし、別ユーザで実行してみました。すると、そのユーザのホームディレクトリ下にhibernateプラグインが見つからないと警告メッセージが出て、ネットワークからダウンロードして展開する動きをしました。問題はないと思われます。
grailsがファイル生成時にSubversionへの追加処理を行う
記事にあるように、プロジェクトディレクトリ下のscripts/Events.groovyにCreatedFileイベントハンドラを記述しておきます。
eventCreatedFile = { fileName -> "svn add ${fileName}".execute() println "### ${fileName} was just added to subversion." }
プロジェクトディレクトリのSubversionへの登録とチェックアウト
まずgrailsで生成したディレクトリ一式をSubversionへ登録します。
以下の例は、同一マシン上にあらかじめsvnserveを起動してリポジトリを作成した場合の例です。
work$ svn import trip-planner svn://localhost/path/to/snvrep/trunk/trip-planner work$
importしたディレクトリは削除か別名に退避しておき、あらたにリポジトリからチェックアウトします。
work$ svn co svn://localhost/path/to/svnrep/trunk/trip-planner trip-planner work$
Hotelドメイン・クラスの生成
trip-planner$ grails create-domain-class Hotel :(中略) ### /home/torutk/work/trip-planner/grails-app/domain/Hotel.groovy was just added to subversion. Created DomainClass for Hotel ### /home/torutk/work/trip-planner/test/unit/HotelTests.groovy was just added to subversion. Created Tests for Hotel trip-planner$
さきに作成したイベントハンドラが起動しています。
trip-planner$ svn status A test/unit/HotelTests.groovy ? scripts/Events.groovy A grails-app/domain/Hotel.groovy
確かに追加(svn add)されているのが分かります。
記事に沿って、Hotelクラスにプロパティnameを追記します。
ドメインクラスのタイムスタンプ
Hotelクラスに、以下のプロパティを追加します。
Date dateCreated Date lastUpdated
次に、scaffoldのテンプレートを修正するために、grails install-templatesを実行します。
src/templates/scaffoldingディレクトリにある、create.gspとedit.gsp
を修正します。
これで、Hotelを作成するとdateCreated, lastUpdatedにタイムスタンプが自動で登録され、Hotelを更新するとlastUpdatedにタイムスタンプが更新されるようになります。
dateCreated, lastUpdatedの名前は規約か?
確認するために、xdateCreated, xlastUpdatedとプロパティ名を修正してみました。Hotel.groovyとcreate.gsp、およびedit.gspをそれぞれ変更し実行すると、作成時にエラーとなりました。
[class Hotel]クラスのプロパティ[xdateCreated]にnullは許可されません。 [class Hotel]クラスのプロパティ[xlastUpdated]にnullは許可されません。
どうやら、この名前でないといけないようです。
タイムスタンプのカスタマイズ
dateCreated, lastUpdatedは、String型でないとエラーとなってしまいます。例えばテキストでデータベースに格納すると言った場合、記事にあるように、beforeInsert, beforeUpdateメソッドを定義するとよさそうです。
class Hotel { static constraints = { name() modifiedTime(nullable:true) } String name String modifiedTime def beforeInsert = { def time = new Date() modifiedTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(time) } def beforeUpdate = { def time = new Date() modifiedTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(time) } }
毎回SimpleDateFormatをnewしているのはちょっといただけないですが、後日groovyでstaticフィールドの使用方法を調べて確認することにします。