Grailsで家計簿--第5章「レシートの商品番号を活用した入力システム」後編--

前へ 次へ

5.1FujiControllerのsaveメソッド

Grailsを記述するスクリプト言語「Groovy」には実はメソッドという書法はなくて、{}の中に書かれた処理というものに名前をつけているだけで、その形から「クロージャ」と言うらしいんですが、ここはひとつメソッドと呼ばせてください。パラメータ名itemcodeで送られてきた商品コードをどう処理するか?それはFujiController.groovy中の、メソッド「save」の記述を変更します。

新規データの作り方が問題だ

メソッドsaveは、送られてきたパラメータから新しいFujiのインスタンスを作って、Grailsのsaveメソッドに渡してしまうだけです。その新しいFujiインスタンスは、以下のように作成されます。
def fujiInstance = new Fuji(params)
paramsは配列です。その中味は、 が、ガンくび揃えて渡されてくることが想定されています。でも、 create.gspを 編集しちゃってこれは崩壊しました。itemcodedataが来なくなり、代わりにこのままでは使えないitemcodeが来ることになってしまったのです。送られてくるパラメータを一括paramsで扱うことはできません。

パラメータはあとで喰わせろ

そこで、まずプロパティの値が空っぽのFujiInstanceを作ります。大丈夫、プロパティを全部揃えるのは(Grailsのシステムのsaveメソッドで)いざ表に書き込む、というときまでにやりゃいいんです。
def fujiInstance = new Fuji()

そしたら、まずそろっているだけのパラメータを喰わせます。パラメータの一部だけを喰わせる方法はいろいろありますが、「itemcodedataのプロパティだけハズす」というのは以下の方法です。

bindData(fujiInstance,params,[exclude:"itemcodedata"])
bindDataはドメインクラスではなくControllerクラスに定義された関数です。ここではFujiControllerの実行時オブジェクトが呼び出すことになるので、呼び出し元は省略されます。パラメータを喰わせようという相手は、bindDataメソッドの第1番目の引数になります。2番目の引数がparamsで、3番目が条件を配列に入れたものです。

一方、いよいよパラメータitemcodeをもとに、ItemCodeオブジェクトを検索します。これは以下のようにします。

fujiInstance.itemcodedata=ItemCode.findByItemcode(params.itemcode)
findByItemcodeというメソッドに注目してください。findByItemCodeではありません。ItemCodeクラスのパラメータitemcodeが「キャメルケース」のルールに乗っかって最初の1文字だけがIと大文字になったのです。
こんなメソッドは我々は作っていません。でもItemCodeクラスにitemcodeというパラメータを定義した瞬間(でもなくてサーバが上がって中味がコンパイルされたときなんだけど)、自動的にこういうメソッドが定義されてくれるのです。これでパラメータitemcodeの値で検索したItemCodeオブジェクトを得ることができます。

おおっとぅ。得られるのは検索されたオブジェクトの配列なんじゃないかと思った人は思慮深い人です。ワタシもたった今思いました。でもGrailsはさすがです。1個のデータを求めて検索したのに配列が得られてグハァッとかなる場合が多いことを知っているのですね。findByナントカメソッドでは、見つかった最初のオブジェクトが得られます。見つかったオブジェクトの配列を得るにはfindAllByナントカになるのです。

見つかったオブジェクトが、fujiInstanceのプロパティitemcodedataの値になります。これは上記のように直にfujiInstance.itemcodeと書いちゃって構いません。Javaやって長い方はこういう直接のアクセスはお好きでないかも知れませんが、これはGroovyの書き方だからで、コンパイルされて最終的にJavaプログラムになるときにはちゃんとsetItemcodeみたいな礼儀正しい方法になっているはずですから安心してください。 これでfujiInstanceは保存可能になりました。あとは元のコードの処理に任せます。そこでsaveメソッドの全文は以下のようになります。

def save = {
		def fujiInstance = new Fuji()
		bindData(fujiInstance,params,[exclude:"itemcodedata"])
		fujiInstance.itemcodedata=ItemCode.findByItemcode(params.itemcode)
        //このあとは元のコードのまま
					if (fujiInstance.save(flush: true)) {.....
さてサーバは再起動しなくても大丈夫。FujiのCreateページに新規データを入れます。ただーし、今はまだ「すでにItemCodeに登録してあるコード」を入れてください。たとえば今の例では「3239」を「ヨーグルト」として登録してあるので、Itemcode「3239」を入れて、価格Priceに「128」を入れて、Pdateは本日のまんまでもいいですしテキトーな期日にしてもいいので、値がそろったら運命の「Create」ボタンを押すーッ。

エラーが出たらヴァナタの負け。ていうかsaveメソッドを確認してください。正しく書き変わってあればShowページに切り替わります。

5.2Fujiのshow.gspを編集

Showページでは「Itemcodedata」の一時的な識別名が表示されています。たとえば、「ItemCode:1」とあれば、それはidが1で登録されているItemCodeオブジェクトです。入力した商品コードがそのオブジェクトに該当すれば(このページに切り替わった以上は、該当してるはず)成功です。

これでコントローラの編集が成功したことがわかりました。次はShowページの「idが1のItemCodeオブジェクト」という表示を、「ヨーグルト」に変更する作業です。つまり、そのオブジェクトの「itemname」の値を表示させるのです。 FujiのShow.gspを編集しましょう。

itemcodedataのさらにitemnameを表示させる

修正すべきは以下のところです。

<td valign="top" class="value">
	<g:link controller="itemCode" action="show" id="${fujiInstance?.itemcodedata?.id}">
		${fujiInstance?.itemcodedata?.encodeAsHTML()}</g:link>
</td>
まず、全体が<td>タグで挟まれていますから、表のセルのひとつです。でもホントに表を使いたいわけじゃなくて、項目や値をまっすぐ揃えて配置するのに表の書式を使っているだけです。

<td>タグの中には<g:link>タグがあります。Grailsで「リンク」を表すタグです。元はitemcodedataを表示するこの部分、クリックするとコイツが該当するItemCodeオブジェクトのShowページにリンクするようになっています。だから「ItemCode:1」と書かれていてこりゃ何じゃと思っても,クリックすればその内容がわかるというしくみです。

しかしいちいちクリックして内容を見る必要をなくしましょう。要は買ったものがナニかわかればいいのです。つまり商品名、itemnameプロパティーです。

「リンク」を表すタグに挟まれている太字の部分が、Showページに「表示される」内容です。「?」がついているのは、空でなければという安全措置です。「ヌル(とかニルとか)のデータがメソッドを呼ぶことはできません」というエラーが表示されてアプリケーションがストップしたのにムカーという思いをしたことは多いのではないでしょうか。Grailsでは「?」のおかげで、ヌルデータになった時点で黙って処理をやめてくれます。

また、「encodeAsHTML」は「文字列にタグが入っていてもそのまんま表示する」という、いわゆる不正タグ注入防止のアレです。ワタシも、なんでそれが「HTMLとしてエンコードする」というメソッド名になるのかうんと悩んだんですけど、こういうことらしいですね。

文字列に<が入っていたらアンパサンドltセミコロンに「エンコード」する
そんなわけで、ココはこのように書いてやれば、該当するItemCodeオブジェクトの「商品名」を表示することができます。
fujiInstance?.itemcodedata?.itemcodename.encodeAsHTML()

その上の行が、「ラベル」ですが、「Itemcode」という文字が表示されるようになっています。これ、「Itemname」に直しておきます。

<td valign="top" class="name"><g:message code="fuji.itemcodedata.label" default="Itemname" />

以上、今度は作成したあと、入力した商品コードからちゃんと商品名が検索されて表示されます。といってもまだ、ItemCodeデータとして登録済みの商品コードを慎重に選んでください。

5.3Fujiのindex.gspを編集

最後にindex.gspに記述されている「一覧ページ」にも、ItemCodeオブジェクトのidではなく商品名が表示されるようになるように書き換えれば、この件はひと安心です。それは以下のごとし。

index.gspの中で、ItemCodeオブジェクトのidが表示されている列はこのように書いてあります。

	<td>${fieldValue(bean: fujiInstance, field: "itemcodedata")}</td>
太字の部分を"itemcodedata.itemname"に直します。

ついてるラベルは、以下のように書いてあります。

<th><g:message code="fuji.itemcodedata.label" default="Itemcodedata" /></th>
こちらは、太字のItemcodedataをItemnameに直します。

これで、「ItemCodeに登録済みの商品コードであれば、Fujiのほうではコードを入力すれば商品名がわかる」システムになりました。
次は、「ItemCodeに登録してない商品は、Fujiから登録」できるようにしましょう。次へ