CRUD による基本的な管理機能のセットアップ
今のところ、ブログ UI を使って新しいブログや控えめなコメントを作る方法はありません。Play は、すぐに使えて、基本的な管理機能を素早く生成する CRUD モジュールを提供しています。
CRUD モジュールの有効化
Play アプリケーションはいくつかのアプリケーションモジュールから組み立てることができます。これにより、複数のアプリケーションをまたいでアプリケーションコンポーネントを再利用したり、大きなアプリケーションをいくつかの、より小さなアプリケーションに分割することが可能です。
CRUD モジュールは、シンプルなリストとフォームを作るためにモデルクラスに対してイントロスペクションを行う、汎用アプリケーションです。
CRUD モジュールを有効にするには、 /yabe/conf/application.conf ファイルに次の行を追加します:
# Import the crud module
module.crud=${play.path}/modules/crud
そして、このモジュールには、今回、再利用できる一般的な routes のセットが含まれています。これらのルートをインポートするには、 /yabe/conf/routes に次の行を追加するだけです:
# Import CRUD routes
* /admin module:crud
これで、URL パスの接頭辞として /admin を使用する、すべての CRUD ルートがインポートされます。
新しいモジュールを認識させるために、アプリケーションを再起動する必要があります。
CRUD コントローラの宣言
管理エリアに統合するどのモデルオブジェクトについても、 controllers.CRUD スーパーコントローラを継承するコントローラを宣言しなければなりません。これはとても簡単です。
モデルオブジェクトごとに 1 つのコントローラを作成します。例えば、 Post オブジェクト用に、 /yabe/app/controllers/Posts.java ファイル中に Posts コントローラを作成します。
package controllers;
import play.*;
import play.mvc.*;
public class Posts extends CRUD {
}
対応するコントローラを作成するためには、モデルオブジェクトの名前を複数形にすることが規約で定められています。こうすることで、Play はそれぞれのコントローラに関連するモデルオブジェクトを自動的に見つけます。もし違う名前をつける必要がある場合は、 @CRUD.For アノテーションを使うこともできます。マニュアルページ を確認してください。
すべてのモデルオブジェクトについて同じように作りましょう:
package controllers;
import play.*;
import play.mvc.*;
public class Users extends CRUD {
}
package controllers;
import play.*;
import play.mvc.*;
public class Comments extends CRUD {
}
package controllers;
import play.*;
import play.mvc.*;
public class Tags extends CRUD {
}
これで、ただ URL http://localhost:9000/admin/ を開くだけで、管理機能を使用することができるはずです。
管理機能をざっと見てみると、リストされたオブジェクトの名前が少し雑なことに気付くと思います。これは、デフォルトではモデルオブジェクトの判読できる表現を得るために、単純に toString() を使用することが原因です。
すべてのモデルオブジェクトについて、 toString() の適切な実装を提供することで、簡単にこれを修正することができます。例えば、User クラスについて、以下のようにします:
...
public String toString() {
return email;
}
...
バリデーションの追加
生成された管理機能の主な問題は、フォームにどのようなバリデーションルールも含まれていないことです。しかし、実際には CRUD モジュールは、モデルクラスが適切にアノテーションで注釈されている場合、バリデーションルールを抽出することができます。
User クラスにいくつかアノテーションを追加しましょう:
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import play.data.validation.*;
@Entity
public class User extends Model {
@Email
@Required
public String email;
@Required
public String password;
public String fullname;
public boolean isAdmin;
...
これで、 User モデルオブジェクトの編集または作成フォームにアクセスしてみると、このフォームに自動的に、魔法のようにバリデーションルールが追加されていることを見ることができます:
Post クラスにも同じことをしましょう:
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import play.data.validation.*;
@Entity
public class Post extends Model {
@Required
public String title;
@Required
public Date postedAt;
@Lob
@Required
@MaxSize(10000)
public String content;
@Required
@ManyToOne
public User author;
@OneToMany(mappedBy="post", cascade=CascadeType.ALL)
public List<Comment> comments;
@ManyToMany(cascade=CascadeType.PERSIST)
public Set<Tag> tags;
...
結果を確認してみます:
ここに興味深い副作用が見られます: @MaxSize バリデーションルールは、Play が Post フォームを表示する方法を変更しています。今では content フィールド用に大きなテキストエリアを使用しています。
最後に、 Comment と Tag クラスにも同様にバリデーションルールを追加しましょう:
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import play.data.validation.*;
@Entity
public class Tag extends Model implements Comparable<Tag> {
@Required
public String name;
...
package models;
import java.util.*;
import javax.persistence.*;
import play.db.jpa.*;
import play.data.validation.*;
@Entity
public class Comment extends Model {
@Required
public String author;
@Required
public Date postedAt;
@Lob
@Required
@MaxSize(10000)
public String content;
@ManyToOne
@Required
public Post post;
...
より良いフォームラベル
見ての通り、フォームのラベルが少し雑です。Play はフォームのラベルに Java のフィールド名を使用します。これをカスタマイズするのに必要なのは、単に /yabe/conf/messages により良いラベルを提供することだけです。
ちなみに、アプリケーションがサポートする言語ごとに messagaes ファイルを分けて作成することができます。例えば、フランス語のメッセージは yabe/conf/messages.fr ファイルに書くことができます。12 章で地域化された言語を追加する方法をご覧に入れます: 国際化と地域化.
これらのラベルを messages ファイルに追加しましょう:
title=Title
content=Content
postedAt=Posted at
author=Author
post=Related post
tags=Tags set
name=Common name
email=Email
password=Password
fullname=Full name
isAdmin=User is admin
どれかフォームをリフレッシュすれば、新しいフォームラベルが表示されます:
‘Comments’ データリストのカスタマイズ
CRUD モジュールは完全にカスタマイズ可能なように作られています。例えば、コメントリストのページを見てみると、データを一覧するやり方は良くありません。もっと列を、特に容易に一覧をフィルタできるようにする ‘related post’ 列を追加したくなります。
実際のところ、アプリケーションがこのマスタを保持する限り、CRUD モジュールによって提供された アクション または テンプレート のどれでも完全に上書きすることができます。例えば、‘comment list’ ビューをカスタマイズする場合にしなければならないのは、別の /yabe/app/views/Comments/list.html テンプレートを用意することだけです。
CRUD モジュールが有効化されている場合、追加の Play コマンドが提供されます。 crud:ov コマンドはテンプレートを上書きする手助けとなります。コマンドラインから、以下のようにタイプしてください:
$ play crud:ov --template Comments/list
/yabe/app/views/Comments/list.html に、新しいテンプレートが作成されます:
#{extends 'CRUD/layout.html' /}
<div id="crudList" class="${type.name}">
<h2 id="crudListTitle">&{'crud.list.title', type.name}</h2>
<div id="crudListSearch">
#{crud.search /}
</div>
<div id="crudListTable">
#{crud.table /}
</div>
<div id="crudListPagination">
#{crud.pagination /}
</div>
<p id="crudListAdd">
<a href="@{blank()}">&{'crud.add', type.modelName}</a>
</p>
</div>
まさに #{crud.table /} がテーブルを生成します。 fields パラメータを使うことで、さらに列を追加するようカスタマイズすることができます。以下のようにしてみてください:
#{crud.table fields:['content', 'post', 'author'] /}
これでテーブルに列が 3 つ表示されます:
いくつかのコメントの content フィールドはとても長くなることがあり、問題です。必要な場合はこれを切り詰められるよう、 #{crud.table /} タグがこれを扱う方法を特別にしてみましょう:
以下のようにして、 #{crud.custom /} タグを使うあらゆるフィールドの表示方法をカスタマイズすることができます:
#{crud.table fields:['content', 'post', 'author']}
#{crud.custom 'content'}
<a href="@{Comments.show(object.id)}">
${object.content.length() > 50 ? object.content[0..50] + '…' : object.content}
</a>
#{/crud.custom}
#{/crud.table}
はい、ここでの作業には groovy の糖衣構文が使われています。
‘Post’ フォームのカスタマイズ
生成されたフォームについても同様にカスタマイズ可能です。例えば、Post フォームにタグを入力する方法は、とても簡単ではありません。もっと良くすることができます。 Posts/show テンプレートを上書きしましょう:
$ play crud:ov --template Posts/show
/yabe/app/views/Posts/show.html に、新しいテンプレートが作成されます:
#{extends 'CRUD/layout.html' /}
<div id="crudShow" class="${type.name}">
<h2 id="crudShowTitle">&{'crud.show.title', type.modelName}</h2>
<div class="objectForm">
#{form action:@save(object.id), enctype:'multipart/form-data'}
#{crud.form /}
<p class="crudButtons">
<input type="submit" name="_save" value="&{'crud.save', type.modelName}" />
<input type="submit" name="_saveAndContinue" value="&{'crud.saveAndContinue', type.modelName}" />
</p>
#{/form}
</div>
#{form @delete(object.id)}
<p class="crudDelete">
<input type="submit" value="&{'crud.delete', type.modelName}" />
</p>
#{/form}
</div>
tags フィールドをカスタマイズするために #{crud.form /} タグをハックすることができます:
#{crud.form}
#{crud.custom 'tags'}
<label for="tags">
&{'tags'}
</label>
<style type="text/css">
.tags-list .tag {
cursor: pointer;
padding: 1px 4px;
}
.tags-list .selected {
background: #222;
color: #fff;
}
</style>
<script type="text/javascript">
var toggle = function(tagEl) {
var input = document.getElementById('h'+tagEl.id);
if(tagEl.className.indexOf('selected') > -1) {
tagEl.className = 'tag';
input.value = '';
} else {
tagEl.className = 'tag selected';
input.value = tagEl.id;
}
}
</script>
<div class="tags-list">
#{list items:models.Tag.findAll(), as:'tag'}
<span id="${tag.id}" onclick="toggle(this)"
class="tag ${object.tags.contains(tag) ? 'selected' : ''}">
${tag}
</span>
<input id="h${tag.id}" type="hidden" name="${fieldName}"
value="${object.tags.contains(tag) ? tag.id : ''}" />
#{/list}
</div>
#{/crud.custom}
#{/crud.form}
これはちょっと hacky であり、もっとうまくやることもできますが、ちょっとした JavaScript を使うことでタグセレクタはよりシンプルになりました:
管理機能のスタートは上々です!
次の章 に進みましょう