まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

Play Framework 2.3 For Java ことはじめ #8 フォームを扱う編

f:id:masato47744:20140720125846p:plainPlay Framework 2.3 For Java 入門記事一覧

第8回はWebアプリケーションで重要な要素の一つであるフォームの話です。

フレームワークに乗っかれば、リクエストのマッピング、バリデーション、エラー表示などいろいろなことを面倒みてくれます。何気にこの仕組みって自分で作ろうとしたらすごく大変です。では、いってみましょう。

1. Playでのformの扱い方を見てみる

まず、DocumentationにFormの説明があるんですが、どこから始めればいいかというか、リクエストとかデータベースとの関わりみたいのがピンときません。
どうしようもんかとGoogleの海をサーフィンしてたら、そもそもPlayのトップページにチュートリアル動画があることに気づきました。
このチュートリアル動画がなんとまぁ分かりやすいこと。なぜ、今まで見なかったんだろうかw

ということでこの動画を見てみるとフォームはだいたいこんな感じで扱えます。

  • Personクラスのモデルを作る(このチュートリアルではEBeanなのでModelを継承)
@Entity
public class Person extends Model {
    @Id
    public String id;

    public String name;
}
  • コントローラーで、play.data.Formクラスの便利メソッドを使ってリクエストをモデルに変換してデータベースに保存
public static Result addPerson() {
    Person person = Form.form(Person.class).bindFromRequest().get(); 
    person.save();
    return redirect(routes.Application.index());
}
  • viewで<form>を作る
<form action="@routes.Application.addPerson()" method="post">
    <input type="text" name="name" />
    <button>Add Person</button>
</form>

うーん、分かりやすい。Form.form(クラス)ってやるだけでリクエストを簡単にマッピングしてくれちゃうんですね。

じゃあ、その逆は?

編集画面なんかでは、逆にモデルからフォームへ変換したくなりますよね。それもちゃんと用意されてます。

Person person = Person.find.byId(id);
Form<Person> form = form(Person.class).fill(person);

モデルをとってきてfillに渡すだけ。この辺は苦労しなさそうです。

2. バリデーションとエラーメッセージ

それじゃ、バリデーションとエラーメッセージの仕組みはどうなってるのか。
いまどきのフレームワークなら当然モダンな感じで用意されてるんでしょう。
ということでJavaFormsを見てみる。

まず、バリデーションを入れたいフィールドに、アノテーションをつける。
さっきのPersonクラスで名前を必須にしたい場合はこんな感じ。

@Entity
public class Person extends Model {
    @Id
    public String id;

    @Constraints.required
    public String name;
}

そして、コントローラー側では、formを取得して、hasErrors()でバリデーションを検知できる。
そして今まではokメソッドだったけど、badRequestメソッドで返すようだ。

Form<Person> personForm = form(Person.class).bindFromRequest();
if(personForm.hasErrors()) {
    return badRequest(createForm.render(personForm));
}

ビュー側では、こんな感じでエラーメッセージを取得できる。

@for(error <- personForm("name").errors) {
    <p>@Messages(error.message)</p>
}

ちなみに、このエラーメッセージは、conf/messagesというところに書いておけば上書きできる。

error.required=このパラメータは必須フィールドです

ふむふむ。雰囲気はなんとなく分かった。

3. ヘルパーメソッド

まず、基本的な@form@inputTextなどは他のフレームワークでも一般的に用意されてるのでなんとなくタグを生成してくれるんだろうなっていうのは分かった。ただ、例えば、@formに渡せるパラメータは何なのかAPIの見方がScalaの書き方なので、すぐに分からないのでまずはリファレンスの見方を理解する。

def apply(action: Call, args: (Symbol, String)*)(body: ⇒ Html): play.twirl.api.HtmlFormat.Appendable

APIのformValue Membersのところにメソッドっぽいのあったのでこれのようだ。
def applyっていうやつがあって、ググってみるとこのapplyというやつはScalaでは特別な意味を持ってるようです。applyは省略しても呼ばれるメソッドということなので、だから、@form(){ ... }で呼び出せたんですね。
ってことは、、ということで試しにやってみたら@form.apply(){ ... }でもいけた。

そして、下の方に使い方の例と各引数の説明があった。

Generate an HTML form.

Example:

@form(action = routes.Users.submit, args = 'class -> "myForm") {
...
}
action  The submit action.
args    Set of extra HTML attributes.
body    The form body.

うん。引数の説明とか分かりやすくてとてもいいです。
でも、使い方の例のところの、'classの前についてる'おまえ。お前はダメだ。一体君はどこにかかっているんですか?いつ閉じられるんですか?閉じないんですか?はっきりしてください。と不満が爆発しそうでしたが、そもそも文字列をくくるものではありませんでした。

シングルクオテーション scalaとかでググったらちゃんとみつかりました。

Scala文字列メモ(Hishidama's Scala String Memo)

シンボル(Symbol)も文字列を表す。[2011-02-07]
シンボルのリテラルは、文字の並びの前にシングルクォーテーションを1つ付ける('名前)。
もしくはSymbol("名前")でインスタンスを取得する。(Symbolオブジェクトのapply()メソッド) スペースの入ったシンボルや数字から始まるシンボルを取得するには後者の方法を使うしかない。
Stringとの違いは、Stringは同じ内容でも異なるインスタンスが存在しうる("abc"とnew String("abc")はインスタンスが異なる)のに対し、Symbolでは同じ内容ならインスタンスも等しい。

はぁーなるほど。シンボルでしたか。rubyでいう:ね。シンボルって未だにあんまり概念が理解できないんだけど、要は絶対的な値というか、番号というかそんな感じでよかった気がする。
人間から見たら、classという5文字であることは、'classでも、"class"でも変わらないんだけど、コンピュータの世界では別ものであり、シンボルの方は絶対に変わらない値なので、生成のコストが低いみたいなメリットがあるって理解してる。

この調子で、API見れば、使い方分かりそうだ。Play For Javaと言えど、Scalaの文法は覚える必要があるなぁこれ。今回覚えたつまづきそうなJavaと違うScalaの文法を整理しとく。

4. FieldConstructor

あとは、このFieldConstructorというのが理解するのが大変だったけど、Scalaの文法調べたり、APIリファレンス見てるうちにやっと分かってきた。

つまり、FieldConstructorというやつは、@form() { }の中括弧の中に書いてあるhelperで作った(直書きでinputタグ書いた場合は適用されなかった)inputタグに対して自動的に適用されるhtmlの装飾みたいなものってこと。
だって、どうせ、ただの<input>タグだけとかそんなんないでしょ?<label>とか必要になるでしょ?ってことで作られたんだと思う。

例えば、こう書くと

@form(routes.Application.index()) {
    @inputText(computerForm("name"), '_label -> "name")
}

こんなふうに、FieldConstructorが適用されたhtmlが返ってくる。これは、defaultFieldConstructorとして定義されています。

<dl class=" " id="name_field">
    <dt><label for="name">Computer name</label></dt>
    <dd>
    <input type="text" id="name" name="name" value="">
    </dd>
    <dd class="info">Required</dd>
</dl>

でも、今どきのbootstrapとか自分たちで作ってるサイトもそうだけど、全てがこんな形になってるわけじゃなくて、独自のhtmlタグの構造になってるはず。
それを、オーバーライドすることもできる。

例えば、Twitter Bootstrapで言えば、こういうテンプレートファイルを作っておいて、

@(elements: helper.FieldElements)

@***************************************************
* Generate input according twitter bootstrap rules *
***************************************************@
<div class="clearfix @if(elements.hasErrors) {error}">
    <label for="@elements.id">@elements.label</label>
    <div class="input">
        @elements.input
        <span class="help-inline">@elements.infos.mkString(", ")</span> 
    </div>
</div>

利用するフォームタグのhtmlで、このFieldConstructorを設定する。

@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } 

すると、さっきまでのhtmlと違い、bootstrap用のhtmlが出力されるようになる。

<div class="clearfix ">
    <label for="name">Computer name</label>
    <div class="input">
    <input type="text" id="name" name="name" value="">
        <span class="help-inline">Required</span> 
    </div>
</div>

ようは、FieldConstructorってフォームタグのところを整形するためのものだったんすね。やっと分かりました。

まとめ

個人的には、思いのほか難しかった。Scalaの文法が混じってるところが結構ハードル高いのかもしれない。 リクエストをモデルに変換する部分やバリデーションやエラーメッセージもフレームワークの機能として用意されてる。さらに、ヘルパーとしてform周りのタグを便利に生成してくれますよーということがなんとなく分かりました。

第9回は、FieldConstructorが結構難しいので、その出し分け方法について、さらに突っ込んでみることにしました。