本稿から2回に分けて、「Grailsをコントロールせよ!」と題して、コントローラからプレゼンテーション層の連携と、WebアプリケーションのUI設計との連携に必要なURLの定義方法などを解説します。先ずは、どのように要求を受けてどのように結果を返すか、そこでどのような受け返しができるか、その内容を内外共に解りやすく定義できるかを理解することで、Grailsをきれいにコントロールできるようになれると良いでしょう。
コントローラは、クライアントからのリクエストを受け取り、リクエストの内容に応じて処理を行い、ビューを返すクラスです。(※ここではMVC2の細かな話は割愛させていただきます。)アプリケーションのリクエスト・処理などの動作をコントロールする部分になります、と、簡単に言うと怒られるかもしれませんが、初心者の方はその解釈で充分だと思います。
Grailsでコントローラクラスを作成するには、create-controllerコマンドを実行することで、コントローラのスケルトンを生成してくれます。
まずはじめにコントローラクラスを生成します。プロジェクトの階層で以下のコマンドを実行してHomeコントローラを作成。
※Grailsでの開発経験がある方はここは読み飛ばしてもかまいません。
grails create-controller home #クラス生成時にパッケージを指定する事もできます。 grails create-controller jp.company.Home
grails-app/controllers階層のパッケージ階層以下にコントローラクラスが生成されます。同時にビュー用のgspを配置するための階層 grails-app/views/home が作成されます。今回のコマンドで生成されたコントローラクラスの内容は、次のような内容になります。
package gmag01 class HomeController { def index = { } //アクション }
この例では、生成時にパッケージを指定していないため、アプリケーション名がパッケージ名として自動的に付加されています。(grails-app/controllers/gmag01/HomeController.groovy)
コントローラクラスは、grails-app/controllers階層内に配置され、必ずクラス名の最後がControllerで終わる必要があります。生成コマンドを使わないでコントローラクラスを追加するときは、クラス名の最後がControllerにするのを忘れないようにしましょう!
では生成されたコントローラクラスの内容を見ていきます。単純なgroovyのクラスとして生成され、コードとしてはindexというクロージャがあらかじめ記述されています。これを処理単位を記述する場所 "アクション"と呼びます。アクションはコントローラクラス内に複数記述することが可能です。
このアクションにドメインモデル操作などの処理を記述して、アクションからビューにモデルを渡す、またはアクションから、そのまま文字列を返す等の処理を記述できます。
コントローラクラスには、ビューテンプレート指定や、リダイレクト処理、他にも様々なコントローラ・アクション内で使用できるメソッドや、request,response,sesson等の変数が動的に追加されています。これは、Grailsで初めて開発する人には不思議な状態に見えます。そして何が活用できるかは、実際にドキュメント無しには解りにくくなっているのもGrailsの特徴!?の一つとなります。 (公式ドキュメント http://grails.org/doc/latest )
解りにくいと言い切ってしまうのも・・・なので、最低限のコントローラクラスの説明は終わりとして、ここから実際に動作できるコードでしていきます。
最初に(※よく質問受けるので)前提としてコントローラは、プロトタイプです。リクエスト毎にコントローラは生成されます。
コントローラからビューの連携には様々な方法があります。まずは、簡単なハローワールドを記述して動かしてみましょう。
class HomeController { def index = { render 'Hello World!' } }
render 'Hello World!' と記述して起動します(grails run-app)、そして、 http://localhost:8080/gmag01/home/index にアクセスします。この例では、単純に'Hello World!'と文字列を返しブラウザー画面に表示します。この "render"は単純なテキストのレスポンス処理から、ビューへの連携等、多彩に幅広く活用できるメソッドです。またこの例では、省略していますが、render 'Hello World!'の部分を、次のように、text: と指定して記述することもできます。
render text:'Hello World!'
ハローワールド画面
次のサンプルはリクエストクエリー付きになります。
class HomeController { def index = { render "Hello ${params.who}!" } }
render 'Hello World!' を"Hello ${params.who}!" に変更してみます。そして、先ほどのURLの後ろに ?who=Worldを追加した http://localhost:8080/gmag01/home/index?who=World にアクセスします。今回は、params変数をプロパティ参照した、params.whoをレスポンス結果を返すコードに追加しました。リクエストクエリーのパラメータは、コントローラの機能で自動的に収集され、params (詳細は後述)変数に収納されます。パラメータを参照するには、params.whoのように、params変数に対して、"."に次いでパラメータキーを指定するだけです。
では、次に簡単にGSPと連携をしてみましょう。GSPは、何も定義しない場合、"grails-app/views/コントローラ名/アクション名.gsp"ファイルに関連づけられます。例えば次のコード例では、アクションに特に何も指定しないと自動的に、"grails-app/views/home/index.gsp"をGSPとしてビューを表示します。そして該当ファイルが無い場合は、次に"grails-app/views/home/index.jsp"ファイルを探しにいき、JSPファイルも存在しなければ、エラーコード404を返す流れとなります。
class HomeController { def index = { // } }
前の例では返値を何も返してません。アクションの最後に返値のMapを返すことでGSPへモデルを渡すことができます。
class HomeController { def index = { [username:"tyama",score:2500] } }
GSPでのモデルの参照は、このようになります。
<html> <head> <title>indexページ</title> <meta name="layout" content="main" /> </head> <body> username: ${username} score is ${score} </body> </html>
ビュー用のGSPファイルを指定することもできます。次の例では "grails-app/views/home/foo.gsp" を指定しています。
class HomeController { def index = { render view:"foo", model: [username:"tyama",score:2500] } }
ここまでの内容で、簡単にリクエストを行いパラメータを取得してアクションで処理を行いレスポンスするという一連の動作はわかったと思います。
そして、もう気付いてると思いますが、ブラウザーでアクセスしたURLをみると、 "/gmag01/home/index" これを置き換えて説明すると、 "/アプリケーション名/コントローラ名/アクション名" となっていることがわかります。これはGrailsでのURLの基本となります。URLマッピング(詳細は次号)を定義して変更する事も可能です。
URLとコントローラの関係
簡単にコントローラを動作させることができたところで、アクションからのコントロールをするために大事な、ここまでに一部例としても出てきた render、redirect、chain、forwardという4つのメソッドを紹介します。ほとんどがそれぞれ読んで字のごとくですが、多彩な機能を持つrenderをはじめ、それぞれ使い方がシンプルのようで、それなりに複雑さも持ち合わせています。これらを上手く活用することでGrailsのリクエスト処理を快適にコントロールできます。
renderは様々なビューレスポンスを返すためのメソッドです。renderを使用して単純なテキスト、ビューやテンプレートの指定、XML、JSON、マークアップを使ったXMLのレスポンス、エラーコード等、多彩にレンダリング操作を行えます。
単純なテキストレスポンス例。
render "Hello!" //又は render text:"Hello!" {code} コンテントタイプ・エンコーディングも指定できる。 {code} render text:"<message>こんにちわ</message>", contentType:"text/xml", encoding:"UTF-8"
ビューを指定する例。前述しましたが、viewにビューファイル名、modelに返すモデルを指定します。
render view:"foo", model: [username:"tyama",score:2500] //上記の例では、アクションを記述したコントローラ用のビュー階層を参照します。 //別の階層を参照するには、パスで指定します。(.gspは省略) render view:"/common/foo", model: [username:"tyama",score:2500] //プラグイン内にあるビューを指定するには。 render plugin:"myplugin", view:"foo", model: [username:"tyama",score:2500]
※コード例中に説明があるように、ビューファイルの指定は、grails-app/viewsをルートとして、拡張子なしの絶対パスで指定します。
テンプレートを指定する例。テンプレート用ビューファイルは、ファイル名の先頭に"_"を付加します。(例) _mytemplate.gsp
render template:"mytemplate", model: [username:"tyama",score:2500]
テンプレートにビーンを指定してビューに渡す。
render template:"list",bean:new Person(name:'tyama', age:27) //gspの内容 <user>${it.name} (${it.age})</user>
テンプレートでコレクションを扱う。テンプレート_list.gspを指定して、コレクション[1,2,3,4]を表示させます。
<item>${num}</item>
collectionにコレクションを指定して、テンプレートで参照する変数をvarに指定します。varが省略された場合は、テンプレート内では"it"で参照できます。
render template:"list",collection:[1,2,3,4], var:"num"
マークアップビルダーを使用する。
def users = ['user1','user2'] render(contentType:"text/xml") { followers { users.each{user-> username(user) } } }
例では若干中身を変えないと動きませんが、JSONの場合は、単純にコンテントタイプを"text/json"に変更。
def users = ['user1','user2'] render(contentType:"text/json") { followers:users }
次回のRESTでも紹介しますが、JSON・XMLを返す場合は、コンバーターを使用できます。
コンバーターをimportします。
import grails.converters.*
そしてrenderでは、それぞれ次のように記述します。
render Book.list(params) as JSON render Book.get(params.id) as XML
HTTPのステータスを返す事もできます。
render status: 503, text: 'Failed..'
以上がrenderの使用方法になります。レンダリングの指定方法が多数有りますが、AJAXを使用したり、ポータル的にパーツを返したり等の用途によって、使いやすい方法を選択して活用しましょう。
リダイレクトを行うメソッドです。リクエストを受けたアクションから、指定した場所へHTTPのリダイレクトを行います。
コントローラ・アクション・IDを指定すると、内容に従ってURIを形成した対象へリダイレクトします。次の例では、"/user/show/1"へリダイレクトされます。
redirect controller:"user", action:"show", id:1
このサンプルにparamsを指定する事もできます。次の例では、"/user/show/1?status=good"へリダイレクトされます。
redirect controller:"user", action:"show", id:1, params:[status:'good']
フラグメント指定。先ほどの例に"/user/show/1#comment"のように"#comment"と最後に追加します。
redirect controller:"user", action:"show", id:1, fragment:"comment"
通常のURL、URIの指定も可能です。
redirect uri:"/user/list" redirect url:"http://www.yahoo.co.jp"
HTTPリダイレクトとは違いサーバサイド側でフォワードするメソッドです。redirectとほとんど同じですが、forwardでは、コントローラ・アクション・ID・パラーメータのみの扱いになります。
forward controller:"user", action:"show", id:1, params:[status:'good']
別のアクションへHTTPリダイレクトを行いながら、アクションを連携する場合に使用するメソッドです。
公式ドキュメントから例を拝借。
class HomeController { def first = { chain(action:second,model:[one:1]) } def second = { println chainModel.one chain(action:third,model:[two:2]) } def third = { [three:3] } }
この例で/home/firstにアクセスすると、最終的に/home/thirdに到達して、アクションthirdから返るモデルは、[one:1, two:2, three:3]になります。chainで渡されたモデルは、"chainModel"から参照できます。
コントローラには、さまざまなプロパティが動的に設定されています。「簡単に動かしてみよう」で試したように、リクエストのパラメータを参照するためのparamsをはじめとして、GrailsはJavaのフレームワークなので、もちろん、session、request、response、servletContextも参照可能です。他に次のリクエストまで内容を保持するテンポラリ収納用マップの変数flash等があります。
先ずはじめに簡単な物から紹介。意外と使わないようでお世話になる変数、actionName、controllerName。これももちろん読んで字のごとく、それぞれ参照した場所のアクション名、コントローラ名を取得できます。
render "コントローラ名は${controllerName}でアクション名は${actionName}です。"
sessionは、HttpSessionのインスタンスです。HttpSessionの実装である org.codehaus.groovy.grails.web.servlet.mvc.GrailsHttpSession が参照できます。HttpSessionのメソッド実行のほか、セッション内容に対してGroovyマップのようにアクセスすることができます。
def username = session["username"] session.username
requestは、HttpServletRequestのインスタンスです。HttpServletRequestの実装である org.apache.catalina.core.ApplicationHttpRequest が参照できます。HttpServletRequestにある通常の機能に加えて、Grailsでは便利なメソッド・フィールドが追加されています。
※(注意)機能によっては開発時に使う必要は無く、内部的に利用するための機能でもあるので、乱用注意!
RESTで活用できる機能。それぞれXML、JSONを受け取った場合に以下のように参照できます。
def xml = request.XML // XmlSlurperのGPathResult def json = request.JSON // GrailsのJSONObject
リクエストがgetまたはpostかを判別する。
if(request.get){ //getの場合 }else if(request.post){ //postの場合 }
HttpServletRequestのgetAttributeも簡単にアクセスできるようになっています。アトリビュートの参照は、例のように簡単にアクセスできるほか、requestに対して、each、find、findAllも使用できます。
def username = request['username'] def score = request.score
フォーム送信時に<form>にenctype="multipart/form-data"が存在した場合、言い換えるとマルチパートデータのフォーム送信時は、自動的に、MultipartHttpServletRequestになります。ファイルアップロードを行う場合は、request.getFile()として、次のコード例のように取得できます。request.getFile()で取得したインスタンスはSpringのMultipartFile (org.springframework.web.multipart.MultipartFile)です。
def uploadFile = request.getFile('upload') uploadFile.transferTo( new File('/dir/to/save/file.jpg') )
responseは、HttpServletResponseのインスタンスです。HttpServletResponseの実装である org.codehaus.groovy.grails.web.sitemesh.GrailsContentBufferingResponse が参照できます。
def downloadFile = { byte[] bytes = // バイト配列 response.outputStream << bytes }
servletContextは、ServletContextのインスタンスです。ServletContextの実装である org.apache.catalina.core.ApplicationContextFacade が参照できます。
servletContextも他のと同様に、getAttributeが簡単にアクセスできるようになっています。
def setting = servletContext["setting"] servletContext["setting"] = "hoge"
flashは、次のリクエストまで使用できる一時保存マップです。次のリクエストが完了すると動的に内容がクリアされます。
実態は、org.codehaus.groovy.grails.web.servlet.GrailsFlashScopeです。
次の例では、indexアクションにリクエストを受けるとflash.messageにメッセージを代入して、homeアクションにリダイレクト、homeアクションで処理されるビューでflash.messageが参照できます。そして処理が完了すると、flashはクリアされます。
def index = { flash.message = "Hello!" redirect(action:home) } def home = { // }
前半でも簡単に紹介した、リクエストパラメータを収納している変数です。公式ドキュメントでは、ミュータブル多次元ハッシュマップと説明されています。クラスの実態は、org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMapです。
参照方法の簡単な例です。
println params.username def user = User.get(params.id)
paramsは、ただ単にリクエストパラメータを収納して参照するだけではなく、コントローラの開発を柔軟でスムーズに進めるための機能を多く持っています。その説明は次の「リクエストパラメータ型変換、バインディング、コマンドオブジェクト」で細かく説明していきます。
Grailsでは、リクエストパラメータをドメインクラス・モデルと簡単にバインドできる仕組みが提供されています。バインディングの説明をするには、ドメインクラスが関係してきますが、今回はドメインクラスの深い内容は割愛します。
paramsにはリクエストで受けた内容を正確に受け取るために便利な型変換機能が備えられています。
例えば、次のようなコードがあるとします。
def num = params.num if(num){ println num+10 }
この例では、params.numに文字列がセットされていた場合は、正常な処理結果が得られません。これを解決するのにparams型変換を使用します。
def num = params.int('num') if(num){ println num+10 }
params.numであれば文字列の場合でもif文を通ることができますが、params.int('num')と指定する事で、数値以外であれば確実にnullを返すようになるので、条件を通ることができません。このように、params型変換(nullセーフコンバータとも呼ぶ)を使用することで処理を無駄なく確実にします。
通常Webリクエストは主に文字列でパラメータから受け取るので、型検証や変換を予め行う必要があります。その処理をわかりやすくシンプルに記述できます。intの他にboolean、long、char、shortが使用できます。
この型変換には同じ名称のパラメータを配列に変換するリスト変換も存在します。リクエストの内容が、 /home/index?key=apple&key=lemon の用に同じ名称が複数存在する物を処理するには次のように記述します。
def keys = params.list('key') keys.each{ println it }
params.keyと取得しても確実にリクエスト内容に "key"が複数存在するのであれば、リストとしても取得できるのですが、もし"key"が1個のみの場合はリストにならないので、その場合の処理を正確に記述するために使えます。
// リクエストが/index?key=appleの場合は。 def keys = params.key //keysはStringになる。 // リクエストが/index?key=apple&key=lemonの場合は。 def keys = params.key //keysは配列になる。 def keys = params.list('key') // リクエスト内容の数に関係無く確実に配列になる。
先ずはじめに単純な使用方法の例をおさらいします。リクエスト内容が、 /home/index?username=tyama&age=27&email=tyama@xmldo.jp とすると、アクションの内部で、paramsから簡単に取得できました。
def username = params.username def email = params.email
ドメインクラスUserを例として、
class User { String username String password String email int age }
先ほどのパラメータ内容をドメインクラスにバインドする場合は、次のようにするのではなく。
def user = new User() user.username = params.username user.email = params.email
ドメインにリクエストパラメータをバインドさせるには、単純にparamsをconstructorに与えるか、propertiesに代入するだけです。
def user = new User(params) //または def user = new User() user.properties = params
bindDataメソッドでバインディングを行うこともできます。
def user = new User() bindData(user, params)
※但しデータバインドは、内部で自動でバインドを行うために速度的な懸念もあります。従って先に挙げた例も間違いではありませんので、速度を検証してみて使いやすい方を選ぶのも良いと思います。個人的意見では、間違った代入や、コードの見やすさを考えるとGrailsが提供するデータバインドを使用した方が良いと思います。
データバインディングの問題点として、リクエストに更新を行うべきでない不正なパラメータも付加されてしまった場合でも、自動でセットしてしまうという懸念があります。それを制御するために、不用なパラメータのバインディングを行わないようにするために、propertiesにバインディングを行いたいフィールドを指定する方法があります。この例ではリクエストとして、username、emailの他に不正に付加してpasswordが含まれているとします。user.properties['username','email'] と指定する事で、不用なパラメータpasswordは、paramsからバインドされることはありません。
def user = User.get(1) user.properties['username','email'] = params
bindDataを使用した例。
def user = new User() bindData(user, params, [ include: ['username','email'] ]) //除外する場合は。 bindData(user, params, [exclude:'password'])
自動生成されるスカッフォルドのコードについては、このような対策がされていません。スカッフォルドで生成したコードを重要箇所にそのまま使用することは無いとは思いますが、それも含めて、セキュリティを重要視するモデルに対してのバインディングには必ずこの方法を使用しましょう。
今回はここまでです。今回はコントローラの基礎から、リクエストの処理から、簡単なバインディングまで解説しました。いよいよ、次回はリレーショナルのあるドメインのバインディング、コマンドオブジェクト、REST、コントローラにまつわる制御の仕組み、GSP、URLマッピングを解説します。ではまた次号でお会いしましょう!