(クイックリファレンス)

6 Webレイヤ

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith ,
Japanese Translation: T.Yamamoto, Japanese Grails Doc Translating Team
このドキュメントの内容はスナップショットバージョンを元に意訳されているため、一部現行バージョンでは未対応の機能もあります。

Version: 2.1.0.BUILD-SNAPSHOT

目次

6 Webレイヤ

6.1 コントローラ

コントローラはリクエストを処理してレスポンスの準備・作成を行います。コントローラはレスポンスをビューに委譲したり、直接返す事ができます。単純にgrails-app/controlersディレクトリに、クラス名称の最後をControllerにしたクラスを作成することで、コントローラを追加できます。(パッケージに入れる場合は、パッケージ名のサブディレクトリに作成します。)

URL マッピングのデフォルト設定では、Controllerの前に付けた名称がベースのURIにマップされ、コントローラに定義したそれぞれのアクション名称がコントローラ名称と繫がれてURIをマップします。

6.1.1 コントローラとアクションの理解

コントローラを作成する

create-controllerまたはgenerate-controllerコマンドでコントローラが作成できます。Grailsプロジェクトのルートで以下のようにコマンドを実行します。

grails create-controller book

コマンドを実行するとgrails-app/controllers/myapp/BookController.groovyにコントローラを生成します。

package myapp

class BookController {

def index() { } }

パッケージ名を指定しない場合は、"myapp"というアプリケーション名であれば、自動的にデフォルトパッケージとして生成されます。

デフォルトでは、BookControllerは、URIが /bookとなります。(アプリケーションのコンテキストルートからの相対パス)

create-controllergenerate-controllerコマンドは、簡単にコントローラを生成する便利なコマンドですが、IDEやテキストエディタの機能でクラスを作成してもかまいません。

アクションの作成

コントローラは複数のアクションメソッドを持つことができます。それぞれURIにマップされます:

class BookController {

def list() {

// do controller logic // create model

return model } }

この例では、アクションメソッド名がlistなので、URIは/book/listにマップされます。

パブリックメソッドをアクションとする

以前のバージョンまでは、アクションがクロージャで実装されてました。この方式はまだサポートされています。ただし今後のアクションの実装はメソッドを使用することを推奨します。

クロージャではなく、メソッドにすることによって以下の利点があります:
  • メモリ効率
  • ステートレスコントローラの使用 (シングルトンスコープ指定)
  • サブクラスでのアクションのオーバーライドが可能。スーパークラスのアクションメソッド呼び出しが可能。super.actionName()
  • クロージャはフィールドのため複雑だった、スタンダードなプロキシを使用したメソッドのインターセプトが可能。

クロージャを使用したい場合や、旧バージョンで作られたコントローラを使用しながら、メソッドの利点も欲しい場合は、BuildConfig.groovyに、 grails.compile.artefacts.closures.convertを定義します:
grails.compile.artefacts.closures.convert = true

この定義をすることで、AST変換を使用してクロージャをメソッドに変換したバイトコードを生成します。

If a controller class extends some other class which is not defined under the grails-app/controllers/ directory, methods inherited from that class are not converted to controller actions. If the intent is to expose those inherited methods as controller actions the methods may be overridden in the subclass and the subclass method may invoke the method in the super class.

デフォルトアクション

コントローラのルートURIににデフォルトURIをマップする概念をもっています。例としてBookControllerの場合は /bookになります。デフォルトURIが呼ばれたときに呼ばれるアクションは以下のルールになっています:
  • アクションが1つの場合は、それがデフォルトになります。
  • indexという名称のアクションがある場合は、それがデフォルトになります。
  • defaultActionを定義して指定できます。

static defaultAction = "list"

6.1.2 コントローラとスコープ

使用可能なスコープ

スコープは、変数を収納可能なハッシュのようなオブジェクトです。コントローラでは、次のスコープが使用可能です。
  • servletContext - webアプリケーションの全体にわたってスコープを共有するアプリケーションスコープです。ServletContextのインスタンスです。
  • session - セッションは、通常クッキーを使用してリクエストを行ったクライアントとの連携をして、ステートを保持します。HttpSessionのインスタンスです。
  • request - リクエストオブジェクトはリクエスト時のみオブジェクトを保持するオブジェクトです。HttpServletRequestのインスタンスです。
  • params - 受け取ったリクエストクエリー文字列または、POSTパラメータを保持するミュータブルマップです。
  • flash - 以下を参照

スコープへのアクセス

以下のようにスコープはGroovyの配列インデクスオペレータで、変数の名称を指定して参照することができます。HttpServletRequestにあるような、Servlet APIの提供する変数に関しても同じようにアクセスできます

class BookController {
    def find() {
        def findBy = params["findBy"]
        def appContext = request["foo"]
        def loggedUser = session["logged_user"]
    }
}

フィールド参照オペレータを使用してスコープの変数を参照すれば、シンタックスもっと簡潔にすることもできます:

class BookController {
    def find() {
        def findBy = params.findBy
        def appContext = request.foo
        def loggedUser = session.logged_user
    }
}

これはGrailsでの、様々なスコープにアクセスする一例です。

フラッシュスコープを使用する

Grailsは、今回のリクエストと次のリクエストのみで値を一時的に保持する flashスコープをサポートしています。保持された内容は自動的に削除されます。リダイレクト等を行う前にメッセージを残す時などに使用できます。以下が例です。

def delete() {
    def b = Book.get(params.id)
    if (!b) {
        flash.message = "User not found for id ${params.id}"
        redirect(action:list)
    }
    … // remaining code
}

この例では、deleteアクションに対してリクエストをすると、リダイレクトされてlistアクションが実行された際に、flashスコープのmessageに代入した内容がメッセージ表示等に使用可能になります。このリダイレクトされたlistアクション(deleteの次のアクション)の処理が終了したら、flashスコープの内容が削除されます。

メッセージ表示などで文字列型が頻繁に使用されますが、flashスコープに一時的に保持する値は、どのようなオブジェクトでもかまいません。

コントローラのスコープ設定

デフォルトでは、コントローラはリクエスト毎にインスタンスが生成されます。コントローラのスコープはprototypeになっているため、リクエストがそれぞれのスレッドを持っているので、スレッドセーフになります。

コントローラにスコープを指定することで振る舞いを変更できます。サポートされているスコープは:
  • prototype (デフォルト) - リクエスト毎にコントローラが生成されます。(クロージャでアクションを推奨)
  • session - ユーザのセッションを通して1個のコントローラが生成されます。
  • singleton - 1個のコントローラのみが存在する状態です。(メソッドでのアクションをお勧めします)

スコープを定義するには、コントローラクラスに、static scope 変数で指定したいスコープを定義します。以下が例になります。

static scope = "singleton"

デフォルトスコープは、Config.groovygrails.controllers.defaultScopeを指定する事で変更できます。

grails.controllers.defaultScope = "singleton"
コントローラのスコープは使用状況を考えてお使いください。例えば、シングルトンのコントローラは、_全ての_リクエストで共有されるためプロパティを持つべきではありません。デフォルトで定義されているスコープのprototype を変更することで、プラグインなどで提供されたコントローラがprotopypeを前提で実装されている場合があるので注意しましょう

6.1.3 モデルとビュー

モデルを返す

モデルはビューを描写する際に使用するMapインスタンスです。ビュー内でMapのキーに一致する変数名が使用できます。モデルを返すには、数種類の方法があります。先ずはじめにMapインスタンスを明示的に返す方法です。

def show() {
    [book: Book.get(params.id)]
}

上記の例はスカッフォルドのビューで使用する例ではありません。スカッフォルドに関しては、スカッフォルドのセクションを参照してください。

以下の例のように、明示的にモデルを返さない場合は、コントローラのプロパティがモデルとして使用されます。

class BookController {

List books List authors

def list() { books = Book.list() authors = Author.list() } }

これはコントローラのスコープがprototype(リクエスト毎にコントローラが生成される)の場合に可能な方法です。他の場合はスレッドセーフではありません。

上記の例では、 プロパティのbookauthorsがビューで参照できます。

上級者向けのアプローチとしては、SpringのModelAndViewインスタンスを返す方法があります。

import org.springframework.web.servlet.ModelAndView

def index() { // get some books just for the index page, perhaps your favorites def favoriteBooks = ...

// forward to the list view to show them return new ModelAndView("/book/list", [ bookList : favoriteBooks ]) }

一つ覚えておいて欲しいのが、一定の変数名が、モデル使用できないという事です。
  • attributes
  • application

現状では使用したとしてもエラーが出ることはありませんが、将来的には変わるかもしれません。

ビューの選択

これまでの例ではビューがどのように設定されるのかを説明しませんでたが、Grailsではどのようにビューを選択しているのでしょうか?答えは慣習にあります。何も指定しなければ、以下のアクションでは、Grailsが自動的に、grails-app/views/book/show.gspをビューとして使用します。

class BookController {
    def show() {
         [book: Book.get(params.id)]
    }
}

別のビューを描写する場合は、renderメソッドで指定します。

def show() {
    def map = [book: Book.get(params.id)]
    render(view: "display", model: map)
}

上記の例では、grails-app/views/book/display.gspの描写を試みます。注目する場所はGrailsは自動的にコントローラがbookであれば、grails-app/viewsディレクトリのbookディレクトリを、指定しなくても取得する所です。これは便利なのですが、ビューを共有する場合は、パスを指定する必要があります。

def show() {
    def map = [book: Book.get(params.id)]
    render(view: "/shared/display", model: map)
}

上記の例では、grails-app/views/shared/display.gspの描写を試みます。

Grailsでは、ビューにJSPもサポートしています。これによって、参照したパスにGSPが存在しない場合は、JSPが代わりに使用されます。

レスポンスの描写

直接文字列をコントローラからレスポンス(例としてAjaxアプリケーションなど)したい場合、とてもフレキシブルなrenderメソッドを使うと簡単にできます。

render "Hello World!"

上記の例ではレスポンスに"Hello World!"を返します。他の例として:

// マークアップを返す
render {
   for (b in books) {
      div(id: b.id, b.title)
   }
}

// 指定したビューを描写
render(view: 'show')

// コレクションの中身をそれぞれ指定したテンプレートを適応して描写
render(template: 'book_template', collection: Book.list())

// 文字列を指定したエンコーディングとコンテントタイプで描写
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")

renderメソッドでGroovyのMarkupBuilderを使用してHTMLを生成する場合は、HTMLエレメント名とGrailsタグとの名称衝突を気をつけましょう。(Grailsのタグはアクション内部でメソッドとして使用できるため)

import groovy.xml.MarkupBuilder
…
def login() {
    def writer = new StringWriter()
    def builder = new MarkupBuilder(writer)
    builder.html {
        head {
            title 'Log in'
        }
        body {
            h1 'Hello'
            form {
            }
        }
    }

def html = writer.toString() render html }

上記の例ではformの場所にformタグが呼ばれてしまいます。これを回避して<form>タグを正常に生成するには、以下のようにbuilder.formとします。

def login() {
    // …
    body {
        h1 'Hello'
        builder.form {
        }
    }
    // …
}

6.1.4 リダイレクトとチェイン

リダイレクト

コントローラのメソッド、redirectを使用して、アクションをリダイレクトすることができます:

class OverviewController {

def login() {}

def find() { if (!session.user) redirect(action: 'login') return } … } }

redirectメソッドは内部的にHttpServletResponseオブジェクトのsendRedirectメソッドを使用しています。

redirectメソッドでは以下のいずれかのように使用します:

*同じコントローラ内のアクションクロージャ(プロパティで)の指定:

// Call the login action within the same class
redirect(action: login)

  • アクション名称でのしていと、別のコントローラのアクションの場合はコントローラの指定:

// Also redirects to the index action in the home controller
redirect(controller: 'home', action: 'index')

  • アプリケーションコンテキストパス内のリソースのURI:

// Redirect to an explicit URI
redirect(uri: "/login.html")

  • URLを全指定:

// Redirect to a URL
redirect(url: "http://grails.org")

メソッドのparams引数をにパラメータを指定することで、アクションへのパラメータを渡せます:

redirect(action: 'myaction', params: [myparam: "myvalue"])

これらのパラメータは、アクセス時のリクエストパラメータのparamsプロパティから参照できるようになります。もしリクエストと同じパラメータを指定した場合は、パラメータは上書きされます。

paramsオブジェクトはMapなので、以下のように、そのまま全てのリクエストパラメータを次のアクションに渡すことができます:

redirect(action: "next", params: params)

ターゲットURIに、フラグメントを含めることもできます:

redirect(controller: "test", action: "show", fragment: "profile")

この例では(URLマッピング定義によりますが)、リダイレクトは"/myapp/test/show#profile"になります。

チェイニング

アクションのチェインが可能です。チェインではアクションからアクションへのモデル参照が可能です。以下の例でアクションfirstを呼び出します:

class ExampleChainController {

def first() { chain(action: second, model: [one: 1]) }

def second () { chain(action: third, model: [two: 2]) }

def third() { [three: 3]) } }

結果モデルは以下のようになります:

[one: 1, two: 2, three: 3]

途中でコントローラアクションからモデルにアクセスするには、chainModelマップを使用します。この動的プロパティはchainメソッドから呼ばれた時のみに存在します。

class ChainController {

def nextInChain() { def model = chainModel.myModel … } }

redirectメソッドと同じように、chainメソッドにパラメータを渡すこともできます:

chain(action: "action1", model: [one: 1], params: [myparam: "param1"])

6.1.5 コントローラ・インターセプター

リクエスト、セッションまたはアプリケーションで処理のインターセプトができると、たいへん役に立ちます。これはアクションインターセプターで達成できます。現在2種類のインターセプターbeforeとafterがあります。

複数のコントローラにインターセプターを反映させたい場合は、Filterを使用することをお勧めします。Filterは多数のコントローラまたはURIに、ロジックを追加すること無く反映できます。

ビフォア・インターセプション

beforeInterceptorは、アクションが実行される前に処理の追加を行うことができます。beforeInterceptorで、falseを返すとアクションは実行されません。コントローラ内の全てのアクションにインターセプターを定義するには以下のようにします。

def beforeInterceptor = {
    println "Tracing action ${actionUri}"
}

上記はコントローラ内部に設定します。全てのアクションの前に実行され処理を干渉することはありません。 他によくある実装例としては簡単な認証などです:

def beforeInterceptor = [action: this.&auth, except: 'login']

// defined with private scope, so it's not considered an action private auth() { if (!session.user) { redirect(action: 'login') return false } }

def login() { // display login page }

上記の例では先ず、authメソッドを定義します。外部にアクションとして認識されないようにprivateメソッドにします。 今回は、アクション名がlogin以外の全てのアクションで実行されるように、実行するメソッドをaction:に指定し、除外するアクション'login'を except:に指定して、beforeInterceptorを定義します。authメソッドはGroovyのメソッドポインターシンタックスで指定します。これで、authの処理によって、sessionにuser変数が見つからなかった場合は'login'アクションへリダイレクトしてfalseを返しアクションを実行しないインターセプターが行えます。

アフター・インターセプション

afterInterceptorは、アクションが実行される後に処理の追加を行うことができます:

def afterInterceptor = { model ->
    println "Tracing action ${actionUri}"
}

アフター・インターセプターでは、処理結果のモデルを引数として取得して、モデルやレスポンスを操作することができます。

描写直前のSpring MVCのModelAndViewも取得して変更することも可能です。以下のケースでは、前の例に追記すると:

def afterInterceptor = { model, modelAndView ->
    println "Current view is ${modelAndView.viewName}"
    if (model.someVar) modelAndView.viewName = "/mycontroller/someotherview"
    println "View is now ${modelAndView.viewName}"
}

この例では返されたモデルの内容を元にビューを変更しています。インターセプトされた、アクションがredirectrenderを呼び出している場合は、modelANdViewnullになります。

インターセプションの条件指定

'excepr'条件は、Railsユーザにとってインターセプターを使う時はお馴染みだと思います。(Railsではインターセプターはフィルターと呼ばれています。Javaでは用語的にサーブレットフィルターと衝突するのでインターセプターとしています)

def beforeInterceptor = [action: this.&auth, except: 'login']

上記の例では、exceptで指定したアクション以外でインターセプターを実行します。次のように、複数指定する場合はリストで指定します:

def beforeInterceptor = [action: this.&auth, except: ['login', 'register']]

指定したアクションのみでインターセプターを実行する場合は、'only'を使用します:

def beforeInterceptor = [action: this.&auth, only: ['secure']]

6.1.6 データバインディング

データバインディングとは、受信したリクエストパラメータを、オブジェクトのプロパティまたオブジェクト構造へ結合させる機能です。データバインディングでは、フォームから送信された文字列のリクエストパラメータを、必要な型への変換を行います。

Grailsでは、Springのデータバインディングを基礎にデータバインディングを行います。

モデルにリクエストデータをバインドする

リクエストパラメータをドメインクラスのプロパティへバインドする2通りの方法があります。1つめはドメインクラスのMapを指定するコンストラクタを使用します。

def save() {
    def b = new Book(params)
    b.save()
}

この例での、 new Book(params)の部分でデータバインディングが実行されます。paramsオブジェクトをドメインクラスのコンストラクタに渡すことで、Grailsが自動にリクエストパラメータを認識してバインドします。例えば以下の例のようなリクエストを受信したとします。

/book/save?title=The%20Stand&author=Stephen%20King

リクエストパラメータのtitleauthorが、自動的にドメインクラスへセットされます。もう一つの方法として、既存のドメインクラスインスタンスへデータバインディングを行う方法として、インスタンスのpropertiesプロパティにparamsをセットする方法があります。

def save() {
    def b = Book.get(params.id)
    b.properties = params
    b.save()
}

上記の例で先ほどのコンストラクタと同じ結果となります。

シングルエンデッド・アソシエーション(単一終端関連)のデータバインディング

one-to-oneまたはmany-to-oneなどの関連も、Grailsデータバインディングの機能で更新可能です。例として次のようなリクエストを受信したとします:

/book/save?author.id=20

同じ方法で、Grailsがリクエストパラメータの.id接尾辞を自動認識して、与えられたidで該当するAuthorインスタンスを検索してデータバインディングを行います

def b = new Book(params)

次のように、関連のプロパティに文字列で"null"と指定することでnullをセットする事ができます

/book/save?author.id=null

メニーエンデッド・アソシエーション(複数終端関連)のデータバインディング

one-to-manyまたはmany-to-manyなどの関連には、それぞれの関連形式よって違ったデータバインディング方法があります。

Setベースの関連(hasManyでのデフォルト)で、簡単に関連を設定するには、idのリストを送る方法が有ります。以下の例は<g:select>を使用した方法です:

<g:select name="books"
          from="${Book.list()}"
          size="5" multiple="yes" optionKey="id"
          value="${author?.books}" />

このタグでは複数選択のセレクトボックスが生成されます。この例でフォームを送信すると、Grailsが自動的にセレクトボックスで選択されたidを使用して、booksの関連を設定します。

ただしこの方法は、関連オブジェクトのプロパティを更新する場合は使用できません。代わりに添字演算子を使用します:

<g:textField name="books[0].title" value="the Stand" />
<g:textField name="books[1].title" value="the Shining" />

ただし、Setベースの関連では、マークアップ(HTML)が描写された順序とは同じ順序で更新されない危険性あります。 これはSetには順序という概念がなく、books[0]books[1]と指定したとしても、サーバサイドでの関連のソートを明快に指定しない限り、順序が保証されないことになるからです。

Listベース関連の場合は、Listの順序とインデックスを指定することができるので問題有りません。Mapベース関連も同じ事がいえます。

さらに注意するポイントとして、バインドする関連が2個の物に、それより多い数の場合は:

<g:textField name="books[0].title" value="the Stand" />
<g:textField name="books[1].title" value="the Shining" />
<g:textField name="books[2].title" value="Red Madder" />

Grailsが新たにインスタンスを指定したポジションに生成します。途中のいくつかのエレメントを飛ばした数値を指定した場合は:

<g:textField name="books[0].title" value="the Stand" />
<g:textField name="books[1].title" value="the Shining" />
<g:textField name="books[5].title" value="Red Madder" />

Grailsが間のインスタンスも自動的に生成します。上記のケースでは関連が2個の場合は、4個のインスタンスが追加されます。

既存のインスタンスをList関連でバインドするには、単一終端関連と同じように.idシンタックスを使用します。例として:

<g:select name="books[0].id" from="${bookList}"
          value="${author?.books[0]?.id}" />

<g:select name="books[1].id" from="${bookList}" value="${author?.books[1]?.id}" />

<g:select name="books[2].id" from="${bookList}" value="${author?.books[2]?.id}" />

この例では、別々に選択して、books Listの特定の場所にエントリを可能にします。

同じ方法で、個々のインデックスからエントリの削除も可能です:

<g:select name="books[0].id"
          from="${Book.list()}"
          value="${author?.books[0]?.id}"
          noSelection="['null': '']"/>

上記の例では、空のオプションを選択することで、book[0]の関連を削除します。

リストのインデックス数値をパラメータ名称にすることで、マップのキーとして認識されるので、Mapのプロパティも同じ方法で動作します:

<g:select name="images[cover].id"
          from="${Image.list()}"
          value="${book?.images[cover]?.id}"
          noSelection="['null': '']"/>

この例では、Mapプロパティimagesのキー"cover"に対してバインドされます。

複数ドメインクラスでのデータバインディング

paramsオブジェクトから、複数のドメインオブジェクトへのデータのバインドが可能です。

例として以下のようなリクエストがあったとします:

/book/save?book.title=The%20Stand&author.name=Stephen%20King

上記のリクエストでは、それぞれのパラメータにauthor.またbook.という接頭辞が付いていて、どちらの型(ドメインクラス)に属するかを区別しています。Grailsのparamsオブジェクトは多次元ハッシュのようになっており、それぞれバインドするためのパラメータサブセットとして区別されて索引されます。

def b = new Book(params.book)

上記のように接頭辞bookをparams指定することで、ドメインクラスBookに対して、book.titleパラメータだけが区別されてバインドされます。ドメインクラスAuthorも同じように指定します。

def a = new Author(params.author)

データバインディングとアクション引数

Controller action arguments are subject to request parameter data binding. There are 2 categories of controller action arguments. The first category is command objects. Complex types are treated as command objects. See the Command Objects section of the user guide for details. The other category is basic object types. Supported types are the 8 primitives, their corresponding type wrappers and java.lang.String. The default behavior is to map request parameters to action arguments by name:

class AccountingController {

// accountNumber will be initialized with the value of params.accountNumber // accountType will be initialized with params.accountType def displayInvoice(String accountNumber, int accountType) { // … } }

For primitive arguments and arguments which are instances of any of the primitive type wrapper classes a type conversion has to be carried out before the request parameter value can be bound to the action argument. The type conversion happens automatically. In a case like the example shown above, the params.accountType request parameter has to be converted to an int. If type conversion fails for any reason, the argument will have its default value per normal Java behavior (null for type wrapper references, false for booleans and zero for numbers) and a corresponding error will be added to the errors property of the defining controller.

/accounting/displayInvoice?accountNumber=B59786&accountType=bogusValue

Since "bogusValue" cannot be converted to type int, the value of accountType will be zero, controller.errors.hasErrors() will be true, controller.errors.errorCount will be equal to 1 and controller.errors.getFieldError('accountType') will contain the corresponding error.

If the argument name does not match the name of the request parameter then the @grails.web.RequestParameter annotation may be applied to an argument to express the name of the request parameter which should be bound to that argument:

import grails.web.RequestParameter

class AccountingController {

// mainAccountNumber will be initialized with the value of params.accountNumber // accountType will be initialized with params.accountType def displayInvoice(@RequestParameter('accountNumber') String mainAccountNumber, int accountType) { // … } }

型変換エラーとデータバインディング

時折データバインディングの際に、特有の文字列を特有の型に変換できない場合があります。この場合の結果は型変換エラーとなります。Grailsではドメインインスタンスのerrorsプロパティで型変換エラーを保持します。例えば:

class Book {
    …
    URL publisherURL
}

ドメインクラスBookでURLを表すプロパティにjava.net.URLクラスを使用したとします。次のようなリクエストを受信したとします:

/book/save?publisherURL=a-bad-url

型違いエラーが起きるため、文字列a-bad-urlpublisherURLにバインドすることはできません。次のようにしてエラーを確認します:

def b = new Book(params)

if (b.hasErrors()) { println "The value ${b.errors.getFieldError('publisherURL').rejectedValue}" + " is not a valid URL!" }

この章までは、まだエラーコードの説明していませんが(詳しくはValidationを参照)、型変換エラーではgrails-app/i18n/messages.propertiesを使用してメッセージを返す事もあると思います。次のような一般的なメッセージハンドラーを使用することもできます

typeMismatch.java.net.URL=The field {0} is not a valid URL

また、さらに対象を特定する場合は:

typeMismatch.Book.publisherURL=The publisher URL you specified is not a valid URL

データバインディングとセキュリティ考慮

リクエストパラメータからのバッチ更新をする際は、クライアントからの悪意のあるデータを、ドメインクラスやデータベースへの永続化に反映させないように注意が必要です。添字演算子を使用してドメインクラスにバインドするプロパティを制限することができます。

def p = Person.get(1)

p.properties['firstName','lastName'] = params

上記の例では、firstNamelastNameのみバインドされます。

他には、データバインディングのターゲットをドメインクラスではなくコマンドオブジェクトを利用する方法があります。あるいは柔軟なbindDataメソッドを使用します。

bindDataメソッドは任意のオブジェクトにデータバインディングが可能です:

def p = new Person()
bindData(p, params)

bindDataメソッドでは、更新しないパラメータを除外する事が可能です:

def p = new Person()
bindData(p, params, [exclude: 'dateOfBirth'])

または、指定したプロパティのみの更新も可能です:

def p = new Person()
bindData(p, params, [include: ['firstName', 'lastName]])

Note that if an empty List is provided as a value for the include parameter then all fields will be subject to binding if they are not explicitly excluded.

6.1.7 XMLとJSONのレスポンス

renderメソッドでXMLを出力する

GrailsにはXMLとJSONを生成する複数の方法があります。はじめにrenderメソッド。

renderメソッドに、マークアップビルダーのコードブロックを渡してXML生成:

def list() {

def results = Book.list()

render(contentType: "text/xml") { books { for (b in results) { book(title: b.title) } } } }

このコードの結果は以下のようになります:

<books>
    <book title="The Stand" />
    <book title="The Shining" />
</books>

マークアップビルダーを使用する際は、変数名称の衝突を回避する必要があります。例として次のコードはエラーになります:

def list() {

def books = Book.list() // naming conflict here

render(contentType: "text/xml") { books { for (b in results) { book(title: b.title) } } } }

この例では、Groovyがローカル変数のbooksをメソッドとして実行してしまいます。

renderメソッドでJSONを出力する

JSONを出力する場合もrenderメソッドが使用できます:

def list() {

def results = Book.list()

render(contentType: "text/json") { books = array { for (b in results) { book title: b.title } } } }

このコードの結果は以下のようになります:

[
    {title:"The Stand"},
    {title:"The Shining"}
]

JSONビルダーもXMLと同じく、変数名称の衝突を回避する必要があります。

自動XMLマーシャリング

Grailsではドメインクラスから特別なコンバータを使用した自動マーシャリングをサポートしています。

使用するには、grails.convertersパッケージをインポートする必要があります:

import grails.converters.*

ドメインクラスをXMLに変換するのに、次のように見やすいシンタックスで記述できます:

render Book.list() as XML

この例では次のような結果を出力します:

<?xml version="1.0" encoding="ISO-8859-1"?>
<list>
  <book id="1">
    <author>Stephen King</author>
    <title>The Stand</title>
  </book>
  <book id="2">
    <author>Stephen King</author>
    <title>The Shining</title>
  </book>
</list>

またconvertersを使用する別の方法としてGrailsのcodecsを使用することもできます。codecsでは、encodeAsXMLencodeAsJSONメソッドを提供しています:

def xml = Book.list().encodeAsXML()
render xml

XMLマーシャリングの情報はRESTのセクションも参照してください。

自動JSONマーシャリング

GrailsではXMLと同じ仕組みで、JSONの自動マーシャリングもサポートしています。単純に先のXMLJSONにするだけです

render Book.list() as JSON

この例では次のような結果を出力します:

[
    {"id":1,
     "class":"Book",
     "author":"Stephen King",
     "title":"The Stand"},
    {"id":2,
     "class":"Book",
     "author":"Stephen King",
     "releaseDate":new Date(1194127343161),
     "title":"The Shining"}
 ]

もちろん、encodeAsJSONも同じ結果になります。

6.1.8 JSONBuilder (JSONビルダー)

前のセクション「 XMLとJSONレスポンス」で単純なXMLとJSONのを描写してレスポンスする例を紹介しました。Grailsで使用してるXMLビルダーがGroovyの標準的な XmlSlurper に対して、JSONビルダーに関してはGrails特有の実装になっています。

JSONBuilderとGrailsのバージョン

JSONBuilderの振る舞いは使用するGrailsのバージョンに依存します。1.2以前では現在非推奨となるgrails.web.JSONBuilderが使用されます。このセクションでは、Grails 1.2のJSONBuilderの説明をします。

下位互換として、古いバージョンからメンテナンスしているアプリケーションのために、renderメソッドで古いJSONBuilderを使用するようになっています。新しい良くなったJSONBuilderを使用するにはConfig.groovyに、次のような設定をします:

grails.json.legacy.builder = false

単純なオブジェクトを描写

単純なJSONオブジェクトを生成するには、クロージャの中にプロパティをセットするだけです:

render(contentType: "text/json") {
    hello = "world"
}

上記の例では、このような結果が得られます:

{"hello":"world"}

JSON配列描写

オブジェクトのリストを描写するには、変数に配列を指定するだけです:

render(contentType: "text/json") {
    categories = ['a', 'b', 'c']
}

この例では以下を生成:

{"categories":["a","b","c"]}

複雑なオブジェクトのリストも可能です。例として:

render(contentType: "text/json") {
    categories = [ { a = "A" }, { b = "B" } ]
}

この例では以下を生成:

{"categories":[ {"a":"A"} , {"b":"B"}] }

elementメソッドを使用することで、リストをルートとして返す事ができます:

render(contentType: "text/json") {
    element 1
    element 2
    element 3
}

この例では以下を生成:

[1,2,3]

複雑なオブジェクトの描写

より複雑なオブジェクトをクロージャで描写できます。例として:

render(contentType: "text/json") {
    categories = ['a', 'b', 'c']
    title = "Hello JSON"
    information = {
        pages = 10
    }
}

この例では次のようなJSONを生成します:

{"categories":["a","b","c"],"title":"Hello JSON","information":{"pages":10}}

複雑なオブジェクトの配列

前述したように、クロージャを使用してオブジェクトを配列にネストすることも可能です:

render(contentType: "text/json") {
    categories = [ { a = "A" }, { b = "B" } ]
}

arrayメソッドを使用して動的に生成する事もできます:

def results = Book.list()
render(contentType: "text/json") {
    books = array {
        for (b in results) {
            book title: b.title
        }
    }
}

JSONBuilder APIに直接アクセスする

renderメソッドと関係の無い場所ではJSONを生成したい場合はAPIを直に使用できます:

def builder = new JSONBuilder()

def result = builder.build { categories = ['a', 'b', 'c'] title = "Hello JSON" information = { pages = 10 } }

// prints the JSON text println result.toString()

def sw = new StringWriter() result.render sw

6.1.9 ファイルアップロード

プログラムによるファイルアップロード

Grailsでは、SpringのMultipartHttpServletRequestインターフェイスを使用してファイルアップロードをサポートしています。ファイルアップロードを実装するには、先に次のようにフォームをマルチパートフォームにします:

Upload Form: <br />
    <g:uploadForm action="upload">
        <input type="file" name="myFile" />
        <input type="submit" />
    </g:uploadForm>

uploadFormタグは、通常の<g:form>タグに、enctype="multipart/form-data"の属性を追加します。

アップロードされたファイルを処理するには複数の方法があります。そのうちの一つはSpringのMultipartFileインスタンスを直接操作する方法です:

def upload() {
    def f = request.getFile('myFile')
    if (f.empty) {
        flash.message = 'file cannot be empty'
        render(view: 'uploadForm')
        return
    }

f.transferTo(new File('/some/local/dir/myfile.txt')) response.sendError(200, 'Done') }

このMultipartFileを使用すると、ファイルを別の場所に転送したり、InputStreamを取得して直接ファイルを操作したりなど便利です。

データバインディング経由のファイルアップロード

データバインディングでファイルアップロードを処理する事ができます。Imageというドメインクラスがあるとします:

class Image {
    byte[] myFile

static constraints = { // Limit upload file size to 2MB myFile maxSize: 1024 * 1024 * 2 } }

次の例のように、paramsをコンストラクタに渡してImageオブジェクトを生成すると、Grailsが自動的にファイルのコンテントをmyFileプロパティにbyte[]としてバインドします:

def img = new Image(params)

この場合、データベースカラムのサイズが小さいと処理できなくなるので、sizeまたはmaxSizeの制約を指定しましょう。例としてH2とMySQLは、byte[]プロパティで指定した場合のblobのデフォルトサイズが255バイトになります。

テキストファイルなどの場合は、myFileプロパティの型をString型にすることで文字列にすることも可能です:

class Image {
   String myFile
}

6.1.10 コマンドオブジェクト

Grailsのコマンドオブジェクトの設計概念をサポートしています。コマンドオブジェクトはStrutsでのフォームビーンと同じような物で、ドメインクラスで更新に必要なプロパティサブセットを収集するなどに使用すると便利です。または、データバインディングバリデーションを必要として、ドメインクラスを必要としない作用に使用します。

コマンドオブジェクトの宣言

コマンドオブジェクトは通常使用するコントローラと同じファイルに宣言します。例として:

class UserController {
    …
}

class LoginCommand { String username String password

static constraints = { username(blank: false, minSize: 6) password(blank: false, minSize: 6) } }

この例では、ドメインクラスと同じconstraints(制約)も定義しています。

コマンドオブジェクトを使用する

コマンドオブジェクトを使用する際に、コントローラアクションに複数のコマンドオブジェクトパラメータを明記することができます。Grailsにどのオブジェクトを使用して、収集、バリデートするかを知らせるために、パラメータの型は必ず指定しましょう。

コントローラアクションが実行される前に、Grailsがコマンドオブジェクトのインスタンスを生成して、リクエストパラメータをバインドして、必要なプロパティを収集しコマンドオブジェクトでバリデートします。例として:

class LoginController {

def login = { LoginCommand cmd -> if (cmd.hasErrors()) { redirect(action: 'loginForm') return }

// work with the command object data } }

クロージャアクションでは無く、メソッドアクションの場合は引数として明記します:

class LoginController {
    def login(LoginCommand cmd) {
        if (cmd.hasErrors()) {
            redirect(action: 'loginForm')
            return
        }

// work with the command object data } }

コマンドオブジェクトと依存注入

コマンドオブジェクトに依存注入をすることが可能です。Grailsのサービスを使用したカスタムバリデーションなどを使用する際に便利です:

class LoginCommand {

def loginService

String username String password

static constraints = { username validator: { val, obj -> obj.loginService.canLogin(obj.username, obj.password) } } }

この例では、loginServiceをSpringのApplicatinContextから名前解決でコマンドオブジェクトに注入しています。

6.1.11 フォーム二重送信のハンドリング

Grailsには、"同期トークンパターン"を使用した、フォーム二重送信ハンドリングをサポートしています。使用するには、先ずはformタグにuseToken属性を指定します:

<g:form useToken="true" ...>

そして、コントローラアクションでwithFormメソッドを利用して、リクエストの有効・無効それぞれのコードを記述します:

withForm {
   // good request
}.invalidToken {
   // bad request
}

withFormメソッドのみを提供してinvalidTokenにチェインしなかった場合は、デフォルトでGrailsが無効トークンをflash.invalidTokenに保持して、元のページにリクエストをリダイレクトします。これによってビューで以下のように確認できます:

<g:if test="${flash.invalidToken}">
  Don't click the button twice!
</g:if>

withFormメソッドはセッション(session)を使用するので、セッションの類似性またはクラスタで使用する場合はクラスタ化されたセッションが必要です。

6.1.12 シンプルタイプコンバータ

型変換メソッド

データバインディングでのオーバーヘッドを避けたい場合や、受信したパラメータに対して、単純な型変換行いたい場合、 paramsオブジェクトには、型によって幾つかの便利なメソッドが有ります:

def total = params.int('total')

上記の例ではintメソッドを使用しています。ほかに、boolean,long,char,short等多数のメソッドがあります。これらのメソッドはnullセーフであり、パースエラーセーフなので、パラメータに対してのチェックを追加する必要がありません。

型変換メソッドの第二引数にデフォルト値を設定することができます。一致する入力が無い場合や、型変換でエラーが起きた場合に指定したデフォルト値が返されます:

def total = params.int('total', 42)

GSPタグのattrsパラメータでも、同じ仕組みの型変換機能が使用できます。

複数の同一名称パラメータのハンドリング

よくあるケースとして、リクエストパラメータ内の複数の同じ名称を扱う場合があります。クエリーの例として、"?name=Bob&name=Judy"等。

パラメータに1つのみしか無い場合にイテレートしてしまうと、Stringの文字列が1文字ずつ参照されてしまいます。この問題を避けるために、paramsオブジェクトのlistメソッドを使用することで毎回リストを返すことが可能です:

for (name in params.list('name')) {
    println name
}

6.1.13 非同期リクエスト処理

Servlet 3.0での非同期リクエスト処理をサポートしています。有効にするには、BuildConfig.groovy サーブレットバージョン指定を3.0に変更します。

grails.servlet.version = "3.0"

設定を変更したら、async機能がコンパイル時に有効にするために、クリーンしてリコンパイルしてください。

Servlet 3.0を指定した場合は、Servlet 3.0のみ対応でデプロイされるので、Servlet 3.0に対応したコンテナ(Tomcatの場合は7以上)で運用しましょう。

非同期描写

Servlet 3.0のAsyncContextインスタンスを返すstartAsyncメソッドを呼び出すことで、非同期でコンテンツが描写(テンプレート、バイナリー等)できます。AsyncContextの参照をするだけで、あとは通常のGrailsのrenderメソッドを使用してコンテンツを描写できます。

def index() {
    def ctx = startAsync()
    ctx.start {
        new Book(title:"The Stand").save()
        render template:"books", model:[books:Book.list()]
        ctx.complete()
    }
}

コネクションを終了するには必ずcomplete()メソッドをコールしてください。

非同期リクエストの再開

非同期リクエストを再開するには、AsyncContextクラスのdispatch()メソッドを使用します。

def index() {
    def ctx = startAsync()
    ctx.start {
        // do working
        …
        // render view
        ctx.dispatch()
    }
}

6.2 Groovyサーバーページ

Groovy Server Pages(GSPと略す)は、Grailsのビュー技術です。より柔軟で直感的であり、ASPやJSPのような技術に精通しているユーザ向けに設計されています。

GSPは、grails-app/viewsディレクトリに配置されていて、通常は自動的(慣習によって)または、以下のようにrenderメソッドで描写します。

render(view: "index")

GSPはマークアップとビュー描写を補助するGSPタグで内容が構成されています。

GroovyロジックをGSPに埋め込むことも可能ですが推奨しません。マークアップとコードが混在するのは良いことではありません。ほとんどのGSPではコードを持つべきで無く、そうする必要がありません。

GSPは通常ビュー描写に使用される変数のセットなどの"モデル"を持っています。モデルはコントローラからGSPビューげ渡されます。例として次のコントローラアクションを見てみましょう。

def show() {
    [book: Book.get(params.id)]
}

このアクションはBookインスタンスを取り出して、bookというキーでモデルを作成しています。GSPでは、このキーbookが参照できます。

${book.title}

6.2.1 GSPの基本

ここのセクションでは、GSPで何が可能かの基礎を見ていきましょう。先ずはじめにJSPやASPのユーザに親和性の高い基本的なシンタックスをカバーしていきます。

GSPは、Groovyを記述できる、スクリプトレットブロック<% %>をサポートしています。(しつこいですが推奨しません)

<html>
   <body>
     <% out << "Hello GSP!" %>
   </body>
</html>

もちろん<%= %>を使用して値を出力することもできます。

<html>
   <body>
     <%="Hello GSP!" %>
   </body>
</html>

次のようなJSPスタイルのサーバサイドコメント(HTMLに出力されない)もサポートされています。

<html>
   <body>
     <%-- This is my comment --%>
     <%="Hello GSP!" %>
   </body>
</html>

6.2.1.1 変数とスコープ

<% %>で変数を宣言できます。

<% now = new Date() %>

宣言した変数はページ内でアクセスすることができます。

<%=now%>

GSPには、初期定義されたスコープ変数がいくつか存在します。

6.2.1.2 ロジックとイテレーション

以下のように<% %>シンタックスを使ってループなどを記述できます。

<html>
   <body>
      <% [1,2,3,4].each { num -> %>
         <p><%="Hello ${num}!" %></p>
      <%}%>
   </body>
</html>

また他に分岐も

<html>
   <body>
      <% if (params.hello == 'true')%>
      <%="Hello!"%>
      <% else %>
      <%="Goodbye!"%>
   </body>
</html>

6.2.1.3 ページディレクティブ

GSPはJSPのページディレクティブの一部をサポートしています。

importディレクティブは、ページにクラスをインポートします。

<%@ page import="java.awt.*" %>

contentTypeディレクティブもサポートしています。

<%@ page contentType="text/json" %>

contentTypeディレクティブはGSPを別のフォーマットを可能にします。

6.2.1.4 エクスプレッション

GSPでは、先に紹介した<%= %>シンタックスは滅多に使用しません。代わりに、JSPの式言語(JSP EL)と同じような機能をもつGSP(Groovy)表記または、${expr}のようなGroovyのGStringを使用します。

<html>
  <body>
    Hello ${params.name}
  </body>
</html>

JSP式言語(JSP EL)と違い、Groovy表記は、${…}ブロックに記述します。${…}ブロックの変数はデフォルトではエスケープを行いません。したがって、HTML文字列が直接ページ内に描写されます。これによって起こりうる、XSS(クロスサイトスクリプティング)をリスクを軽減するには、grails-app/conf/Config.groovygrails.views.default.codecを設定します。

grails.views.default.codec='html'

他に設定可能な値は'none'(何も設定しない)と'base64'になります。

6.2.2 GSPタグ

魅力の低い遺産JSPはさておき、このセクションではGSPの組込タグを、GSPページでどのように活用すると良いか解説していきます。

カスタムタグの追加などは、タグライブラリのセクションで解説します。

全ての組込GSPタグは接頭辞g:で始まるタグです。JSPと違いタグライブラリのインポートが必要有りません。g:接頭辞のタグは自動的にGSPタグとして用いられます。例としてGSPタグの見ためは次のようになります:

<g:example />

GSPタグの内容も記述できます:

<g:example>
   Hello world
</g:example>

GSPのタグ属性にエクスプレッションを記述できます。エクスプレッションを使用しない場合は文字列として値が用いられます。:

<g:example attr="${new Date()}">
   Hello world
</g:example>

GSPのタグ属性に、ひんぱんに使用する名前付きパラメータ構文として、Map型を記述できます:

<g:example attr="${new Date()}" attr2="[one:1, two:2, three:3]">
   Hello world
</g:example>

属性内での、Mapの文字列値はシングルクォートを使用してください:

<g:example attr="${new Date()}" attr2="[one:'one', two:'two']">
   Hello world
</g:example>

ここまでで基本構文は全てです。次のセクションではどのようなタグがGrailsにデフォルトで組み込まれているか見ていきましょう。

6.2.2.1 変数とスコープ

GSPでは変数をsetタグで定義することができます:

<g:set var="now" value="${new Date()}" />

この例では変数nowにGSP表記の結果を割り当てています(単純にjava.util.Dateのインスタンスを生成しただけです)。<g:set>タグのタグ内容に記述して定義することもできます。:

<g:set var="myHTML">
   Some re-usable code on: ${new Date()}
</g:set>

変数は以下のいずれかのスコープに定義することができます:
  • page - (デフォルト)現在のページ
  • request - 現在のリクエスト
  • flash - flashスコープに配置され、次のリクエストまで。
  • session - ユーザのセッション
  • application - アプリケーション全体

スコープの定義は、scope属性に指定します:

<g:set var="now" value="${new Date()}" scope="request" />

6.2.2.2 ロジックとイテレーション

GSPにはすぐに活用できるロジックタグとイテレーションタグがあります。ロジックタグは分岐に使用するif, else, elseifです:

<g:if test="${session.role == 'admin'}">
   <%-- show administrative functions --%>
</g:if>
<g:else>
   <%-- show basic functions --%>
</g:else>

イテレーションにはeachタグとwhileタグを使用します:

<g:each in="${[1,2,3]}" var="num">
   <p>Number ${num}</p>
</g:each>

<g:set var="num" value="${1}" /> <g:while test="${num < 5 }"> <p>Number ${num++}</p> </g:while>

6.2.2.3 検索とフィルタリング

オブジェクトのコレクションを扱う場合はソート、フィルタリングが必要になります。その場合は findAllタグとgrepタグが活用できます:

Stephen King's Books:
<g:findAll in="${books}" expr="it.author == 'Stephen King'">
     <p>Title: ${it.title}</p>
</g:findAll>

フィルターとして使うGroovy式をexpr属性に記述します。grepタグも同じような動作をします。クラスでのフィルタリングを例とすると:

<g:grep in="${books}" filter="NonFictionBooks.class">
     <p>Title: ${it.title}</p>
</g:grep>

正規表現を使用した例:

<g:grep in="${books.title}" filter="~/.*?Groovy.*?/">
     <p>Title: ${it}</p>
</g:grep>

上記の例ではGPathも使用方法も紹介されています。GPathはGroovyでのXpathのような言語です。変数booksBookインスタンスのコレクションです。全てのBookにプロパティtitleが有る場合、books.titleとすることでBooktitleプロパティのリストを得られます。Groovyは自動的にコレクションを検索して全てのtitleを含んだリストを返してくれます。

6.2.2.4 リンクとリソース

コントローラとアクションでのリンクを操作するGSPタグがあります。linkタグはコントローラとアクション名を指定するだけでURLマッピングをベースとしたリンクを自動的に生成します(URLマッピングで変更をしても)。:

<g:link action="show" id="1">Book 1</g:link>

<g:link action="show" id="${currentBook.id}">${currentBook.name}</g:link>

<g:link controller="book">Book Home</g:link>

<g:link controller="book" action="list">Book List</g:link>

<g:link url="[action: 'list', controller: 'book']">Book List</g:link>

<g:link params="[sort: 'title', order: 'asc', author: currentBook.author]" action="list">Book List</g:link>

6.2.2.5 フォームとフィールド

フォームの基礎

GSPではHTMLのフォームとフィールドに対応した様々なタグをサポートしています。フォーム・フィールドの基礎となるタグはformタグです。formタグは、通常のHTML formタグが、コントローラ・アクションを意識してる版みたいな物です。次のように、url属性にマップでコントローラアクションを指定します:

<g:form name="myForm" url="[controller:'book',action:'list']">...</g:form>

この例では、myFormという名称で、BookControllerlistアクションへ送信するフォームを生成します。通常のHTML属性はそのまま使用できます。

フォームフィールド

フォームを簡単に作成するために、GSPでは多数種類のフィールドタグをサポートしています:

  • textField - 属性typeが'text'のinputタグ用
  • passwordField - 属性typeが'password'のinputタグ用
  • checkBox - 属性typeが'checkbox'のinputタグ用
  • radio - 属性typeが'radio'のinputタグ用
  • hiddenField - 属性typeが'hidden'のinputタグ用
  • select - セレクトボックス用タグ

value属性の値にGSPエクスプレッションを使用できます:

<g:textField name="myField" value="${myValue}" />

そのほかに、これらのタグを拡張した、radioGroup(radioタグのセットを作成)タグや、localeSelectcurrencySelecttimeZoneSelect等、それぞれ、ロケール、通貨、タイムゾーンに対応したタグもあります。

マルチ送信ボタン

Grailsでは、actionSubmitタグを使用することで、マルチ送信ボタン対応は、すっきりと実装できます。単純な送信ボタンのように見えますが、action属性にアクションを指定すると、指定したアクションへフォームが送信されるようになります。

<g:actionSubmit value="Some update label" action="update" />

6.2.2.6 タグをメソッドとして使用

GSPタグと他のタグ技術との大きな違いは、通常のタグとしての利用だけではなく、コントローラタグライブラリまたはGSPビューから、メソッドとして呼び出せるという点です。

GSPでタグをメソッドとして使用する

タグをメソッドとして使用すると、結果をレスポンスへ出力する代わりに、文字列のようなオブジェクトで返します(Stringと同じメソッドを持つStreamCharBuffer)。:

Static Resource: ${createLinkTo(dir: "images", file: "logo.jpg")}

タグの属性内に使用する際などに特に便利です:

<img src="${createLinkTo(dir: 'images', file: 'logo.jpg')}" />

以下の例のように、このような機能をもたないタグ技術は、タグをネストしないといけません。これだとすぐにコードは乱雑になり、Dreamweaver等のツールでの表示が正常になりません:

<img src="<g:createLinkTo dir="images" file="logo.jpg" />" />

コントローラとタグライブラリでタグをメソッドとして使用する

コントローラとタブライブラリ(実装側)で、タグを呼び出す事が可能です。デフォルトのネームスペースg:を持つタグは、接頭辞無しで呼び出す事で、結果にStreamCharBufferを返します:

def imageLocation = createLinkTo(dir:"images", file:"logo.jpg").toString()

名前の衝突を避けるために、ネームスペースを接頭辞とする場合は:

def imageLocation = g.createLinkTo(dir:"images", file:"logo.jpg").toString()

カスタムネームスペースを使用しているタグの場合は、カスタムネームスペースを接頭辞として使います。FCKエディタープラグインを例とすると:

def editor = fckeditor.editor(name: "text", width: "100%", height: "400")

6.2.3 ビューとテンプレート

Grailsもテンプレートの概念を持っています。ビューを管理しやすいかたまりに分類してレイアウト機能で結合する際に活用できます。

テンプレートの基礎

Grailsでは、アンダースコアがビュー名称の前にあるとテンプレートとして認識するルールがあります。例として、Bookコントローラからbookを描写するテンプレートはgrails-app/views/books/_bookTemplate.gspとして配置:

<div class="book" id="${book?.id}">
   <div>Title: ${book?.title}</div>
   <div>Author: ${book?.author?.name}</div>
</div>

Use the render tag to render this template from one of the views in grails-app/views/book: grails-app/views/bookの配置したビューで、テンプレートを使用するには、renderタグを使います:

<g:render template="bookTemplate" model="[book: myBook]" />

この例のrenderタグのmodel属性を使用してモデルをテンプレートに渡している部分に注目してください。もし複数のBookインスタンスを使用して、それぞれのBookをテンプレートで描写する場合は、以下のようにrenderタグのcollection属性にコレクションを指定します:

<g:render template="bookTemplate" var="book" collection="${bookList}" />

テンプレートの共有

前の例ではBookControllergrails-app/views/book階層以下のビューを対象に説明しました。テンプレートはアプリケーション全体を通して使用したいと思います。

このケースでは、ビューのルートディレクトリである grails-app/views、あるいはルートディレクトのサブディレクトリにテンプレートファイルを配置して、template属性には、/(スラッシュ)から始まる grails-app/viewsからの絶対パスで指定します。例として、テンプレートファイルが、grails-app/views/shared/_mySharedTemplate.gspだとした場合は次のように指定します:

<g:render template="/shared/mySharedTemplate" />

この方法では、どこにテンプレートが配置されていても、どのビューまたはコントローラからでもテンプレートが使用できます:

<g:render template="/book/bookTemplate" model="[book: myBook]" />

テンプレートネームスペース

テンプレートを多用する場合は、テンプレートを簡単に使用するためのtmplという名称の、テンプレートネームスペースが使用できます:

<g:render template="bookTemplate" model="[book:myBook]" />

This can be expressed with the tmpl namespace as follows: 上記の例をtmplネームスペースで記述すると以下のようになります:

<tmpl:bookTemplate book="${myBook}" />

コントローラとタグライブラリでのテンプレート

コントローラのrenderメソッドを使用してコントローラでテンプレートを描写することもできます。Ajaxアプリケーションのように、ページ内の一部をHTMLのパーツやデータをレスポンスして更新する場合に有用です:

def bookData() {
    def b = Book.get(params.id)
    render(template:"bookTemplate", model:[book:b])
}

通常の振る舞いですが、コントローラのrenderメソッドは直接結果をレスポンス出力します。文字列としてテンプレートの結果を取得する場合は、renderタグを使用できます。:

def bookData() {
    def b = Book.get(params.id)
    String content = g.render(template:"bookTemplate", model:[book:b])
    render content
}

ネームスペースgを使用することでタグをメソッドとして使用するのか、コントローラのrenderメソッドなのかを区別しています。

6.2.4 Sitemeshでレイアウト

レイアウトを作成する

GrailsはデコレータエンジンSitemeshの力を借りてビューレイアウトをサポートしています。レイアウトはgrails-app/views/layoutsディレクトリに配置します。標準的なレイアウトは以下のようになります:

<html>
    <head>
        <title><g:layoutTitle default="An example decorator" /></title>
        <g:layoutHead />
    </head>
    <body onload="${pageProperty(name:'body.onload')}">
        <div class="menu"><!--my common menu goes here--></menu>
            <div class="body">
                <g:layoutBody />
            </div>
        </div>
    </body>
</html>

鍵となるエレメントは、 layoutHeadlayoutTitlelayoutBodyタグです:

  • layoutTitle - ページのタイトルを出力
  • layoutHead - ページのheadタグコンテントを出力
  • layoutBody - ページのbodyタグコンテントを出力

さらに前述した例では、指定した内容をターゲットページから精査して内容を返すpagePropertyタグの紹介しています。

レイアウトを反映させる

レイアウトを反映させる幾つかの方法があります。簡単な方法は、ビューにmetaタグを追加するだけです:

<html>
    <head>
        <title>An Example Page</title>
        <meta name="layout" content="main" />
    </head>
    <body>This is my content!</body>
</html>

この例では、レイアウトのgrails-app/views/layouts/main.gspが呼び出されてページのレイアウトに使用されます。内容が先ほどのレイアウトの内奥であれば、以下のような出力が得られます:

<html>
    <head>
        <title>An Example Page</title>
    </head>
    <body onload="">
        <div class="menu"><!--my common menu goes here--></div>
        <div class="body">
            This is my content!
        </div>
    </body>
</html>

コントローラでレイアウトを指定

別の方法として、コントローラに"layout"プロパティの値としてレイアウトする事ができます。例として以下のようなコントローラがあるとしたら:

class BookController {
    static layout = 'customer'

def list() { … } }

grails-app/views/layouts/customer.gspというレイアウトファイルを作成すると、BookControllerに連動する全てのビューに反映します。 "layout"プロパティの値はgrails-app/views/layouts/ディレクトリが含まれます。例として:

class BookController {
    static layout = 'custom/customer'

def list() { … } }

Views rendered from that controller would be decorated with the grails-app/views/layouts/custom/customer.gsp template. この例で、このコントローラのビューは、grails-app/views/layouts/custom/customer.gspテンプレートでデコレートされます。

慣習によるレイアウト

"慣習によるレイアウト"によってレイアウトと連動することが可能です。例として、次のようなコントローラがあるとします:

class BookController {
    def list() { … }
}

BookControllerのビューに反映されるレイアウトgrails-app/views/layouts/book.gspを作成できます。

あるいは、BookControllerのアクションlistのみに反映するレイアウトgrails-app/views/layouts/book/list.gspを作成できます。

両方のレイアウトが存在する場合は、アクションに指定したレイアウトが優先されます。

もしこれらの慣習にあわせたレイアウトが存在しない場合は、最終的にデフォルトレイアウトのgrails-app/views/layouts/application.gspを探しに行きます。アプリケーションのデフォルトレイアウト名称は、次のプロパティをgrails-app/conf/Config.groovyに定義することで変更が可能です。:

grails.sitemesh.default.layout = 'myLayoutName'

この例でのアプリケーションのデフォルトレイアウトはgrails-app/views/layouts/myLayoutName.gspになります。

インラインレイアウト

Grailsでは、applyLayoutタグを使用することでSitemeshのインラインレイアウトをサポートしています。インラインレイアウトでは、テンプレート、URL、任意のコンテントにレイアウトを使用することができます。

例としては以下のようになります:

<g:applyLayout name="myLayout" template="bookTemplate" collection="${books}" />

<g:applyLayout name="myLayout" url="http://www.google.com" />

<g:applyLayout name="myLayout"> The content to apply a layout to </g:applyLayout>

サーバーサイドインクルード

applyLayoutタグは、外部コンテントに対してレイアウトを反映させるのに便利ですが、単純に外部コンテンツを現在のページに含みたい場合はincludeタグが使用できます。

<g:include controller="book" action="list" />

柔軟な対応をするために、applyLayoutタグとincludeタグを一緒に利用できます:

<g:applyLayout name="myLayout">
   <g:include controller="book" action="list" />
</g:applyLayout>

もちろん、includeタグをメソッドとして、コントローラやタグライブラリで使用できます:

def content = include(controller:"book", action:"list")

コンテントの結果は、includeタグの返値で提供されます。

6.2.5 静的リソース

2.0から、洗練された静的リソース管理を提供するResourcesプラグインを統合しています。このプラグインはデフォルトで新規Grailsアプリケーションにインストールされます。

静的リソースのリンクを含む場合、通常resourceタグを使用します。これはURIでファイルを指定する簡単なアプローチになります。

しかし近頃のアプリケーションは、様々なJavaScriptやCSSライブラリやフレームワークに依存しており、もっとパワフルな方法が必要となります。

Resourceフレームワークの取り組んだ課題:
  • Webアプリケーションのパフォーマンスチューニングが難しい
  • リソースの配置順と、JavaScriptの遅延包括
  • 先に読み込む必要のある依存関係のあるリソース
  • プラグインやアプリケーションでの、静的リソースを表示する方法の標準化が必要
  • リソースの最適化を行うための拡張可能な連鎖行程が必要
  • 同じリソースの多重配置を防ぐ

プラグインでアーテファクトの提供と、サーバのローカルファイルシステムを使用して処理をすることで、これらを達成しています。

プラグインは、リソースを宣言するためのアーテファクト、リソースを処理する"マッパー"、処理されたリソースを提供するサーブレットフィルタを追加します。

信じられないほど上級のリソースシステムが、簡単に高度に最適化されたWebアプリケーション作成をdevelpoment,productionモード共に可能とします。

リソースプラグインのドキュメントでは、さらに詳細な内容を提供しています。

6.2.5.1 リソースタグでのリソースの使用

r:requireでリソースを制御する

リソースを使用するにはどのリソースモジュールが必要がGSPページで示す必要があります。例として、"jquery"リソースモジュールを提供しているjQueryプラグインを、サイトの全てのページで使用する場合は単に次のように追加するだけです:

<html>
   <head>
      <r:require module="jquery"/>
      <r:layoutResources/>
   </head>
   <body><r:layoutResources/>
   </body>
</html>

これで自動的にjQueryで必要なリソースをページの正確な場所に配置します。デフォルトではプラグインが"head"に配置するので、ページの初めの方でロードされます。

GSPページでr:requireを複数回呼んでも構いません。そして"modules"属性を使用してモジュールを列挙することもできます:

<html>
   <head>
      <r:require modules="jquery, main, blueprint, charting"/>
      <r:layoutResources/>
   </head>
   <body><r:layoutResources/>
   </body>
</html>

上記の結果は、多くのJavaScriptとCSSファイルが正確な順番に、そして一部のJavaScriptはページ読み込み時間を向上させるために、ページボディの最後にロードされるようにページに含まれます。

r:requireは単体で使用することはできません。例にあるように実際に描写を行う<r:layoutResources/>タグが必要になります。

r:layoutResourcesでリソースへのリンクを描写する

GSPページに必要なリソースモジュールを宣言すると、フレームワークは正常なタイミングでリソースへのリンクを描写する必要があります。

これを正常に行うには、r:layoutResourcesタグをページまたはGSPレイアウトの2ヶ所に配置します。:

<html>
   <head>
      <g:layoutTitle/>
      <r:layoutResources/>
   </head>
   <body>
      <g:layoutBody/>
      <r:layoutResources/>
   </body>
</html>

これはリソースフレームワークに対応した、いちばん単純なSitemeshレイアウトの例です。

リソースフレームワークには全てのリソースに"配置"の概念を持っています。ページのどの部分にリソースを含めるべきかを指示する物です。

基本的な配置はリソースの種類によって適用されます。全てのCSSはHTMLの<head>に描写されるべきです。したがって全てのCSSは、デフォルトで最初のr:layoutResourcesによって"head"に描写されます。JavaScriptはページコンテンツの最後に読み込ませてJavaScriptファイルの実行を遅らせることで("defer")、ページのロード時間が向上されます。したがって2つ目のr:layoutResourcesが実行された時に描写されます。

GSPページとSItemeshレイアウト両方で(もちろんGSPテンプレートでも)、リソースに依存するr:requireを呼び出す事ができます。唯一の制約として、内容を描写するr:layoutResourcesの前に必ずr:requireを呼び出す必要があります。

r:scriptでページ固有のJavaScriptコードを追加

Grailsにはリソースプラグインがインストールされている場合には、リソースプラグインに従うjavascriptタグがありますが、JavaScriptコードを含みたい場合は、直接r:scriptを呼ぶことを推奨します。

これはJavaScriptをインラインで記述できて描写はインラインでは無く、配置指定をした<head>またはボディの最後に描写されます。

このようなSitemeshレイアウトがあるとします:

<html>
   <head>
      <g:layoutTitle/>
      <r:layoutResources/>
   </head>
   <body>
      <g:layoutBody/>
      <r:layoutResources/>
   </body>
</html>

GSPではJavaScriptコードをヘッドまたページの最後の方に以下のように記述します:

<html>
   <head>
      <title>Testing r:script magic!</title>
   </head>
   <body>
      <r:script disposition="head">
         window.alert('This is at the end of <head>');
      </r:script>
      <r:script disposition="defer">
         window.alert('This is at the end of the body, and the page has loaded.');
      </r:script>
   </body>
</html>

disposition属性のデフォルトは"defer"になります。従って後者のr:scriptはここでの説明として定義しているだけです。

r:scripのコードブロックは、必要なライブラリがロードされているのを保証するために、常に指定したモジュールの後に読み込まれます。

r:imgで画像へのリンク

このタグは、リソースフレームワークを使用してリソースを処理した、<img>タグを描写するのに使用します。(定義をした場合。e.g.画像キャッシュ等)

あらかじめリソースモジュール宣言されたタグの属性も<img>タグの属性に追加します。

この仕組みでは、モジュールに宣言した、width,heightまたその他の属性をタグに取り込みます。

例として:

<html>
   <head>
      <title>Testing r:img</title>
   </head>
   <body>
      <r:img uri="/images/logo.png"/>
   </body>
</html>

Grailsには、<img>タグを描写する、リソース参照できるg:imgタグが存在します。Grailsのimgタグは、存在した場合リソースフレームワークが認識してr:imgに委譲しますが、リソースプラグインを使用している場合は直接r:imgを使用することを推奨します。

通常のGrails imgタグと一緒で、簡潔に記述できる"uri"属性にも対応しています。

詳細はResourcesプラグインのドキュメントを参照。

6.2.5.2 他のリソースタグ

r:resource

これはGrails resourceタグと同じで静的リソースへのリンクを処理して返します。Grailsのg:resourceタグが有った場合はこのタグに委譲します。リソースプラグインを使用している場合は直接r:resourceを使用しましょう。

通常のGrails resourceタグと一緒で、簡潔に記述できる"uri"属性にも対応しています。

詳細はResourcesプラグインのドキュメントを参照。

r:external

Grailsでのexternalタグのリソースフレームワーク版です。CSS,JS,favicon等の外部リソースを含む為のHTMLマークアップを描写します。

詳細はResourcesプラグインのドキュメントを参照。

6.2.5.3 リソースの宣言

リソースとモジュールを宣言するためのDSLが提供されています。アプリケーション固有の場合はConfig.groovyファイルに記述、また通常はリソースアーテファクトとしてgrails-app/confにファイルで配置します。

特に画像に関しては、全ての静的リソースを宣言する必要はありませんが、依存関係またはリソース固有の属性を確立する必要があります。宣言されていないリソースは"ad-hoc"とよばれ、リソースの種類に対応した処理が行われます。

次のようなリソース定義がgrails-app/conf/MyAppResources.groovyファイルで例として有るとします:

modules = {
    core {
        dependsOn 'jquery, utils'

resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js' resource url: '/css/main.css', resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }

utils { dependsOn 'jquery'

resource url: '/js/utils.js' }

forms { dependsOn 'core,utils'

resource url: '/css/forms.css' resource url: '/js/forms.js' } }

ここでは'core','utils','forms'の3つのリソースモジュールを定義しています。このモジュールのリソースは、モジュール名に応じて、すぐに使えるように、より少ないファイルに自動的にバンドルされます。それぞれのリソースはbundle:'someOtherName'またはモジュールでdefaultBundleを呼ぶことででオーバーライドできます。(Resourcesプラグインドキュメントを参照)

リソースの読み込み順を操作するために、それぞれの依存関係をdependsOnで宣言しています。

GSPに<r:require module="forms"/>を含んだ場合、この例では'core','util'そして'jquery'のリソースを正常な順番で引っ張ります。

さらにcore.jsファイルのdisposition:'head'に注目してください。その他全てのリソースはボディの最後に配置して、このファイルは必ず<head>に配置する指定をしています。

プリント指定用のCSSでは、カスタム属性をattrsマップで追加しています。そしてこれらはr:externalタグを通してリソースへのリンクを形成します。これによってリンクのHTML属性をカスタマイズすることができます。

モジュールやxxxResources.groovyアーテファクトには数量制限がありません。jQueryプラグインのようにプラグインでモジュールを提供することもできます。

アプリケーションのConfig.groovyでモジュールを宣言する場合は、モジュールのDSLクロージャをgrails.resources.modulesに定義します。

リソースDSLの詳細はResourcesプラグインのドキュメントを参考にしてください。

6.2.5.4 プラグインリソースのオーバーライド

リソースモジュールにバンドルのグルーピングと他のリソース属性の宣言ができることで、提供されている設定が個々のアプリケーションで違う場合が有るのを認識します。

例として、jQueryと他のライブラリをまとめてバンドルするとします。これだとロード時間とキャッシングのトレードオフになりますが、しかし良くあるケースとしてこれらの設定をオーバーライドするとします。

これには、モジュールまたは固有のリソース属性で変更可能なdefaultBundle設定で定義した名称を指定する、DSLの"overrides"で対応しています。

modules = {
    core {
        dependsOn 'jquery, utils'
        defaultBundle 'monolith'

resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js' resource url: '/css/main.css', resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }

utils { dependsOn 'jquery' defaultBundle 'monolith'

resource url: '/js/utils.js' }

forms { dependsOn 'core,utils' defaultBundle 'monolith'

resource url: '/css/forms.css' resource url: '/js/forms.js' }

overrides { jquery { defaultBundle 'monolith' } } }

この設定で全てのコードが'monolith'という名称の一つのバンドルになります。この設定を行ってもそれぞれのバンドルとしてそれぞれのファイルで配置されます。

固有のリソースをオーバライドする場合は、もとの宣言がリソース毎にユニークIDで含まれている必要があります。

For full details of the resource DSL please see the resources plugin documentation. リソースDSLの詳細はリソースプラグインのドキュメントを参考にしてください。

6.2.5.5 リソースの最適化

リソースフレームワークは"マッパー"を使用して、最終フォーマットへリソースを変化させてユーザへ配信します。

リソースマッパーはそれぞれの静的リソースに一度だけ特定の順番で適用されます。幾つかのプラグインzipping, caching や minifyingのように独自のリソースマッパーを作成することが可能です。

すぐに使用できる仕組みとして、リソースをまとめて少なくするバンドルマッパーをResourcesプラグインで提供しています。

複数のリソースを少ないファイルにまとめる

バンドルマッパーは、"bundle"が定義されたリソース、またはモジュールのdefaultBundleに継承されたリソースに対してデフォルトで操作します。モジュールはモジュール名と同じ名称で、暗黙なデフォルトバンドル名を持っています。

同じ種類のファイルがこのバンドルに集計されます。バンドルはモジュールにわたって操作されます:

modules = {
    core {
        dependsOn 'jquery, utils'
        defaultBundle 'common'

resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js', bundle: 'ui' resource url: '/css/main.css', bundle: 'theme' resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }

utils { dependsOn 'jquery'

resource url: '/js/utils.js', bundle: 'common' }

forms { dependsOn 'core,utils'

resource url: '/css/forms.css', bundle: 'ui' resource url: '/js/forms.js', bundle: 'ui' } }

この例でモジュールにわたって、リソースは'common','ui','theme'のグループにまとめられます。

もしモジュールにリソースが1つの場合は、自動バンドリング処理は行われません。

クライアントのブラウザでリソースを"永久的に"キャッシュさせる

リソースを"永久的に"キャッシュさせるには、コンテンツが変わらない限り唯一のリソース名を持ち、キャッシングヘッダーがレスポンスにセットされている時のみです。

cached-resourcesプラグインでは、ファイルをハッシュ管理してハッシュ管理に基づいて名称を変更するマッパーを提供しています。さらにキャッシングヘッダーを全てのリソースのレスポンスにセットします。これを使用するには単にcached-resourcesをインストールするだけです。

キャッシングヘッダーはリソースがアプリケーションから配信されたときのみセットされます。もし別のサーバで静的コンテンツを配信している場合は(Apache HTTPD等で)、サーバでそれらのキャッシングヘッダーの定義を行ってください。あるいはリソースをアプリケーションコンテナーを介して配信するように設定も可能です。

リソース(Zip)圧縮

ページロード時間と帯域を減らす別の方法として、gzipしたリソースを返すことができます。

zipped-resourcesプラグインは、デフォルトで圧縮されている画像等を除いたコンテンツを自動的に圧縮するマッパーを提供しています。

単にzipped-resourcesプラグインをインストールするだけで使用できます。

最小化(Minifying)

最小化してコード内容をわかりにくくしたりサイズを縮小する幾つかのCSSとJavascript minifierが有ります。現段階ではプラグインの提供は無いですが近々リリースされると思います。

6.2.5.6 デバッグ

リソースが、リソースフレームワークにより、動き回り、名称変更されたり、変更されるとクライアントサイドのデバッグが大変になります。最近のブラウザSafari,Chrome,Firefoxには、リクエストでのページの全てのリソースやヘッダーやその他の情報が閲覧できる優秀なツールがあります。

リソースフレームワークには、幾つかのデバッグ時に使用する機能を持ち合わせています。

X-Grails-Resources-Original-Srcヘッダー

開発(development)モードでのリソースには、レスポンスを構成した元のリソースファイルを示す、X-Grails-Resources-Original-Src:ヘッダーが追加されています。

デバッグフラグを追加

クエリパラメータ _debugResources=y をURLに追加してページをリクエストすると、Resourcesプラグインはバイパスされて元のソースファイルを見ることができます。

さらにこの機能では、ユニークなタイムスタンプを全てのリソースに追加することでブラウザにキャッシュされるのを阻止します。したがってページをリロードするたびに最新のコードを参照することになります。

常にデバッグを有効にする

前途したデバッグ機能をクエリパラメータ無しで有効にするには、以下のようにConfig.groovyで設定します:

grails.resources.debug = true

もちろんこの設定は環境ごとに定義できます。

6.2.5.7 リソース生成・変換処理を防ぐ

特定のリソースだけ変換処理を行わない事もあります。時折リソースマッピングを無効にしたいこともあります。

個々のリソースの特定のマッパー処理を防ぐ

全てのリソース宣言では、XXXXがマッパー名称とすると、noXXXX:trueのような規約に対応しています。

例として、リソースに適用される"hashandcache"マッパーの処理を防ぐ場合は:

modules = {
    forms {
        resource url: '/css/forms.css', nohashandcache: true
        resource url: '/js/forms.js', nohashandcache: true
    }
}

特定のマッパーでの、パス、ファイルタイプの除外と包含

マッパーには、Antパターンで対象リソースを制御するincludes/excludes定義があります。各マッパーはそれぞれの動作に対応したふさわしいデフォルトをセットしています。例としてzipped-resourcesプラグインの"zip"マッパーはデフォルトで画像を除外する設定をしています。

Config.groovyにマッパー名で定義することができます:

// We wouldn't link to .exe files using Resources but for the sake of example:
grails.resources.zip.excludes = ['**/*.zip', '**/*.exe']

// Perhaps for some reason we want to prevent bundling on "less" CSS files: grails.resources.bundle.excludes = ['**/*.less']

逆の意味の"includes"も使用できます。設定をするとマッパーデフォルトのincludes/excludesを入れ換えるので注意が必要です。

"ad-hoc"(レガシー)リソースとして扱う物の操作

"ad-hoc"リソースは、宣言されずGrailsまたResourcesのリンク系タグを使用しないで、直接アプリケーションにリンクされているリソースです。

古い方式のプラグインやパスがコードに埋め込まれている場合をさします。

サーブレットAPI準拠のフィルタURIマッピングのリストを定義して、どのリクエストを"ad-hoc"リソースとしてResourcesのフィルタが使用するか認識させるConfig.groovy用の設定 grails.resources.adhoc.patterns があります。

デフォルトでは次のようにセットされています:
grails.resources.adhoc.patterns = ['images/*', '*.js', '*.css']

6.2.5.8 リソース関連のプラグイン

現時点でのリソースフレームワークに対応したプラグイン:

6.2.6 Sitemeshコンテントブロック

サイト全体のページをデコレートするのに便利ですが、ページ内のブロック(パーツ)をデコレートする必要も有ると思います。この場合はコンテントブロックを使用します。コンテントブロックを使用するには<content>タグでページ内をブロック分割します。

<content tag="navbar">
… ここにナビゲーションバーコンテンツ…
</content>

<content tag="header"> … ここにヘッダーコンテンツ… </content>

<content tag="footer"> … ここにフッターコンテンツ… </content>

<content tag="body"> … ここにボディコンテンツ… </content>

そしてレイアウト側には、それぞれのコンポーネントの参照を配置します:

<html>
    <body>
        <div id="header">
            <g:applyLayout name="headerLayout">
                <g:pageProperty name="page.header" />
            </g:applyLayout>
        </div>
        <div id="nav">
            <g:applyLayout name="navLayout">
                <g:pageProperty name="page.navbar" />
            </g:applyLayout>
        </div>
        <div id="body">
            <g:applyLayout name="bodyLayout">
                <g:pageProperty name="page.body" />
            </g:applyLayout>
        </div>
        <div id="footer">
            <g:applyLayout name="footerLayout">
                <g:pageProperty name="page.footer" />
            </g:applyLayout>
        </div>
    </body>
</html>

6.2.7 デプロイされたアプリケーションへの変更

Grailsアプリケーションをデプロイする際に起きる大きな問題の一つに(または他のサーブレットベースのアプリケーションも含む)、ビューを変更するたびにアプリケーション全体を再デプロイする必要がある事です。もし誤植や画像リンクの修正など有る場合は無駄な作業が発生します。そのような事を発生させないために、Grailsには、grails.gsp.view.dir定義という解決方法が存在します。

はじめに、何処にGSPを配置するかを決めましょう。/var/www/grails/my-appの階層はパックしないこととします。以下の2行をgrails-app/conf/Config.groovyに追加します:

grails.gsp.enable.reload = true
grails.gsp.view.dir = "/var/www/grails/my-app/"
最初の行は変更されたGSPファイルはリロードするという指定です。この指定をしないと、GSPが更新されても再起動するまで反映されません。2行目は何処にあるビューとレイアウトをロードするかの指定です。

grails.gsp.view.dirの最後に付いているスラッシュは重要です。これが無いとGrailsが親ディレクトリにビューを探しに行きます。

"grails.gsp.view.dir"の設定は省略可能です。省略した場合はwarがデプロイされたディレクトリのファイルを変更できます。アプリケーションサーバによりますが、サーバのリスタート時にこれらのファイルは上書きされてしまいます。ホット再デプロイメントをサポートしたアプリケーションサーバの場合にのみ推奨します。

この場合に、サーバ内で設定をするには、Webアプリケーションのディレクトリから別のディレクトリへ全てのビューをコピーします。Unix系のシステムでは以下のように作業します:

mkdir -p /var/www/grails/my-app/grails-app/views
cp -R grails-app/views/* /var/www/grails/my-app/grails-app/views
ポイントとなるのは、grails-app/viewsを含んだ全てのビューディレクトリ階層を持たなくてはならない所です。この場合だとパスは、/var/www/grails/my-app/grails-app/views/...となります。

ひとつ気にかけてほしいのが、GSPを更新するたびに、permgenスペースを多く使用するという事です。それにより再起動するのと比べて、そのうち"out of permgen space"エラーが起こります。したがって、常に更新される物や、ビューへの大きな変更には推奨できません。

GSPリロードをコントロールするシステムプロパティも提供しています:
名称説明既存値
grails.gsp.enable.reloadConfig.groovy変更せずにGSPリロードモードを許可にします。 
grails.gsp.reload.intervalgspソースファイルの更新を確認するインターバル。ミリ秒で指定。5000
grails.gsp.reload.granularityファイルが更新されたかを判断する時間のゆとり。ミリ秒で指定。1000

プリコンパイルGSPに対してのGSPリロードはGrails 1.3.5からサポートされています。

6.2.8 GSPデバッグ

生成されたソースコードを表示する

  • URLに、 "?showSource=true" または、"&showSource=true"を追加するとページ描写の代わりに、ビュー用に生成されたGroovyコードが参照できます。但し含んだテンプレートのコードは含まれません。開発モードのみで動作します。
  • Config.groovyにプロパティ"grails.views.gsp.keepgenerateddir"を指定すると生成ファイルが指定された場所に保存されます。
  • "grails war"でのgspプリコンパイル時に、生成されたソースコードがgrails.project.work.dir/gspcompileに保存されます。(通常は~/.grails/(grails_version)/projects/(project name)/gspcompileです。)

デバッガでGSPコードをデバッグ

使用されたテンプレートの情報を参照

規模の大きいなWebアプリケーションでは、g:renderタグを使用してGSPテンプレートは再利用されます。幾つかの小さなテンプレートが1つのページで使われる事もあります。どの部分でGSPテンプレートが実際にHTMLを描写したか探すのは大変だと思います。テンプレートデバッグ機能ではhtmlコメントでGSPテンプレートのデバッグ情報を提供します。

使用方法は簡単、URLに"?debugTemplates" または "&debugTemplates"を追加して表示されたページのソースを参照します。"debugTemplates"は開発モードのみで動作します。

debugTemplatesでは以下のようなコメントが追記されます:

<!-- GSP #2 START template: /home/.../views/_carousel.gsp
     precompiled: false lastmodified: … -->
.
.
.
<!-- GSP #2 END template: /home/.../views/_carousel.gsp
     rendering time: 115 ms -->

それぞれのコメントブロックには番号を持っていて、テンプレートの開始と終了を探し安くしてあります。

6.3 タグライブラリ

Java Server Pages (JSP)のように、GSPでもカスタムタグライブラリをサポートしています。JSPと違いGrailsでのタグライブラリは単純簡潔です。

タグライブラリを作成するには、grails-app/taglibディレクトリに、名称の最後がTagLibとなるGroovyクラスを作成します:

class SimpleTagLib {

}

タグライブラリクラスを作成したら、2つの引数(属性とタグ内容)を受け取るクロージャを追加してタグを作成します:

class SimpleTagLib {
    def simple = { attrs, body ->

} }

The attrs argument is a Map of the attributes of the tag, whilst the body argument is a Closure that returns the body content when invoked: 引数attrsはタグ属性のMapです。引数bodyはタグ内容のコンテンツを呼び出すクロージャです:

class SimpleTagLib {
    def emoticon = { attrs, body ->
       out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
    }
}

上記の例では、レスポンスを出力するWriterを参照する変数outに対してコンテンツを追加しています。ここまで作成したら、以下のように、GSPの中でタグをそのまま参照します。importは必要ありません。:

<g:emoticon happy="true">Hi John</g:emoticon>

タグリブはGroovyコードを使用しているため、タグで使用可能な属性全てを認識することは困難です。SpringSource Tool Suite (STS)等のIDEでの補完機能に対しての手助けとして、タグクロージャのJavadocコメント内に@attrを含めましょう。

例として:

class SimpleTagLib {

/** * Renders the body with an emoticon. * * @attr happy whether to show a happy emoticon ('true') or * a sad emoticon ('false') */ def emoticon = { attrs, body -> out << body() << (attrs.happy == 'true' ? " :-)" : " :-(") } }

必須条件の属性には、キーワードREQUIREDを指定します。

class SimpleTagLib {

/** * Creates a new password field. * * @attr name REQUIRED the field name * @attr value the field value */ def passwordField = { attrs -> attrs.type = "password" attrs.tagName = "passwordField" fieldImpl(out, attrs) } }

6.3.1 変数とスコープ

タグライブラリのスコープには、多数の前定義された変数があります:

  • actionName - 現在実行中のアクション名
  • controllerName - 現在実行中のコントローラ名
  • flash - flashオブジェクト
  • grailsApplication - GrailsApplicationインスタンス
  • out - 出力用のレスポンスライター
  • pageScope - GSP描写で使用するpageScopeオブジェクトへの参照
  • params - リクエストパラメータ取得用のparamsオブジェクト
  • pluginContextPath - タグライブラリを含むプラグインへのコンテキストパス。
  • request - HttpServletRequestインスタンス
  • response - HttpServletResponseインスタンス
  • servletContext - javax.servlet.ServletContext インスタンス
  • session - HttpSessionインスタンス

6.3.2 簡単なタグ

前の説明で、簡単にコンテンツを出力するタグは簡単に記述できると説明しました。他の例としてdateFormatタグを紹介します:

def dateFormat = { attrs, body ->
    out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}

この例ではJavaのSimpleDateFormatクラスを使ってフォーマットした日付をレスポンスに出力します。このタグは、GSPで次のように使用します:

<g:dateFormat format="dd-MM-yyyy" date="${new Date()}" />

単純なタグでHTMLマークアップをレスポンスすることもあるでしょう。一つのアプローチとして直接コンテンツを埋め込む事ができます:

def formatBook = { attrs, body ->
    out << "<div id="${attrs.book.id}">"
    out << "Title : ${attrs.book.title}"
    out << "</div>"
}

上記の方法はあまりきれいでは無いので、実際には、renderタグを使用すると良いでしょう:

def formatBook = { attrs, body ->
    out << render(template: "bookTemplate", model: [book: attrs.book])
}

このようにして、実際に描写されるGSPテンプレートと切り離せます.

6.3.3 制御タグ

条件に合ったときのみにタグ内容を出力する制御タグを作成できます。セキュリティタグを例とします:

def isAdmin = { attrs, body ->
    def user = attrs.user
    if (user && checkUserPrivs(user)) {
        out << body()
    }
}

この例ではアクセスしたユーザをチェックして、管理者であった場合のみ内容を出力します:

<g:isAdmin user="${myUser}">
    // some restricted content
</g:isAdmin>

6.3.4 イテレートタグ

タグ内容を複数回実行するような、イテレートタグも簡単に作れます:

def repeat = { attrs, body ->
    attrs.times?.toInteger()?.times { num ->
        out << body(num)
    }
}

この例では、属性timesを有無や数値なのかを確認し、その数値でGroovyのtimesメソッドを実行して、与えられた数値の回数、実行内容を繰り返します:

<g:repeat times="3">
<p>Repeat this 3 times! Current repeat = ${it}</p>
</g:repeat>

ここでは、数値を参照した変数itの使い方を紹介しています。これはbody実行時にイテレーションの数値をbodyに渡すことで動作しています。:

out << body(num)

この値はタグ内の変数itに渡されます。ただしタグをネストした場合は名称の衝突が起きるため違う名称を指定できるようにする必要があります。:

def repeat = { attrs, body ->
    def var = attrs.var ?: "num"
    attrs.times?.toInteger()?.times { num ->
        out << body((var):num)
    }
}

この例では、属性varが存在すれば、その値を名称として使用できるようにbodyに渡して実行します。

out << body((var):num)

変数名パーレン使用時の注意点です。これを省略するとGroovyは文字列キーとして解釈してしまい変数として参照できません。

これで、以下のようにタグで使用できます:

<g:repeat times="3" var="j">
<p>Repeat this 3 times! Current repeat = ${j}</p>
</g:repeat>

こうすることで、var属性に指定した変数名jをタグ内部で変数として参照することができます。

6.3.5 タグネームスペース

デフォルトでのタグは、Grailsのデフォルトネームスペースに追加され、GSPではg:接頭辞が使用できます。ネームスペースはstaticプロパティnamespaceTagLibクラスに追加する事で、独自に定義することが可能です。:

class SimpleTagLib {
    static namespace = "my"

def example = { attrs -> … } }

この例ではnamespacemyと定義したので、このタグを参照するにはGSPページ内では以下のように参照できます:

<my:example name="..." />

これ例でnamespaceプロパティに設定したものが接頭辞として使用しています。ネームスペースはプラグインでタグ提供を行う際に有用です。

ネームスペース付きタグのメソッドコール時はネームスペースを接頭辞として使用します:

out << my.example(name:"foo")

GSP、コントローラ、タグライブラリで使用できます。

6.3.6 JSPタグライブラリの使用

単純なタグライブラリ機能がGSPで提供されているのに加えて、GSPでは、JSPタグを使用することもできます。使用するには、taglibディレクティブを定義します。

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

他のタグと同じように使用できます:

<fmt:formatNumber value="${10}" pattern=".00"/>

さらに追加として、JSPタグをメソッドとして実行可能です:

${fmt.formatNumber(value:10, pattern:".00")}

6.3.7 タグの戻り値

Grails 1.2以降、タグライブラリを呼び出すと、デフォルトではorg.codehaus.groovy.grails.web.util.StreamCharBuffer クラスのインスタンスを返します。 この実装でリクエスト処理時のバッファリング最適化や、オブジェクト生成の減少によりパフォーマンスの向上が行われました。 それ以前のGrailsではjava.lang.Stringインスタンスを返します。

さらに、Grails 1.2からは、タグライブラリから直接オブジェクトを返す事が可能です。オブジェクトを返すタグを定義するにはstatic returnObjectForTagsプロパティにタグ名称をリストします。

例:
class ObjectReturningTagLib {
    static namespace = "cms"
    static returnObjectForTags = ['content']

def content = { attrs, body -> CmsContent.findByCode(attrs.code)?.content } }

6.4 URLマッピング

ここまでのドキュメント内容では、URLのルールはデフォルトの/controller/action/idを使用しています。このルールはGrailsで必須になっているわけでは無く、grails-app/conf/UrlMappings.groovyに配置されているURLマッピングクラスでコントロールされています。

URLMappingsクラスは、コードブロックが定義されたmappingsというプロパティを持っています。:

class UrlMappings {
    static mappings = {
    }
}

6.4.1 コントローラとアクションにマッピング

簡単なマッピングは、相対URLをメソッド名とするメソッドに、マップ対象のコントローラとアクション名称を、名前付きパラメータ引数として作成します:

"/product"(controller: "product", action: "list")

この例ではURL /productは、ProductControllerlistアクションにマップ定義したことになります。アクションを省略すると、コントローラのデフォルトアクションにマップ定義できます:

"/product"(controller: "product")

他のシンタックスとして、コントローラとアクションを指定したコードブロックをメソッドに渡すこともできます:

"/product" {
    controller = "product"
    action = "list"
}

どちらのシンタックスを使用するかは個人の選択に依存します。(コントローラ・アクションを指定するのでは無く)URIを明示的なURIへ書き換えする場合は:

"/hello"(uri: "/hello.dispatch")

特定のURIへの書き換えは、他のフレームワークと統合する場合に便利です。

6.4.2 埋込変数

簡単な変数

前セクションでは、具体トークンURLを簡単にマップする方法を紹介しました。URLマッピングでのトークンとは"/"スラッシュの間にある文字列の事を指します。具体トークンは/productのように適切に指定できますが、状況によっては、ランタイム時にしか一部のトークンが予測できない場合があります。その場合は以下の例のように変数を指定できます:

static mappings = {
  "/product/$id"(controller: "product")
}

この例では、2つめのトークンとして変数$idを埋め込むことで、Grailsが自動的に2つめのトークンを名称がidのパラメータとしてマップします(このパラメータはparamsオブジェクトから取得できます)。例として、URL /product/MacBookへアクセスを行うと、次のコードでは、結果として"MacBook"をレスポンスします:

class ProductController {
     def index() { render params.id }
}

もっと複雑なマッピングも作成できます。例としてブログのようなURLフォーマットを次のようにしてマッピングできます:

static mappings = {
   "/$blog/$year/$month/$day/$id"(controller: "blog", action: "show")
}

このマッピングでは、以下のようなURLでアクセスを可能にします:

/graemerocher/2007/01/10/my_funky_blog_entry

URLの個々のトークンはparamsオブジェクトにマップされるため、変数blog, year, month, day, idとして値を取得する事ができます。

動的なコントローラ名とアクション名

変数を使って動的にコントローラとアクション名を構成することができます。GrailsでのデフォルトURLマッピングはこの機能を使用しています:

static mappings = {
    "/$controller/$action?/$id?"()
}

URLに指定された内容から、変数controller,action,idを取得して、コントローラ、アクション、idを確定します。

コントローラ名の解決を変数で行い、クロージャを実行して動的にアクション名を解決することもできます:

static mappings = {
    "/$controller" {
        action = { params.goHere }
    }
}

省略可能な変数

変数の最後に ? を付加することで、変数を省略可能にすることが可能です。この仕組みを使用して、前述した例のブログURLマッピングをもっと柔軟にしてみましょう:

static mappings = {
    "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

この設定で以下のURL全てマッチします、そしてそれぞれの関係のあるパラメータのみをparamsオブジェクトに追加します:


/graemerocher/2007/01/10/my_funky_blog_entry
/graemerocher/2007/01/10
/graemerocher/2007/01
/graemerocher/2007
/graemerocher

任意変数

URLマッピングからコントローラへ、コードブロック内で設定した任意のパラメータを渡すことが可能です:

"/holiday/win" {
     id = "Marrakech"
     year = 2007
}

変数はparamsオブジェクトから参照可能になります。

動的解決変数

任意の変数をURLマッピングに埋め込むのは便利ですが、時折状況に応じて変数の内容を演算したい場合があると思います。この場合コードブロックを変数に割り当てます。:

"/holiday/win" {
     id = { params.id }
     isEligible = { session.user != null } // must be logged in
}

URLが実際にマッチした時にコードブロックのロジックが実行されて変数の内容が解決されます。

6.4.3 ビューへマッピング

コントローラまたはアクションを含まずにビューへURLをマップできます。例としてルートURL / を、grails-app/views/index.gspにマッピングするには:

static mappings = {
    "/"(view: "/index")  // map the root URL
}

あるいは、特定のコントローラにあるビューを指定する場合は:

static mappings = {
   "/help"(controller: "site", view: "help") // to a view for a controller
}

6.4.4 レスポンスコードへマッピング

GrailsではHTTPレスポンスコードをコントローラ、アクション、ビューにマッピングすることができます。使用したいレスポンスコードをメソッド名に指定するだけです:

static mappings = {
   "403"(controller: "errors", action: "forbidden")
   "404"(controller: "errors", action: "notFound")
   "500"(controller: "errors", action: "serverError")
}

カスタムエラーページを指定する場合は:

static mappings = {
   "403"(view: "/errors/forbidden")
   "404"(view: "/errors/notFound")
   "500"(view: "/errors/serverError")
}

エラーハンドリング宣言

特定の例外をのハンドラを定義することも可能です:

static mappings = {
   "403"(view: "/errors/forbidden")
   "404"(view: "/errors/notFound")
   "500"(controller: "errors", action: "illegalArgument",
         exception: IllegalArgumentException)
   "500"(controller: "errors", action: "nullPointer",
         exception: NullPointerException)
   "500"(controller: "errors", action: "customException",
         exception: MyException)
   "500"(view: "/errors/serverError")
}

この設定では、IllegalArgumentExceptionErrorsControllerのillegalArgumentNullPointerExceptionnullPointerアクション、MyExceptioncustomExceptionアクションでそれぞれ処理されます。その他の例外がcatch-allルールでハンドルされビューに/errors/serverErrorを使用します。

カスタムエラーハンドリングで、ビューまたは、コントローラアクションから例外を参照するには、requestインスタンスのexceptionを使用します:

class ErrorController {
    def handleError() {
        def exception = request.exception
        // perform desired processing to handle the exception
    }
}

エラーハンドリング用のコントローラアクションが例外を出した場合は、最終的にStackOverflowExceptionになります。

6.4.5 HTTPメソッドへマッピング

URLマッピングでは、HTTPメソッド(GET, POST, PUT, DELETE)へのマップを定義することができます。HTTPメソッドをベースに制限したり、RESTful API等に便利です。

このマッピングの例ではProductController用にRESTful API URLマッピングを提供しています:

static mappings = {
   "/product/$id"(controller:"product") {
       action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
   }
}

6.4.6 マッピングワイルドカード

GrailsのURLマッピング機能ではワイルドカードもサポートしています。例として次ぎのマッピングに注目してください:

static mappings = {
    "/images/*.jpg"(controller: "image")
}

このマッピングが/image/logo.jpgのような画像パスにマッチします。変数も使用可能です:

static mappings = {
    "/images/$name.jpg"(controller: "image")
}

ダブルワイルドカード使用すると1レベル下の階層までマッチします:

static mappings = {
    "/images/**.jpg"(controller: "image")
}

このマッピング例では、/image/logo.jpgや、/image/other/logo.jpgにマッチします。ダブルワイルドカード変数も使用できます:

static mappings = {
    // will match /image/logo.jpg and /image/other/logo.jpg
    "/images/$name**.jpg"(controller: "image")
}

この例では、nameパラメータにマッチしたパスをparamsオブジェクトから取得できます:

def name = params.name
println name // prints "logo" or "other/logo"

ワイルドカードURLマッピングを使用すると、一定のURIをURLマッピングでの除外も必要となります。UrlMappings.groovyクラスで、excludesを指定することで可能です:

class UrlMappings {
    static excludes = ["/images/*", "/css/*"]
    static mappings = {
        …
    }
}

この例では、 /imagesまた/cssで始まるURIはURLマッピングから除外されます.

6.4.7 自動リンクリライト

URLマッピングには、linkタグの振る舞いを自動的にカスタマイズ するという優れた機能が存在します。その機能のおかげでURLマッピングを変更しても、その都度リンクを変更しなくても良くなります。

URLマッピングからリバースエンジニアリングによってURLをリライトするという仕組みで実装されています。前のセクションからのブログマッピングを例があるとします:

static mappings = {
   "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

以下のようにlinkタグを使用します:

<g:link controller="blog" action="show"
        params="[blog:'fred', year:2007]">
    My Blog
</g:link>

<g:link controller="blog" action="show" params="[blog:'fred', year:2007, month:10]"> My Blog - October 2007 Posts </g:link>

GrailsがURLを自動的にリライトします:

<a href="/fred/2007">My Blog</a>
<a href="/fred/2007/10">My Blog - October 2007 Posts</a>

6.4.8 制約の適用

URLマッピングでは、URLがマッチしてるか確認するために、バリデーション制約が使用できます。例として、前と同じブログのサンプルを使用します:

static mappings = {
   "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

以下のURLを許可します:

/graemerocher/2007/01/10/my_funky_blog_entry

もちろんこのままでは、以下のようなURLも許可します:

/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry

これが許可されるといたずらされる危険性があります。ここで!URLマッピングに制約を定義してURLトークンをバリデートします:

"/$blog/$year?/$month?/$day?/$id?" {
     controller = "blog"
     action = "show"
     constraints {
          year(matches:/\d{4}/)
          month(matches:/\d{2}/)
          day(matches:/\d{2}/)
     }
}

この例では、year,month,dayパラメータが特定のパターンにマッチするように制約を定義しています。

6.4.9 名前付きURLマッピング

URLマッピングでは、名前で連携ができるように、名前付きマッピングに対応しています。

名前付きマッピングの定義は以下のようになります:

static mappings = {
   name <mapping name>: <url pattern> {
      // …
   }
}

例として:

static mappings = {
    name personList: "/showPeople" {
        controller = 'person'
        action = 'list'
    }
    name accountDetails: "/details/$acctNumber" {
        controller = 'product'
        action = 'accountDetails'
    }
}

次のように、GSPでlinkタグからマッピングを参照します:

<g:link mapping="personList">List People</g:link>

この結果は以下のようになります:

<a href="/showPeople">List People</a>

params属性でパラメータを定義することができます.

<g:link mapping="accountDetails" params="[acctNumber:'8675309']">
    Show Account
</g:link>

この例の結果は:

<a href="/details/8675309">Show Account</a>

名前付きマッピングでは、linkタグの代わりに、linkネームスペースのタグが使用できます。

<link:personList>List People</link:personList>

この例の結果は:

<a href="/showPeople">List People</a>

linkネームスペースタグでは、パラメータを属性で指定できます。

<link:accountDetails acctNumber="8675309">Show Account</link:accountDetails>

この例の結果は:

<a href="/details/8675309">Show Account</a>

属性に指定した全ての内容は生成されたhrefに適用されてしまうので、他の属性は、attrs属性にMapで指定します。

<link:accountDetails attrs="[class: 'fancy']" acctNumber="8675309">
    Show Account
</link:accountDetails>

この例の結果は:

<a href="/details/8675309" class="fancy">Show Account</a>

6.4.10 URLフォーマットのカスタマイズ

URLでの URLマッピングは、デフォルトでキャメルケースのURLになります。例えばコントローラMathHelperControllerのアクションaddNumbersの場合、デフォルトURLでのURLは、/mathHelper/addNumbersのようになります。Grailsでは、このパターンをカスタマイズして、/math-helper/add-numbersのようなハイフン形式にキャメルケース形式を入れ換える実装が可能です。ハイフンURLを可能にするには、grails-app/conf/Config.groovygrails.web.url.converterを"hyphenated"にします。

// grails-app/conf/Config.groovy

grails.web.url.converter = 'hyphenated'

任意の方法追加するには、インターフェイスUrlConverterを実装したクラスを、ビーン名称grails.web.UrlConverter.BEAN_NAMEで追加します。Grailsがそのビーン名称をコンテキストに有ることを認識した場合、指定したクラスの実装がデフォルトとなります。この場合は、grails.web.url.converterプロパティは必要有りません。
// src/groovy/com/myapplication/MyUrlConverterImpl.groovy

package com.myapplication

class MyUrlConverterImpl implements grails.web.UrlConverter {

String toUrlElement(String propertyOrClassName) { // URLで描写するプロパティ名またはクラス名を返す } }

// grails-app/conf/spring/resources.groovy

beans = { "${grails.web.UrlConverter.BEAN_NAME}"(com.myapplication.MyUrlConverterImpl) }

6.5 Webフロー

概要

Grailsでは、Spring Web Flow をベースにしたWebフローをサポートしています。Webフローとは、処理範囲での複数のリクエストと保有するステート間の対話です。Webフローでは明示された開始と終了ステートも持っています。

WebフローにはHTTP sessionは必要有りません。代わりに、リクエストパラメータでGrailsが持ち回る、フローエクスキュージョンキーを使用した連載形式でステートを保持しています。この方式はメモリ継承やクラスタリング問題のあるHttpSessionを使用したステートフルアプリケーションの形式よりもフローをスケーラブルにします。

Webフローは本質的には、"フロー"の実行のステートの流れを管理する上級ステートマシーンです。 ステートが管理されると、Webフローが管理してくれるので、ユーザがフロー途中のアクションから入ってくる場合どうするか等を気にしなくても良くなります。これによりショッピングカート、ホテル予約などの複数ページにまたがるワークフローを持つアプリケーションにWebフローがよく合うと言う事です。

Grails 1.2からWebフローはGrailsのコア機能では無くなりました。この機能を使用するには、Webフロープラグインをインストールする必要があります: grails install-plugin webflow

フローの作成

フローを作成するには、名称がFlowで終わるアクションをGrailsの通常にコントローラに作成します。例として:

class BookController {

def index() { redirect(action: "shoppingCart") }

def shoppingCartFlow = { … } }

フローをアクションとしてリダイレクトまたは参照をするには、接尾辞のFlowを省略します。言い換えれば上記のフローのアクション名はshoppingCartとなります。

6.5.1 開始と終了のステート

前述したように、Webフローでは明示された開始と終了ステートも持っています。開始ステートは、ユーザが最初に対話(またはフロー)を開始するステートです。最初のブロックのメソッドコールがGrailsフローの開始ステートです:

class BookController {
   …
   def shoppingCartFlow ={
       showCart {
           on("checkout").to "enterPersonalDetails"
           on("continueShopping").to "displayCatalogue"
       }
       …
       displayCatalogue {
           redirect(controller: "catalogue", action: "show")
       }
       displayInvoice()
   }
}

この例でのフローの開始ステートはshowCartノードになります。showCartがアクションまたリダイレクトを定義していない場合は、慣習によりgrails-app/views/book/shoppingCart/showCart.gspを参照して表示するビューステートとして見なされます。

通常のコントローラアクションと違い、ビューファイルはフロー名称のディレクトリに配置された物にマッチします:grails-app/views/book/shoppingCart

このshoppingCartフローには、2つの終了ステートがあります。一つ目のステートは、外部のコントローラアクションにリダイレクトしてフローから出るdisplayCatalogue。二つ目のステートは、何もイベント処理が無く単純に、ビューのgrails-app/views/book/shoppingCart/displayInvoice.gspを描写して同時にフローを終了する、displayInvoiceです。

フローが終了したら、開始フローのみから再開できます。この例ではshowCartになります。

6.5.2 アクションステートとビューステート

ビューステート

actionまたridairectを定義していないものがビューステートです。この例がビューステートになります:

enterPersonalDetails {
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

デフォルトでは、ビューファイルgrails-app/views/book/shoppingCart/enterPersonalDetails.gspを使用します。enterPersonalDetailsステートは、submitreturnという2つのイベントを定義しています。これらのイベントはビューでトリガーする必要があります。renderメソッドを使用して描写するビューを変更することが可能です:

enterPersonalDetails {
   render(view: "enterDetailsView")
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

この例でgrails-app/views/book/shoppingCart/enterDetailsView.gspを参照するようになります。viewのパラメータに / を使用して共有ビューを使用できます:

enterPersonalDetails {
   render(view: "/shared/enterDetailsView")
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

この例でgrails-app/views/shared/enterDetailsView.gspを参照するようになります。

アクションステート

アクションステートはコードを実行してビューを描写しないステートです。アクションの結果は指示されたフロートランジションで使用されます。actionメソッドに実行したいコードブロックを渡してアクションステートを実装します:

listBooks {
   action {
      [bookList: Book.list()]
   }
   on("success").to "showCatalogue"
   on(Exception).to "handleError"
}

ご覧の通りアクションはコントローラアクションに似ています。もちろん必要であればコントローラアクションを再利用することもできます。アクションが正常に終了したらsuccessイベントがトリガーされます。この例では、モデルと見なしたMapを返して自動的にflowスコープに配置されます。

加えて、上記の例では、エラーを取り扱う例外ハンドラも使用しています:

on(Exception).to "handleError"

例外時にhandleErrorというステートにフローが移行します。

フローリクエストコンテキストとやりとりをする複雑なアクションを記述できます:

processPurchaseOrder {
    action {
        def a =  flow.address
        def p = flow.person
        def pd = flow.paymentDetails
        def cartItems = flow.cartItems
        flow.clear()

def o = new Order(person: p, shippingAddress: a, paymentDetails: pd) o.invoiceNumber = new Random().nextInt(9999999) for (item in cartItems) { o.addToItems item } o.save() [order: o] } on("error").to "confirmPurchase" on(Exception).to "confirmPurchase" on("success").to "displayInvoice" }

この例では、アクションで、flowスコープから収集した情報からOrderオブジェクトを生成しています。そして生成後オーダーをモデルで返しています。ここで重要なのはリクエストコンテキストと"flowスコープ"のやりとりです。

アクションのトランジション

他の形式のアクションとしてトランジションアクションがあります。トランジションアクションは、イベントがトリガーされたら、トランジションステートで直接優先的に実行されます:

enterPersonalDetails {
   on("submit") {
       log.trace "Going to enter shipping"
   }.to "enterShipping"
   on("return").to "showCart"
}

この例では、submitイベントにコードブロックを渡してトランジションのログを実行しています。この後に解説をする、データバインディングとバリデーションで、このトランジションステートが便利に活用できます。

6.5.3 フロー実行イベント

ステートから次のステートへのフローでの トランジション 実行ができると、次のフローの イベントトリガー を指定する方法が必要になります。ビューステートまたはアクションステートからイベントをトリガーできます。

ビューステートからのイベントトリガー

前のコードの例と同じく、フローの開始ステートには2つのイベントを与えます。checkoutイベントとcontinueShoppingイベントです:

def shoppingCartFlow = {
    showCart {
        on("checkout").to "enterPersonalDetails"
        on("continueShopping").to "displayCatalogue"
    }
    …
}

shopCartイベントはビューステートなので、grails-app/book/shoppingCart/showCart.gspを描写します。このビューの中に、フロートリガーを実行するコンポーネントを持つ必要があります。 submitButtonタグを使用したフォームを使います:

<g:form action="shoppingCart">
    <g:submitButton name="continueShopping" value="Continue Shopping" />
    <g:submitButton name="checkout" value="Checkout" />
</g:form>

フォームはshoppingCartフローへ送信するようにします。それぞれのsubmitButtonタグのname属性にトリガーするイベントを指定します。フォームを使用しない場合は、次のようにlinkタグをイベントトリガーに使用できます:

<g:link action="shoppingCart" event="checkout" />

アクションからのイベントトリガー

アクションからイベントをトリガーするには、メソッドを実行します。組込のerror()success()メソッド(トランジションアクション)が有るのでそれを例に使用します。次の例では、トランジションアクションでバリデーションが失敗したらerror()イベントをトリガーしています:

enterPersonalDetails {
   on("submit") {
         def p = new Person(params)
         flow.person = p
         if (!p.validate()) return error()
   }.to "enterShipping"
   on("return").to "showCart"
}

このケースでは、errorトランジションアクションにより、フローはenterPersonalDetailsステートに戻ります。

アクションステートでは、リダイレクトフローへイベントをトリガーできます:

shippingNeeded {
   action {
       if (params.shippingRequired) yes()
       else no()
   }
   on("yes").to "enterShipping"
   on("no").to "enterPayment"
}

6.5.4 フローのスコープ

スコープの基礎

先ほどの例で、"フロー範囲で有効な"オブジェクトを蓄積する、flowという特殊なオブジェクトを使用しました。Grailsフローでは5つのスコープが利用できます:

  • request - 現在のリクエスト範囲のオブジェクトを蓄積
  • flash - 現在と次までが有効範囲のオブジェクトを蓄積
  • flow - フロー範囲で有効なオブジェクトを蓄積。フローが終了ステートに到達したら削除されます。
  • conversation - ルートフローやサブフローを含む範囲での対話でのオブジェクトを蓄積
  • session - ユーザのセッションへオブジェクトを蓄積

Grailsサービスクラスは自動的にflowスコープになります。詳しくはサービスを参照してください。

アクションからモデルMapを返すと自動的に結果にあるモデルがflowスコープに配置されます。例としてトランジションアクションで次のようにflowスコープにオブジェクトを配置できます:

enterPersonalDetails {
    on("submit") {
        [person: new Person(params)]
    }.to "enterShipping"
   on("return").to "showCart"
}

それぞれのステートごとに新しいrequestスコープができます、(例えば)アクションステートのrequestスコープに配置したオブジェクトは次のビューステートでは使用できません。他のステートにオブジェクトを渡す場合は別のスコープを使いましょう。さらにWebフローでは:

  1. ステート間のトランジションでオブジェクトをflashスコープからrequestスコープへ移動します。
  2. 描写する前に、flowとconversationスコープのオブジェクトをビューモデルにマージします。(GSPページ等のビューでオブジェクトを参照する際はスコープ接頭辞を含まないでください)

フロースコープとシリアライズ

flashflowまたconversationスコープに配置するオブジェクトは、必ずjava.io.Serializableを継承してください。継承しない場合は例外を投げます。ドメインクラスは通常スコープに配置されビューの描写に使用するので影響を受けます。例として次のようなドメインクラスがあるとします:

class Book {
    String title
}

Bookのインスタンスをflowスコープで使用する場合は次のようにします:

class Book implements Serializable {
    String title
}

ドメインクラスに関連するクラスやクロージャにも影響があります。次のようになっていたとします:

class Book implements Serializable {
    String title
    Author author
}

関連するAuthorSerializableでは無い場合はエラーとなります。onLoadonSave等のGORMイベントのクロージャにも影響があります。次のドメインクラスをflowスコープに配置するとエラーとなります:

class Book implements Serializable {

String title

def onLoad = { println "I'm loading" } }

理由はアサインされたonLoadイベントのブロックがシリアライズされないからです。これを対応させるにはイベントにtransientを指定します:

class Book implements Serializable {

String title

transient onLoad = { println "I'm loading" } }

またはメソッドにします:

class Book implements Serializable {

String title

def onLoad() { println "I'm loading" } }

The flow scope contains a reference to the Hibernate session. As a result, any object loaded into the session through a GORM query will also be in the flow and will need to implement Serializable.

If you don't want your domain class to be Serializable or stored in the flow, then you will need to evict the entity manually before the end of the state:

flow.persistenceContext.evict(it)

6.5.5 データバインディングとバリデーション

開始と終了ステートのセクションの中で、最初の例の開始ステートはenterPersonalDetailsステートへトリガーしています。このステートはビューを描写して、ユーザが必要な情報をエントリーするのを待ちます:

enterPersonalDetails {
   on("submit").to "enterShipping"
   on("return").to "showCart"
}

ビューは、submitイベントまたはreturnイベントをトリガーする、2つの送信ボタンを持つフォームを持っています:

<g:form action="shoppingCart">
    <!-- Other fields -->
    <g:submitButton name="submit" value="Continue"></g:submitButton>
    <g:submitButton name="return" value="Back"></g:submitButton>
</g:form>

フォームから送信された情報を取り込むには、トランジションアクションを使用します:

enterPersonalDetails {
   on("submit") {
      flow.person = new Person(params)
      !flow.person.validate() ? error() : success()
   }.to "enterShipping"
   on("return").to "showCart"
}

リクエストパラメータからデータバインディングを実行して、flowスコープのPersonインスタンスに渡している部分を注目してください。さらに興味深い部分は、バリデーションを実行して、失敗したらerror()メソッドを実行している部分です。この結果を受け取るとトランジションは停止されenterPersonalDetailsビューへ戻りユーザが正当な内容をエントリできるようになります。あるいは内容が問題無ければ、トランジションが続行されてenterShippingステートへ移動します。

通常のアクションと同じく、フローアクションでは、クロージャの最初の引数を定義することで、コマンドオブジェクトにも対応できます:

enterPersonalDetails {
   on("submit") { PersonDetailsCommand cmd ->
       flow.personDetails = cmd
      !flow.personDetails.validate() ? error() : success()
   }.to "enterShipping"
   on("return").to "showCart"
}

6.5.6 サブフローとの対話

GrailsのWebフローはサブフローにも対応しています。サブフローはフォローの中にあるフローです。この検索フローを例とします:

def searchFlow = {
    displaySearchForm {
        on("submit").to "executeSearch"
    }
    executeSearch {
        action {
            [results:searchService.executeSearch(params.q)]
        }
        on("success").to "displayResults"
        on("error").to "displaySearchForm"
    }
    displayResults {
        on("searchDeeper").to "extendedSearch"
        on("searchAgain").to "displaySearchForm"
    }
    extendedSearch {
        // Extended search subflow
        subflow(controller: "searchExtensions", action: "extendedSearch")
        on("moreResults").to "displayMoreResults"
        on("noResults").to "displayNoMoreResults"
    }
    displayMoreResults()
    displayNoMoreResults()
}

この例ではextendedSearchステートで中にサブフローを参照しています。サブフローが同じコントローラで呼ばれる場合は、コントローラを指定するパラメータは省略可能です。

1.3.5以前、サブフローは、subflow(extendedSearchFlow)のように呼び出しました。この方法は非推奨となっており、下位互換のために対応はしています。

サブフローは完全に別のフローです:

def extendedSearchFlow = {
    startExtendedSearch {
        on("findMore").to "searchMore"
        on("searchAgain").to "noResults"
    }
    searchMore {
        action {
           def results = searchService.deepSearch(ctx.conversation.query)
           if (!results) return error()
           conversation.extendedResults = results
        }
        on("success").to "moreResults"
        on("error").to "noResults"
    }
    moreResults()
    noResults()
}

conversationスコープのextendedResultsの位置に注目してください。このスコープはflowスコープと違い、flowの間だけで無く、全ての対話にかけてステートを共有できます。さらに終了ステートに注目してください。サブフローはサブフロー内とメインフローのイベントをトリガーできます:

extendedSearch {
    // Extended search subflow
    subflow(controller: "searchExtensions", action: "extendedSearch")
    on("moreResults").to "displayMoreResults"
    on("noResults").to "displayNoMoreResults"
}

6.6 フィルタ

Grailsのコントローラにはインターセプター機能に対応しています。これらは、少ないコントローラに対しては有用ですが、大きなアプリケーションだと管理が大変になります。その一方フィルタを使用すると、多くのコントローラ、URIスペースや指定したアクションに対して適用できます。フィルタはプラグインにすることも簡単で、コントローラロジックとも切り離されており、セキュリティ、ロギングなどの全体にわたる物として便利です。

6.6.1 フィルタの適用

フィルタを作成するには、grails-app/confディレクトリに、名称がFiltersで終わるクラスを作成します。このクラスにはフィルタを記述する、filtersというコードブロックを定義します:

class ExampleFilters {
   def filters = {
        // your filters here
   }
}

filtersブロックに定義する各フィルタには、範囲と名称を持たせます。名称はメソッド名で、範囲は引数で定義します。例として、ワイルドカードを使用した全コントローラ・アクションに適用されるフィルタを定義します:

sampleFilter(controller:'*', action:'*') {
  // interceptor definitions
}

フィルタの範囲は次のように指定できます:

  • 省略可能なワイルドカードを使用した、コントローラ名と(または)アクション名の組み合わせ。
  • Antパスマッチング書式のURI指定。

フィルタルール属性:
  • controller - コントローラ対象のパターンマッチング, 既存値 * で正規表現コンパイル時には .*に置換されます。
  • controllerExclude - 除外するコントローラのパターン, 既存値 * で正規表現コンパイル時には .*に置換されます。
  • action - アクション対象のパターンマッチング, 既存値 * で正規表現コンパイル時には .*に置換されます。
  • actionExclude - 除外するアクションのパターン, 既存値 * で正規表現コンパイル時には .*に置換されます。
  • regex (true/false) - 正規表現の使用 ('*' を '.*'に置換しない指定)
  • uri - URIマッチ, Antスタイルパス (e.g. /book/**)
  • uriExclude - 除外するURIパターン, Antスタイルパス (e.g. /book/**)
  • find (true/false) - 部分マッチ (java.util.regex.Matcher.find()を参照)
  • invert (true/false) - ルールを逆にする

フィルタの例:
  • 全てのコントローラとアクション

all(controller: '*', action: '*') {

}

  • BookControllerのみ

justBook(controller: 'book', action: '*') {

}

  • BookController以外の全て

notBook(controller: 'book', invert: true) {

}

  • 'save'を名称に含めたアクション全て

saveInActionName(action: '*save*', find: true) {

}

  • "bad*"を除外した、'b'で始まるアクション全て

actionBeginningWithBButNotBad(action: 'b*', actionExclude: 'bad*', find: true) {

}

  • URIへ適用

someURIs(uri: '/book/**') {

}

  • 全てのURIに適用

allURIs(uri: '/**') {

}

filtersコードブロックに定義した順序でフィルタは実行されます。Filtersクラス間での実行を制御するには、フィルタ依存セクションで記載されているdependsOnプロパティが使用できます。

注意:除外パターンが使用された場合は除外マッチングパターンが優先されます。例として、アクションが'b*' で、actionExcludeが'bad*'の場合、'best','bien'等のアクションは適用されますが、'bad','badland'は適用されません。

6.6.2 フィルタの種類

フィルタの中に、幾つかのインターセプタを定義することができます。

  • before - アクションの前に実行されます。falseを返す事で、その後に実行されるフィルタとアクションは実行されません。
  • after - アクションの後に実行されます。最初の引数に、ビュー描写前に変更可能なビューモデルが渡されます。
  • afterView - ビュー描写後に実行されます。実行時に例外が発生した場合はnon-nullなExceptionが引数として渡されます。このクロージャはレイアウトが適用される前に実行されます。

シンプルな認証の仕組みとしてフィルタを定義する例です:
class SecurityFilters {
   def filters = {
       loginCheck(controller: '*', action: '*') {
           before = {
              if (!session.user && !actionName.equals('login')) {
                  redirect(action: 'login')
                  return false
               }
           }
       }
   }
}

このloginCheckフィルタは、beforeインターセプタのコードブロックを実行することで、ユーザがセッションに無い場合はloginアクションへリダイレクトしています。falseを返す事で対象のアクション自身が実行されないようにします。

Here's a more involved example that demonstrates all three filter types:

import java.util.concurrent.atomic.AtomicLong

class LoggingFilters {

private static final AtomicLong REQUEST_NUMBER_COUNTER = new AtomicLong() private static final String START_TIME_ATTRIBUTE = 'Controller__START_TIME__' private static final String REQUEST_NUMBER_ATTRIBUTE = 'Controller__REQUEST_NUMBER__'

def filters = {

logFilter(controller: '*', action: '*') {

before = { if (!log.debugEnabled) return true

long start = System.currentTimeMillis() long currentRequestNumber = REQUEST_NUMBER_COUNTER.incrementAndGet()

request[START_TIME_ATTRIBUTE] = start request[REQUEST_NUMBER_ATTRIBUTE] = currentRequestNumber

log.debug "preHandle request #$currentRequestNumber : " + "'$request.servletPath'/'$request.forwardURI', " + "from $request.remoteHost ($request.remoteAddr) " + " at ${new Date()}, Ajax: $request.xhr, controller: $controllerName, " + "action: $actionName, params: ${new TreeMap(params)}"

return true }

after = { Map model ->

if (!log.debugEnabled) return true

long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]

def msg = "postHandle request #$requestNumber: end ${new Date()}, " + "controller total time ${end - start}ms" if (log.traceEnabled) { log.trace msg + "; model: $model" } else { log.debug msg } }

afterView = { Exception e ->

if (!log.debugEnabled) return true

long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]

def msg = "afterCompletion request #$requestNumber: " + "end ${new Date()}, total time ${end - start}ms" if (e) { log.debug "$msg \n\texception: $e.message", e } else { log.debug msg } } } } }

In this logging example we just log various request information, but note that the model map in the after filter is mutable. If you need to add or remove items from the model map you can do that in the after filter.

6.6.3 変数とスコープ

フィルタでは、コントローラタグライブラリで利用できる共通のプロパティとアプリケーションコンテキストを参照できます:

フィルタではコントローラやタグライブラリで使用できる一部のメソッドが使用できます:

  • redirect - 他のコントローラやアクションにリダイレクトを行う
  • render - カスタムレスポンスを返す。

6.6.4 フィルタ依存関係

Filtersクラスでは、dependsOnプロパティに他のFiltersクラスを定義して先に実行させることが可能です。この機能はFiltersクラスが他のFiltersクラスの振る舞いに依存する場合等に使用します。

Filtersクラスの例を見ていきましょう:

class MyFilters {
    def dependsOn = [MyOtherFilters]

def filters = { checkAwesome(uri: "/*") { before = { if (request.isAwesome) { // do something awesome } } }

checkAwesome2(uri: "/*") { before = { if (request.isAwesome) { // do something else awesome } } } } }

class MyOtherFilters {
    def filters = {
        makeAwesome(uri: "/*") {
            before = {
                request.isAwesome = true
            }
        }
        doNothing(uri: "/*") {
            before = {
                // do nothing
            }
        }
    }
}

MyFiltersには、dependsOnにMyOtherFiltersを指定しています。これにより、リクエストがフィルタ範囲にマッチしたMyOtherFiltersのフィルタがMyFiltersの前に実行されます。この例では"/test"とリクエストした場合全てのフィルタにマッチします。フィルタ実行順は次のようになります:
  • MyOtherFilters - makeAwesome
  • MyOtherFilters - doNothing
  • MyFilters - checkAwesome
  • MyFilters - checkAwesome2

MyOtherFiltersクラスのフィルタが先に実行されて、MyFiltersクラスのフィルタへと続きます。Filtersクラスの実行順が可能になり、各Filtersクラスのフィルタ実行順も保っています。

循環依存関係を検出したら、循環依存関係のあるフィルタをフィルタチェインの最後に追加して処理を続行します。その後、循環依存関係を検出した事をログへ出力します。ログを出力するには、ルートロガーレベルがWARNに設定されFilers用のログ定義をする必要があります。

6.7 Ajax

AjaxはリッチWebアプリケーションでは欠かせない物となっています。この手のアプリケーションには通常、GroovyやRuby等で実装されたアジャイル、ダイナミックフレームワークがお似合いです。GrailsではAjaxアプリケーションを構築をサポートするためのAjaxタグライブラリを提供しています。Ajaxタグライブラリのリストはタグリファレンスを参照してください。

6.7.1 Ajax対応

Grailsはデフォルトで jQuery を使用しています。他のライブラリ、PrototypeDojoYahoo UIGoogle Web Toolkit等もプラグインで提供しています。

このセクションでは全般的なAjax対応の説明をします。先ずはじめに、ページの<head>タグ内に以下を追加します:

<g:javascript library="jquery" />

プラグインで提供された他のライブラリを使用する際は、jqueryの部分を入れ換えます。これはGrailsの適用性のあるタグライブラリで可能にしています。Grailsのプラグインには、次の様々なAjaxライブラリが存在します(これは一部です):
  • jQuery
  • Prototype
  • Dojo
  • YUI
  • MooTools

6.7.1.1 リモートリンク

多くの方法でリモートコンテントをロードすることが可能ですが、一般的な方法としてremoteLinkタグを使用します。 このタグは、非同期リクエストと結果をエレメントにセット(省略可能)するHTMLアンカータグを生成します。リモートリンクタグを作成する簡単な例は次のようになります:

<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>

上記の例で生成されたタグは、id:1をリクエストパラメータとしてdeleteアクションに非同期リクエストを送信します。

6.7.1.2 コンテンツの更新

先ほどの例で非同期送信を行いましたが、通常ユーザへ何が行われたかのフィードバックを提供する必要があります:

def delete() {
    def b = Book.get(params.id)
    b.delete()
    render "Book ${b.id} was deleted"
}

GSPコード:

<div id="message"></div>
<g:remoteLink action="delete" id="1" update="message">
Delete Book
</g:remoteLink>

この例では対象のアクションをコールしてレスポンスをmessegeのidをもったdivエレメントの内容を"Book 1 was deleted"に更新します。この更新動作は、タグのupdate属性で対象を指定しています。さらにMapを使用して更新失敗時の表示先も指定できます:

<div id="message"></div>
<div id="error"></div>
<g:remoteLink update="[success: 'message', failure: 'error']"
              action="delete" id="1">
Delete Book
</g:remoteLink>

この例ではリクエストに失敗すると、errorのIDをもつdivタグ内が更新されます。

6.7.1.3 リモートフォーム送信

HTMLフォームを非同期で送信することができます。remoteLinkタグと同じような属性を使用できるformRemoteタグを使用する例です:

<g:formRemote url="[controller: 'book', action: 'delete']"
              update="[success: 'message', failure: 'error']">
    <input type="hidden" name="id" value="1" />
    <input type="submit" value="Delete Book!" />
</g:formRemote >

他の方法として、submitToRemoteタグを使用して送信ボタンを作成します。このボタンで非同期送信ボタンを可能として、アクションに依存させなくできます:

<form action="delete">
    <input type="hidden" name="id" value="1" />
    <g:submitToRemote action="delete"
                      update="[success: 'message', failure: 'error']" />
</form>

6.7.1.4 Ajaxイベント

指定したJavaScriptを、イベントが発生した際にコールすることができます。全てのイベントは接頭辞"on"で指定します:

<g:remoteLink action="show"
              id="1"
              update="success"
              onLoading="showProgress()"
              onComplete="hideProgress()">Show Book 1</g:remoteLink>

上記のコードでは、実行されると、ロード時に指定した"showProgress()"ファンクションがプログレスバーを表示する等ファンクションの実装に対応した動作を行います。他に以下のイベントが使用可能です:

  • onSuccess - 成功した時に呼び出すJavaScriptファンクション
  • onFailure - 失敗した時に呼び出すJavaScriptファンクション
  • on_ERROR_CODE - 指定したエラーコードを取得したら呼び出すJavaScriptファンクション (eg on404="alert('not found!')")
  • onUninitialized - Ajaxエンジンの初期化に失敗したときに呼び出すJavaScriptファンクション
  • onLoading - レスポンスを読み込み中に呼び出すJavaScriptファンクション
  • onLoaded - レスポンスの読み込み完了時に呼び出すJavaScriptファンクション
  • onComplete - リモートファンクションが完全に完了(更新を含む)したら呼び出すJavaScriptファンクション

XmlHttpRequestオブジェクトを参照する場合は、以下のように、e等のイベントパラメータ使用して取得します:

<g:javascript>
    function fireMe(e) {
        alert("XmlHttpRequest = " + e)
    }
}
</g:javascript>
<g:remoteLink action="example"
              update="success"
              onSuccess="fireMe(e)">Ajax Link</g:remoteLink>

6.7.2 Prototypeを使用したAjax

Prototype はプラグインで対応しています。インストールするにはプロジェクトのルートで次のコマンドをターミナルで実行します:

grails install-plugin prototype

このコマンドでPrototypeプラグインがダウンロードされプロジェクトにインストールされます。そして次のようにページのトップに記述します:

<g:javascript library="prototype" />

Scriptaculous も必要であれば、代わりに次のように指定します:

<g:javascript library="scriptaculous" />

この定義で他のremoteLink,formRemotesubmitToRemote等のGrailsタグを使用できます。

6.7.3 Dojoを使用したAjax

Dojo はプラグインで対応しています。インストールするにはプロジェクトのルートで次のコマンドをターミナルで実行します:

grails install-plugin dojo

このコマンドでDojoプラグインがダウンロードされプロジェクトにインストールされます。そして次のようにページのトップに記述します:

<g:javascript library="dojo" />

この定義で他のremoteLink,formRemotesubmitToRemote等のGrailsタグを使用できます。

6.7.4 GWTを使用したAjax

Google Web Toolkit をプラグインでサポートしています。詳しくはドキュメントを参照してください。

6.7.5 Ajax使用時のサーバサイド

Ajaxを実装するには幾つかの方法があります。通常次のように分類できます:

  • コンテント中心なAjax - リモートコールの結果HTMLページを更新する。
  • データ中心なAjax - XMLまたはJSONをサーバから送信してページをプログラムで更新する。
  • スクリプトを使用したAjax - サーバからJavascriptを送信して実行させる。

Ajaxセクションでの例のほとんどは、ページの更新を行うコンテント中心なAjaxを説明しました。データ中心やスクリプトを使用したAjaxも使用すると思います。このガイドでは他のスタイル尾Ajaxを解説します。

コンテント中心のAjax

同じ説明になりますが、コンテント中心のAjaxでの要件はHTMLをサーバから返す事です。通常この場合はテンプレートをrenderメソッドで描写して実装します:

def showBook() {
    def b = Book.get(params.id)

render(template: "bookTemplate", model: [book: b]) }

これをクライアント側でremoteLinkタグを使用して呼び出します:

<g:remoteLink action="showBook" id="${book.id}"
              update="book${book.id}">Update Book</g:remoteLink>

<div id="book${book.id}"> <!--existing book mark-up --> </div>

JSONを使用したデータ中心のAjax

データ中心のAjaxでは、クライアントがレスポンスで取得した内容を評価してプログラムで更新をします。GrailsではJSONのレスポンスを返すにはJSONマーシャリングを使用します:

import grails.converters.JSON

def showBook() { def b = Book.get(params.id)

render b as JSON }

クライアント側で取得したJSONをAjaxイベントハンドラを使って処理します:

<g:javascript>
function updateBook(e) {
    var book = eval("("+e.responseText+")") // evaluate the JSON
    $("book" + book.id + "_title").innerHTML = book.title
}
<g:javascript>
<g:remoteLink action="test" update="foo" onSuccess="updateBook(e)">
    Update Book
</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>

XMLを使用したデータ中心のAjax

サーバサイドでXMLをえお扱うのも簡単です:

import grails.converters.XML

def showBook() { def b = Book.get(params.id)

render b as XML }

しかしクライアント側でのDOM操作は複雑になります:

<g:javascript>
function updateBook(e) {
    var xml = e.responseXML
    var id = xml.getElementsByTagName("book").getAttribute("id")
    $("book" + id + "_title") = xml.getElementsByTagName("title")[0].textContent
}
<g:javascript>
<g:remoteLink action="test" update="foo" onSuccess="updateBook(e)">
    Update Book
</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>

スクリプトを使用したAjax

スクリプトを使用したAjaxは、Javascriptを返してクライアント側で動作(評価)させます。以下が例になります:

def showBook() {
    def b = Book.get(params.id)

response.contentType = "text/javascript" String title = b.title.encodeAsJavascript() render "$('book${b.id}_title')='${title}'" }

重要な部分としてcontentTypetext/javascriptにすることを思えて起きましょう。Prototypeを使用した場合はこのcontentTypeで自動的にJavaScriptを評価します。

当然このケースでは、クライアントがサーバを壊す事が無いと認めたクライアントサイトAPIでは無いと危険です。これがRailsではRJSを持っている理由です。Grailsには、RJSに相当する仕組みは存在しませんが、同じような内容の仕組みが Dynamic JavaScriptプラグインで提供されています。

AjaxとAjaxでない両方へのレスポンス

AjaxとAjaxではないリクエストを同じコントローラアクションで処理ができます。Grailsでは、Ajaxリクエスト判別用に、HttpServletRequestisXhr()メソッドを追加しています。例として、Ajaxリクエストの場合はテンプレートでページの一部を描写し、それ以外は全ページ描写を行う場合:

def listBooks() {
    def books = Book.list(params)
    if (request.xhr) {
        render template: "bookTable", model: [books: books]
    } else {
        render view: "list", model: [books: books]
    }
}

6.8 コンテントネゴシエーション

Grailsには、明示的なフォーマットに対するリクエストのHTTP AcceptヘッダーまたはマップされたURIの拡張子でのコンテントネゴシエーションに対応しています。

Mimeタイプの定義

コンテントネゴシエーションの処理を行うには、どのコンテントタイプをサポートするかをGrailsに教える必要があります。grails-app/conf/Config.groovygrails.mime.types設定に幾つかのコンテントタイプがデフォルトで定義されています:

grails.mime.types = [ xml: ['text/xml', 'application/xml'],
                      text: 'text-plain',
                      js: 'text/javascript',
                      rss: 'application/rss+xml',
                      atom: 'application/atom+xml',
                      css: 'text/css',
                      csv: 'text/csv',
                      all: '*/*',
                      json: 'text/json',
                      html: ['text/html','application/xhtml+xml']
                    ]

この設定の一部を説明すると、'text/xml'または'application/xml'を含むリクエストは単純にコンテントタイプ'xml'としてGrailsが検出するのを可能にします。独自のタイプを追加するには単純にマップにタイプを追加するだけです。

Acceptヘッダーを使用したコンテントネゴシエーション

全てのHTTPリクエストは、クライアントが受け入れ可能な、メディア(mimeタイプ)を定義した、Acceptヘッダーを持っています。古いブラウザの場合は通常以下のようになります:

*/*

これは単純に何でも可能という意味です。最新のブラウザではもっと便利になっており次のような内容を送信します(FireFoxのAcceptヘッダーを例として):

text/xml, application/xml, application/xhtml+xml, text/html;q=0.9,
text/plain;q=0.8, image/png, */*;q=0.5

Grailsではこの入ってきたフォーマットをパースして、レスポンスフォーマットを選択するためのpropertyresponseオブジェクトに追加します。上記の例では以下のアサーションが通ります:

assert 'html' == response.format

何故でしょう? メディアタイプtext/htmlは、レートでは0.9という高い"特質"を持っているので、優先度が高いからです。先の例で紹介した古いブラウザの場合は少し違います:

assert 'all' == response.format

このケースの'all'はどのクライアントでも受け入れられます。様々な種類のリクエストをコントローラで分別するには、switch文のように動作するwithFormatメソッドを使用します:

import grails.converters.XML

class BookController {

def list() { def books = Book.list() withFormat { html bookList: books js { render "alert('hello')" } xml { render books as XML } } } }

優先されたフォーマットがhtmlの場合Grailsはhtml()のみを実行します。この場合Grailsは、grails-app/views/books/list.html.gspまた grails-app/views/books/list.gspのどちらかのビューを探しに行きます。

ではどのようにフォーマットの"all"を判別するのでしょう?単純にwithFormatブロックのコンテントタイプの順番を指定して一番最初に実行される物を決めます。上記の例では、"all"を受け取った場合は、htmlハンドラを実行します。

withFormatを使用する場合は、withFormatでの返値がコントローラアクションで最終の返値となります。

リクエストフォーマット vs レスポンスフォーマット

Grails 2.0からの対応で、リクエストフォーマットとレスポンスフォーマットでは分離して認識するようになりました。リクエストフォーマットは、CONTENT_TYPEヘッダの指定で入ってくるリクエストを認識してXMLまたJSON等に分類します。レスポンスフォーマットでは、ファイル拡張子、フォーマットパラメータまたはACCEPTヘッダを認識して適したレスポンスをクライアントに返します。

コントローラでのwithFormatはレスポンスフォーマット専用となります。リクエストフォーマットでロジックを記述する場合は、requestオブジェクトのwithFormatメソッドを使用します:

request.withFormat {
    xml {
        // read XML
    }
    json {
        // read JSON
    }
}

リクエストパラメータformatでのコンテントネゴシエーション

リクエストヘッダを参照した方法を行いたく無い場合は、フォーマットを指定するために、リクエストパラメータのformatが使用できます:

/book/list?format=xml

このパラメータはURLマッピングでも定義できます:

"/book/list"(controller:"book", action:"list") {
    format = "xml"
}

URI拡張子でのコンテントネゴシエーション

URI拡張子でのコンテントネゴシエーションにも対応しています。例として次のようなURIがあるとします:

/book/list.xml

Grailsが拡張子を外して、xmlとしてフォーマットをセットして/book/listにマップします。この振る舞いはデフォルトで使用可能です。この機能を外したい場合はgrails-app/conf/Config.groovygrails.mime.file.extensionsプロパティをfalseに指定します:

grails.mime.file.extensions = false

コンテントネゴシエーションをテスト

Unitテストまたは統合テスト(テストのセクションを参照)でコンテントネゴシエーションをテストする際に、入ってくるリクエストヘッダを操作することができます:

void testJavascriptOutput() {
    def controller = new TestController()
    controller.request.addHeader "Accept",
              "text/javascript, text/html, application/xml, text/xml, */*"

controller.testAction() assertEquals "alert('hello')", controller.response.contentAsString }

または、formatパラメータをセットすることが可能です:

void testJavascriptOutput() {
    def controller = new TestController()
    controller.params.format = 'js'

controller.testAction() assertEquals "alert('hello')", controller.response.contentAsString }