scala webアプリ 実践3-チュートリアルを試す

今回は、チュートリアル(localhost:9000からリンク先があります)に従って簡単なアプリケーションを作ってみたいと思います。

TODOリストを作る

プロジェクトを作る
bash-4.2$ play new todolist
起動する
bash-4.2$ cd todolist/
bash-4.2$ play

ideを設定する(前回と同じ設定をintelljにする)

[todolist] $ idea
[todolist] $ run
Application.scalaを編集する

todolist/app/controllers/Application.scalaを編集し、
ブラウザから「http://localhost:9000/」を叩き、helloworldが表示されることを確認する

def index = Action {
  Ok("Hello world")
}
routesをつくりましょ

conf/routesを編集し、routeを追加し、Application.scalaに対応したメソッドをtodoで作成する
ブラウザから「http://localhost:9000/tasks」を叩くと、TODOが表示される。
これは便利!!

# Home page
GET     /                       controllers.Application.index
                                
# Tasks          
GET     /tasks                  controllers.Application.tasks
POST    /tasks                  controllers.Application.newTask
POST    /tasks/:id/delete       controllers.Application.deleteTask(id: Long)
object Application extends Controller {
  
  def index = Action {
    Ok("helloworld")
  }

  def tasks = TODO

  def newTask = TODO

  def deleteTask(id: Long) = TODO
  
}

最後に、/tasks にリダイレクトするように変更します
※helloworldだったところです

  def index = Action {
    Redirect(routes.Application.tasks)
  }
タスクのモデルを準備する

app/models/Task.scalaを新規作成する

package models

case class Task(id: Long, label: String)

object Task {
  
  def all(): List[Task] = Nil
  
  def create(label: String) {}
  
  def delete(id: Long) {}
  
}

※case classとはなにか?

Case classes are regular classes which export their constructor parameters and which provide a recursive decomposition mechanism via pattern matching

つたない感じで訳すと、『コンストラクタのパラメータを出力し、パターンマッチングを経由し、再帰的な分解機能を提供するクラス』だそうです。。ようわかりませんが、いったんふーんと言っておきましょう。

テンプレートを作りましょ

index.scala.htmlを編集します
ここでやっているのは、

  • タスクリストの表示
  • タスクとのフォーム
@(tasks: List[Task], taskForm: Form[String])

@import helper._

@main("Todo list") {
    
    <h1>@tasks.size task(s)</h1>
    
    <ul>
        @tasks.map { task =>
            <li>
                @task.label
                
                @form(routes.Application.deleteTask(task.id)) {
                    <input type="submit" value="Delete">
                }
            </li>
        }
    </ul>
    
    <h2>Add a new task</h2>
    
    @form(routes.Application.newTask) {
        
        @inputText(taskForm("label")) 
        
        <input type="submit" value="Create">
        
    }
    
}

※「helper._」はformの助成をしてくれる

formに、バリデーションとタスクのアクションを指定する

validation部分

  val taskForm = Form(
    "label" -> nonEmptyText
  )

action追加分

  def tasks = Action {
    Ok(views.html.index(Task.all(), taskForm))
  }

Application.scala 全部

package controllers

import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import models.Task

object Application extends Controller {

  val taskForm = Form(
    "label" -> nonEmptyText
  )

  def index = Action {
    Redirect(routes.Application.tasks)
  }

  def tasks = Action {
    Ok(views.html.index(Task.all(), taskForm))
  }

  def newTask = TODO

  def deleteTask(id: Long) = TODO

}
サブミットはハンドリングする

Application.scala のnewTaskのTODO部分の変更

def newTask = Action { implicit request =>
  taskForm.bindFromRequest.fold(
    errors => BadRequest(views.html.index(Task.all(), errors)),
    label => {
      Task.create(label)
      Redirect(routes.Application.tasks)
    }
  )
}

これで、入力エラー時に、「400 Bad Request」を返却し、入力エラーがない場合は200を返すようになります。

データベースを設定する

conf/application.conf を変更します。コメントアウトされているのを
外すだけでいけます。

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

※H2とは?
javaで書かれた、軽量データベースエンジンであり、最速との呼び声もあるそうです

定義ファイルを作成する

conf/evolutions/default/1.sql

# Tasks schema
 
# --- !Ups

CREATE SEQUENCE task_id_seq;
CREATE TABLE task (
    id integer NOT NULL DEFAULT nextval('task_id_seq'),
    label varchar(255)
);
 
# --- !Downs
 
DROP TABLE task;
DROP SEQUENCE task_id_seq;

次に、ブラウザから「http://localhost:9000/tasks」にアクセスすると、
「An SQL script will be run on your database - Apply this script now!」
と表示されていて、「Apply this script now!」部分がボタンになっているので、
これをクリックする。
これにより、databaseは有効化され使用可能になる。

定義を読み込みparseし、処理を組み込む

Task.scalaの全文です。

package models

import anorm._
import anorm.SqlParser._
import play.api.db._
import play.api.Play.current

case class Task(id: Long, label: String)

object Task {

  val task = {
    get[Long]("id") ~
      get[String]("label") map {
      case id~label => Task(id, label)
    }
  }

  def all(): List[Task] = DB.withConnection { implicit c =>
    SQL("select * from task").as(task *)
  }

  def create(label: String) {
    DB.withConnection { implicit c =>
      SQL("insert into task (label) values ({label})").on(
        'label -> label
      ).executeUpdate()
    }
  }

  def delete(id: Long) {
    DB.withConnection { implicit c =>
      SQL("delete from task where id = {id}").on(
        'id -> id
      ).executeUpdate()
    }
  }

}
未実装部分の削除部分のルーティングを編集

Application.scala

  def deleteTask(id: Long) = Action {
    Task.delete(id)
    Redirect(routes.Application.tasks)
  }

完成!!

雑感

チュートリアルが優秀なせいか、いつもむだなところで躓くが、
すんなり、db連携までもできてしまった。すげー