log

Play--Scaffoldを使ってみた

最近、Play framework を触ってみてる。

かなり Rails っぽくサクサク開発できそうなので気に入ってるんだけど、やはり Rails といえば scaffold が欲しい。

Play は標準では scaffold は持ってないけど、モジュールとして Play--Scaffold が提供されている。

Rails の scaffold とは若干機能が違う。以下、相違点。


  • Model はユーザ自身でファイルに記述(scaffold は Model を元に Controller, View を自動生成する)

  • Model 間の関連性(多対一や多対多)を考慮して form を生成

  • ログイン機能を自動生成(オプション)

Play--Scaffold をインストールして使うまでは以下な感じ(今回は Play 1.2.3, Play--Scaffold 0.1 を使用)。

モジュールをインストール

$ play install scaffold

対象プロジェクトを作成

$ play new play-scaffold
$ cd play-scaffold

モジュールのパスを指定

$ vim conf/application.conf
module.scaffold=${play.path}/modules/scaffold-0.1

DB を使用可能にしておく

$ vim conf/application.conf
db=mem

Model クラスを作成

今回は Play framework のチュートリアルにて使用されている yabe ( Yet Another Blog Engine ) の Model を使うことにした。

ER 図(Taggingは仮想的なテーブルで、実際にクラスは作成しない)
yabe の ER図

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;

public User(String email, String password, String fullname) {
this.email = email;
this.password = password;
this.fullname = fullname;
}

public String toString() {
return email;
}
}

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 comments;

@ManyToMany(cascade=CascadeType.PERSIST)
public Set tags;

public Post(User author, String title, String content) {
this.comments = new ArrayList();
this.tags = new TreeSet();
this.author = author;
this.title = title;
this.content = content;
this.postedAt = new Date();
}
}

Comment クラス

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;

public Comment(Post post, String author, String content) {
this.post = post;
this.author = author;
this.content = content;
this.postedAt = new Date();
}
}

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 {

@Required
public String name;

private Tag(String name) {
this.name = name;
}

public String toString() {
return name;
}

public int compareTo(Tag otherTag) {
return name.compareTo(otherTag.name);
}
}

以上のファイルをそれぞれ app/models/ 以下に作成する。

それが終わったら、コマンドを実行する。

コマンドにはオプションがいくつかあって、それぞれ下記のような感じ。

–with-layout: 標準で用意されている main.html と Application.index を上書きする
–with-login: User class を生成し、ログイン/ログアウト機能を提供する
–include=…: View と Controller を生成する Model を指定
–exclude=…: View と Controller を生成しない Model を指定
–overwrite: 自動生成されるファイルと同名のファイルが存在する場合、上書きする

    • with-login を使う場合、Secure モジュールを有効化しておく必要がある。

$ vim conf/application.conf
module.secure=${play.path}/modules/secure

今回は --with-layout と --with-login, --overwrite を指定したが、その場合、User class が上書きされてしまう。
じゃあ User class を作っておく必要はないかと言うと、あらかじめ作っておかないと他モデルとの関連が解決できずにエラーになる。

Exception in thread "main" play.exceptions.CompilationException: User cannot be resolved to a type
at play.classloading.ApplicationCompiler$2.acceptResult(ApplicationCompiler.java:246)
at org.eclipse.jdt.internal.compiler.Compiler.handleInternalException(Compiler.java:672)
at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:516)
at play.classloading.ApplicationCompiler.compile(ApplicationCompiler.java:278)
at play.classloading.ApplicationClassloader.getAllClasses(ApplicationClassloader.java:412)
at play.modules.scaffold.Generate.generateScaffolding(Generate.java:121)
at play.modules.scaffold.Generate.run(Generate.java:178)
at play.modules.scaffold.Generate.main(Generate.java:190)

また、あらかじめ作成した User class のプロパティと scaffold により生成される User class のプロパティが異なると生成される View がちぐはぐになって実行時にエラーとなる。

仕方ないので、以下の手順で解決することにした。


  • 一旦仮の User class を作成

  • scaffold のコマンドを実行して、User class を上書き

  • 再度 scaffold を実行して、Controller と View を生成しなおす

というわけで、以下の要領で二回コマンドを実行した。

% play scaffold:gen --with-layout --with-login --overwrite
% play scaffold:gen --with-layout --with-login --overwrite

アプリケーションを実行してみる前に、生成された View の JavaScript 周りにいくつか修正が必要となる。

main.html の jQuery のバージョンを修正する(1.4.2 -> 1.5.2)

$ vim app/views/main.html
<script src="@{'/public/javascripts/jquery-1.5.2.min.js'}" type="text/javascript" charset="utf-8"></script>

jQuery UI をダウンロード

$ wget http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js
$ mv jquery-ui.min.js public/javascripts/jquery-ui-1.8.2.min.js

修正が終わったら、実際にアプリケーションを実行してアクセスしてみる。

$ play run
$ open http://localhost:9000

Model のリストと、右上に login へのリンクが表示されている。

このままだと login リンクが機能してないので、ソースを一部修正する。

$ vim app/views/main.html
<span style="float:right;">
#{if session.get("username") == null}
<a href="/secure/login">Login</a> <!-- /login から /secure/login へ -->
#{/if}
#{else}
<a href="/secure/logout">Logout</a> <!-- /logout から /secure/logout へ -->
#{/else}
</span>

ブラウザをリロードして login リンクをクリックしてみる。

デフォルトでは admin/admin でログイン可能(ソースに直書きなので実利用の際は修正が必要)。

ログインすると、top 画面へ戻るが右上のリンクが logout に変わっている。

Tags をクリックすると、タグ一覧のページへ移動するがこの時点ではデータが存在しないので空の状態。

Create をクリックすると新規作成のフォームへ移動する。

項目に入力して save ボタンを押すと、リストに項目が追加されているのが確認できる。
ついでにもう一つタグを作っておいて、今度は Postsの画面を見てみる。

同じように Create をクリックし、新規作成画面へ進む。

驚くのが、postedAt にフォーカスを合わせるとカレンダーが表示される点。Rails の scaffold だとここまでやってくれなかった記憶。

あと、Author(多対一)や Tags(多対多)のフォームがそれぞれセレクトボックスになってる点。ここも、Rails だと自分であれこれいじらないといけない。

Comments と Users はだいたい同じ感じなので割愛。

使ってみて思ったのは以下の二点。


  • Rails に比べて手数が増えるのが難点

  • ログイン機能やフォームなど、欲しい機能が最初から入ってるのは便利

Modelが自動生成できて、モジュールが Play 本体に組み込まれてくれると最高なんだけどな。