Grails をマスターする: 初めての Grails アプリケーションのビルドの記事に沿ってGrailsの習得を進めます。
最初はGrailsのインストールです。
インストール
Javaはいつでも最新版(現時点ではJDK 1.6.0_14)を入れているので、Grailsだけインストールします。
- Grailsを動かすためには、JavaとGroovyが必要と思っていました。実はGrailsの中にGroovyが内蔵されているので、Javaだけあればよかったのでした。上記記事にはしっかり書いていますが、一度誤解するとなかなか是正されません。
記事は、Grails 1.0のインストール内容なので、以下にGrails 1.1.1の場合を記録しておきます。
インストール(Linux編)
http://grails.org/
上記URLからGrailsのBinaryをダウンロードします。これをどこでもよいので展開します。
~$ tar xzf grails-bin-1.1.1.tar.gz
環境変数GRAILS_HOME、PATHを定義します。~/.bash_profileに追記します。
export GRAILS_HOME=$HOME/grails-1.1.1 PATH=$GRAILS_HOME/bin:$PATH
コマンドを実行すると、バージョン番号等が表示されます。
~$ grails Welcome to Grails 1.1.1 - http://grails.org/ Licensed under Apache Standard License 2.0 Grails home is set to: /home/torutk/grails-1.1.1
インストール(Windows編)
Linuxと同様、grails-bin-1.1.1.tar.gzを展開し、環境変数GRAILS_HOMEとPATHを定義しました。
- C:\java\grails-1.1.1 に展開
- コントロールパネルの「システムとメンテナンス」→「システム」→「システムの詳細設定」で表示されるシステムのプロパティダイアログで、[詳細設定]タブを選び、[環境変数]ボタンを押す。ユーザー環境変数に、新規でGRAILS_HOMEに「C:\java\grails-1.1.1」を定義し、編集でPATHに「%GRAILS_HOME%\bin」を追加する
これで動くはずが、grailsコマンドを実行するとエラーが発生し悩みました。
C:\>grails Error starting Grails: org/codehaus/gant/GantBinding java.lang.NoClassDefFoundError: org/codehaus/gant/GantBinding :
色々探し回った結果、Grailsのバグで、grails-1.1.1\bin\startGrails.batの130行目が間違っていることが判明しました。
set JAVA_OPTS=%JAVA_OPTS% -Dgrails.version="1.1.1-SNAPSHOT"
これを
set JAVA_OPTS=%JAVA_OPTS% -Dgrails.version="1.1.1"
と直せば起動します。
- TAR/GZ形式ではなく、ZIP形式のGrails-1.1.1をダウンロードすれば修正が入っているようです(未確認)。
開発環境
記事には書かれていませんが、Grailsの開発環境について調べてみました。
NetBeans 6.5以降でGrails開発環境が標準搭載されるので、Grailsを本格的に使うならNetBeansがよさそうです。
http://journal.mycom.co.jp/column/ide/040/index.html
http://www.netbeans.org/features/groovy/index.html
Grailsは、Eclipse用のプロジェクト設定ファイルを吐き出すので、Eclipseからはこのプロジェクトファイルを取り込めばよいようです。ただ、Groovy編集機能やGrailsの各種コマンドを呼び出すことはできないようです。(別途pluginはあるかもしれません)
今回は学習目的であるので、エディタでちょこっと編集し、コマンドライン環境で実行できれば十分です。Linux(CentOS 5.3)のviでは、Groovyを認識していました。
Emacsは、別途groovy-mode.elを入手してsite-lisp下にコピーしておきます。
http://groovy.codehaus.org/Emacs+Plugin
ここには、3種類のgroovy-mode.elがリンクされています。Jeremy版、Russel版、Stuart版です。ファイルの先頭コメントを見て一番日付が新しいJeremy版を、Emacsのsite-lisp下に入れて、.emacsに追記します。
(autoload 'groovy-mode "groovy-mode" "Groovy editing mode." t) (add-to-list 'auto-mode-alist '("\.groovy$" . groovy-mode)) (add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode))
初めてのGrailsアプリケーションの作成
記事に沿って、trip-plannerアプリケーションを作成します。最初の手順は以下となります。
- プロジェクトの自動生成
- ドメインクラスの生成と編集
- コントローラとビューのビルド(自動生成)
- Webアプリケーションの実行
プロジェクトの自動生成
work$ grails create-app trip-planner : work$ cd trip-planner trip-planner$ ls application.properties ivysettings.xml test web-app build.xml lib trip-planner-test.launch grails-app scripts trip-planner.launch ivy.xml src trip-planner.tmproj trip-planner$
ドメインクラスの生成と編集
trip-planner$ grails create-domain-class Trip : Created DomainClass for Trip Created Tests for Trip trip-planner$
生成されるファイルは、記事とディレクトリが少し異なっていて以下となりました。
trip-planner/grails-app/domain/Trip.groovy trip-planner/test/unit/TripTests.groovy
Trip.groovyの内容は以下です。記事とは違ってクラスの内容にconstraintsが記述されています(といってもconstraintsは空ですが)。
class Trip { static constraints = { } }
記事の通り、このTripにプロパティを追記します。
class Trip { static constraints = { } String name String city Date startDate Date endDate String purpose String notes }
コントローラとビューのビルド(自動生成)
trip-planner$ grails generate-all Trip : Generating views for domain class Trip ... Generating controller for domain class Trip ... Finished generation for domain class Trip trip-planner$
生成されたファイルを調べるために、findコマンドでコマンド実行後に作成されたファイルを調べてみましょう。
trip-planner$ find . -type f -cmin -3 ./grails-app/views/trip/show.gsp ./grails-app/views/trip/edit.gsp ./grails-app/views/trip/list.gsp ./grails-app/views/trip/create.gsp ./grails-app/controllers/TripController.groovy ./stacktrace.log trip-planner$
コントローラ TripController.groovyとview(show.gsp, edit.gsp, list.gsp, create.gsp)が生成されたのが分かります。
生成された内容は、記事に書かれたものと多少違っています。Grailsのバージョンが1.0から1.1に上がったことによるものです。
- TripController.groovyのdef list
def list = { params.max = Math.min( params.max ? params.max.toInteger() : 10, 100) [ tripInstanceList: Trip.list( params ), tripInstanceTotal: Trip.count() ]
- list.gsp
<g:each in="${tripInstanceList}" status="i" var="tripInstance"> <tr class="${(i % 2) == 0 ? 'odd' : 'even'}"> <td> <g:link action="show" id="${tripInstance.id}">${fieldValue(bean:tripInstance, field:'id')} </g:link> </td>
- TripController.groovyのdef save
def save = { def tripInstance = new Trip(params) if(!tripInstance.hasErrors() && tripInstance.save()) { flash.message = "Trip ${tripInstance.id} created" redirect(action:show,id:tripInstance.id) } else { render(view:'create',model:[tripInstance:tripInstance]) } }
Webアプリケーションの実行
trip-planner$ grails run-app : Running Grails application.. 2009-07-11 17:40:57,566 [main] WARN mortbay.log - failed SelectChannelConnector@0.0.0.0:8080 java.net.BindException: Address already in use : (大量の例外スタックトレースが表示) Server failed to start: java.net.BindException: Address already in use trip-planner$
おっと、8080ポートが別に使われていたので、ポート番号を指定して起動します。
trip-planner$ grails -Dserver.port=8086 run-app : Server running. Browse to http://localhost:8086/trip-planner
scaffold指定
記事のとおり、TripController.groovyの内容を、scaffold指定だけにして実行し直します。本当に動的になっているか確認するため、ドメインクラスのTrip.groovyにプロパティを追加し、再実行します。
が、表示上まったく追加したプロパティが表われません。あれ、おかしいなぁ。あちこち調べまわってみると、scaffoldは、viewのファイルが存在しない場合はビューを動的生成し、viewのファイルがあるときは動的生成はしないということが分かりました。
ということは、最初にgrails generate-allでviewが静的に生成されているので、これがあるために、ドメインクラスを変更しても表示に反映されないのでした。
そこで、grails-app/views/trip/以下を消してみます。
trip-planner$ rm -r grails-app/views/trip/ trip-planner$
もう一度、表示してみると、追加したプロパティが表示されました。
なお、再起動は不要でした。
感想
scaffoldを利用すると、ドメインクラスの定義のみで、単純なCRUD処理アプリケーションが一つ出来上がります。うまくはまる場合は、とても強力な機能と思います。しかし、実際はお仕着せ画面や機能では不足することが多いので、どこまで通用するかが見きわめポイントです。
日付の表示が、英語圏の並び順(日・月・年)になっているのがいただけません。きっと今後変更する手段が記事で出てくると思います。
インストール・実行については簡単でした。Webアプリケーションは設定やインストールが面倒で複雑だったりするので、簡潔さはとてもうれしいです。
サーバを再起動しなくても、ドメインクラス、コントローラクラス、ビューをいじれば変更が反映されます(内蔵Webサーバ使用時)。これは便利です。というか、スクリプト言語だからこうでなくては・・・。
ディレクトリ名を変えても、アプリケーション名(とURL)は変わりません。