最近色々としがらみから開放されて時間に余裕ができたので、新しく何かに手を出そうと考えてました。まあ、タイトルでネタバレなんですが……。
やっぱ今まで避け続けてたwebアプリケーション辺りをさわろうかと。まあrailsでもやろうかとも思ったんですけど、大分乗り遅れてる感あるので、playでもいっかなーと(テキトー)。まあJava好きですし、Scalaもできるっぽいしでplayに至りました。僕が全然webフレームワーク使ってないんで不安はあるんですが、心が折れるまで頑張ってみます。
playの開発はrailsとかと同じようにターミナルでパチパチやるか、Typesafe Activatorなるものを使って、ブラウザ上で開発するか。
環境が悪いのか、くっそ遅かったのでterminalでやります。なーんか見た感じ途中からでも変更できそうだし。
インストール
play install site
これの右側の「これまでの配布形態」の方です。直接リンク貼ろうと思ったけど、リンクが明らかにバージョンアップしたらダメになりそうだったので。
展開したら適当なディレクトリにおいてパスを通す。
export PATH=$PATH:/relativePath/to/play
ごめんなさい、普通にbrew installできました。
play helpコマンド打ってヘルプが表示されればおk。
playのDocumentationを見ながらチュートリアルします。
terminal使おうかとも思ったけどなんかEclipseでもいいらしい。
プロジェクト作成
新しくアプリケーション作成を行います。
$ play new todolist
これ実行したディレクトリにプロジェクトが作られるので実行する場所注意。なんかパス通したりしてても怖いか後から移動とかもやめたほうがいいかもです。
application nameはプロジェクト名にとりあえず同じにしといて、テンプレートはJavaを指定。
play new するとアプリケーション名(もしくはプロジェクト名)と同じ名前のディレクトリができる。
ディレクトリ名 | 説明 |
---|---|
app/ | ここにmodels, views, controllersが入ってる。.javaファイルが入ってる |
conf/ | 全てのアプリケーションの設定ファイルが入ってる。application.confがメイン。routesもある。 |
project/ | ビルドスクリプトが入ってる。ビルドスクリプトはsbt(?)に基づいてる。新しいPlayアプリケーションはアプリケーションを正常動作させるデフォルトのビルドスクリプトが同梱されている。 |
public/ | 全てのパブリックに利用可能なリソース(JavaScript, css, 画像イメージ)が入ってる |
test/ | テストが入ってる。JUnitで書かれてる |
projectのsbtってなんだ……。
Simple Build Toolの略。Scalaのコンパイル、テストの実行に必要なツール。
- Scalaのコンパイル
- ライブラリの自動ダウンロード
- コードライブラリの作成(Scala, Javaで動くものはすべて使える)
- 作成したライブラリを公開サーバーにアップロードする
- テストの実行
ができる……らしい。よくわかんないね。飛ばして進もう。わかったら後から追記しようかな。
playコンソール
playのコンソールを開いてrunする。
$ play
[todolist] $ run
developmentモードでアプリケーションが実行される。ブラウザを開き、
http://localhost:9000/
にアクセス。
Welcome to Playのページが表示されればおk。
英語で色々書かれてます。どうやらこのページがどう表示されているかを書いてくれているらしい。さすがチュートリアル。
このアプリのエントリーポイント(プログラムが最初に見るファイルらしい!初めて知った、そんな言葉!)はconf/routesファイル。ここでアプリケーションがアクセス可能なURL全てを定義している。生成されたroutesファイルを開けば初期のrouteを確認できる。
# Home page
GET / controllers.Application.index()
webサーバーが/パスへGETリクエストを受信したらcontrollers.Application.index()メソッドを返す、という記述。早速ソースを見ていこう。controllersって書いてあるので当然app/controllersの下にあります。
package controllers;
import play.*;
import play.mvc.*;
import views.html.*;
public class Application extends Controller {
public static Result index() {
return ok(index.render("Your new application is ready."));
}
}
index()メソッドがResult型を返してる。アクションメソッドは全部この型を返さないといけない。
URLとメソッドの設定
このTODOアプリのアクションとURLの対応を書いてやる必要がある。もちろんそれはroutesに書く。
とりあえず必要な操作は「新規作成」「閲覧」「削除」の3つ。これらを定義していく。まあ、こんな感じ。
# Home page
GET / controllers.Application.index()
# Tasks
GET /tasks controllers.Application.showTask()
POST /task controllers.Application.createTask()
POST /tasks/:id/delete controllers.Application.deleteTask(id: Long)
これ、本来のチュートリアルではTaskを生成するメソッドがnewTaskなんだけど、気に食わないのでcreateTaskに変更。メソッド名は動詞じゃないと気持ち悪いんだけど、これ一般的なんかな。一応公式のチュートリアルだし……。
参考としてroutesファイルの文法
で、これ保存してブラウザリロード。サーバ立て直す必要はありません。
「いや、そんなメソッド無いんだけど……」って感じのエラー。とりあえずエラー消すためにメソッド書きましょう。実装とかナシで。index()のしたに3つのメソッドを追加。
public static Result showTasks() {
return TODO;
}
public static Result createTask() {
return TODO;
}
public static Result deleteTask(Long id) {
return TODO;
}
確認してみましょう。http://localhost:9000/tasks
まあ、当然な感じ。とりあえずこれで良しとする。まず最初にいじるべきはtopページ。topページは今あるtaskの一覧であるべき。topページをいじるならindexアクションをいじる。indexアクションでタスク一覧にリダイレクトするように変更する。
public static Result index() {
// 変更前
// return ok(index.render("Your new application is ready!"));
// 変更後
return redirect(routes.Apprication.showTasks());
}
Taskモデル
TodoListの扱うTaskを定義する。Taskは扱うデータ、つまりモデル。app/model下にクラスを生成する。
package models;
import java.util.ArrayList;
public class Task {
public Long id;
public String label;
public static ArrayList<Task> all() {
return new ArrayList<Task>();
}
public static void create(Task task) {
}
public static void delete(Long id) {
}
}
とりあえずダミー実装。後からデータベースに格納する処理を書きそう。
アプリケーションテンプレート
なんのことかよーわからんかったけど、viewsの下に書くものだからviwerでしょう。
Scalaライクなhtml生成用のテンプレートっぽい。
views/Application/index.scala.htmlっていうテンプレートを作ったら、views.html.Application.indexというrender()メソッドを持ったクラスが生成される。このメソッドはstaticなので、コードのどこからでも呼び出せる。Application.javaに書いてあったrender()メソッドのこと。
文法"@"
Scalaテンプレートでは@という文字が唯一の特殊文字。この文字がテンプレート中に出てくると動的コードとして認識される。エスケープしたいなら@@と書けばよい。
テンプレート引数
引数を取れる。引数はテンプレートの先頭行で宣言する。
こんな感じ
@ (customer: models.Customer, orders:List[models.Order])
変数名:型名 ですね。
それらを踏まえて、アプリケーションテンプレートを書く。
@(tasks: List[Task], taskForm: Form[Task])
@* 引数の受け取り *@
@import helper._
@main("Todo list") {
<h1>@tasks.size() task(s)</h1>
@* Taskが何個あるか出力 *@
<ul>
@for(task <- tasks) {
<li>
@task.label
@* Taskのラベルを出力し、 *@
@form(routes.Application.deleteTask(task.id)) {
@* Taskのラベルの横には削除用のフォームを設置 *@
<input type="submit" value="Delete">
@* 削除ボタン *@
}
</li>
}
</ul>
<h2>Add a new task</h2>
@form(routes.Application.newTask()) {
@inputText(taskForm("label"))
@* 作成するTaskの入力フォーム *@
<input type="submit" value="Create">
@* 作成ボタン *@
}
}
TaskForm
Applicationコントローラ(controllers/Application.java)にTaskを生成する型、Formをもたせる。以下のコードを追加すればよい。
static Form<Task> taskForm = Form.form(Task.class);
あとlabelを必須にするため、TaskクラスのString labelに@Requierdアノテーションをつけてみる。
@requierd
public String label;
最初のページをレンダリングする。
準備がだいたいできたので、tasksアクション(TaskクラスのshowTasks)を実装する。
public static Result tasks() {
return ok(
views.html.index.render(Task.all(), taskForm)
);
}
showTasksによりTaskListとTaskFormを呼び出し、index.scala.htmlテンプレートでレンダリングしたhtmlを返す。
確認してみましょう。http://localhost:9000/tasks
やったー、糞UIのTaskListができた!まあ、create押して例のunimplementsなんちゃらが出るだけだよね。実装してないし。実装しましょう。createTask()を実装します。
public static Result createTask() {
Form<Task> filledForm = taskForm.bindFromRequest();
// Form内にエラーがあるかどうかチェック
if (filledForm.hasErrors()) { // errorがあるならbadRequest
return badRequest( views.html.index.render(Task.all(), filledForm));
}
// Form にエラーがないならTaskを作って再表示
Task.create(filledForm.get());
return redirect(routes.Application.showTasks());
}
なんかチュートリアルのコードは無駄なelse句があって見づらかったのでリファクタ。うーん、この時点でcreateしたTaskの表示くらいはできると思ったんだけど、なんかできない。続けたらできるのかね。とりあえずスルーして続けてみよう。
データベース内のTaskの永続化
application.confでコメントアウトされている内容をコメントインすることでデータベースの使用を可能にする。私の環境だとL36, L37だったけど、他だと違ったりするかも。
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
H2というシンプルなインメモリデータベースを使用する。サーバの再起動は必要なく、ブラウザの更新だけでおk。インメモリってことは、PCシャットダウンした時とかにデータが残らない。残したいならファイルに保存すべき。
db.default.url="jdbc:h2:mem:play"を
db.default.url="jdbc:h2:file:play"にすればおk。
次はApplication.confでEbeanを有効にする。さっきのようにコメントインする。L54だった。
Ebean
ebean.default="models.*"
modelsパッケージ以下にある全てのエンティティ(オブジェクトのこと?)を管理する。
EbeanってのはO/Rマッピングフレームワーク。オブジェクトとリレーショナルデータベースを対応付けること。アプリケーションとデータベースのインターフェイス、かな?SQL文とかを直接埋め込むのは手間いし、なにより使用するデータベースが変更になった時に修正しないといけないとこが膨大になったりする。O/Rマッピングを使用したらJavaのコードを変更する必要がなくなる。なのでデータベースは直でさわるものではなく、O/Rマッピングを介して使用する。
PlayはEbeanに対応している。
modelをEbeanで管理するにはもう少しやることがある。app/models/Task.javaをちょっと変更する。
@Entity // 変更点1アノテーション追加
public class Task extends Model {
// 変更点2 Modelクラスを敬称
@Id // 変更点1アノテーション追加
public Long id;
@Required
public String label;
// 変更点3findヘルパーを作成
public static Finder<Long, Task> finder = new Finder<Long, Task>(Long.class, Task.class);
....
}
Modelクラスをextendsはなんとなくわかる。あとはアノテーションか。
アノテーション | 説明 |
---|---|
@Entity | このクラスがDBテーブルを表すオブジェクト |
@Id | DBテーブルのprimary key |
@NotNull | NotNull制約 |
@CreatedTimestamp | 作成時の時間 |
@Version | 更新時の時間 |
こんな感じ。finderはデータベースとJavaをつなぐもの。これを介してデータベースを操作する。
CRUD操作のメソッドを実装する。
public static List<Task> all() {
return finder.all();
}
public static void create(Task task) {
task.save();
}
public static void delete(Long id) {
finder.ref(id).delete();
}
これで、Task生成はできるようになった。createしてみよう。
できた、糞UI!
で、実はdeleteの処理がまだ。上で書いたのはModelのdelete処理を作っただけ。Application側にdeleteの処理を書いてないので実装する。
public static Result deleteTask(Long id) {
Task.delete(id); // delete
return redirect(routes.Application.showTasks()); // 再表示
}
わあ簡単。
うーん、最初からScalaでやりゃあよかったかなー。どうもテンプレート的にScala必須っぽいし。とりあえずチュートリアル終わり、後はテキトーになんか書いてみようかなー。