LoginSignup
158
157

More than 3 years have passed since last update.

Play Frameworkハンズオン

Last updated at Posted at 2016-05-27

環境構築 が完了していることを前提とします。

今日作成するアプリ

画面表示

11-0-1.png

登録後

11-0-2.png

1.ScalaとPlay Frameworkの簡単な説明

Scalaについて

  • オブジェクト指向言語と関数型言語の特徴を統合した言語
  • Javaプラットフォーム(Java仮想マシン)上で動作する
  • 既存のJavaのプログラムと容易に連携できるため、Javaの豊富なライブラリが使える

Scalaの採用事例

主な採用事例

  • Twitter
  • 株式会社ドワンゴ
  • 株式会社はてな(mackerel)
  • SmartNews
  • LINE株式会社
  • ヌーラボ(Typetalk, Backlog)

Scalaの採用事例一覧

Play Frameworkについて

  • ScalaとJava言語で書かれたオープンソースのWebアプリケーションフレームワーク
  • Ruby on RailsやDjangoと似た同種のフレームワーク

2.ディレクトリ構成についての説明

.
├── app アプリケーションのソースコード
│   ├── controllers アプリケーションのコントローラ
│   └── views テンプレート
├── build.gradle
├── build.sbt アプリケーションのビルドスクリプト
├── conf アプリケーションの設定ファイル
│   ├── application.conf メイン設定ファイル
│   ├── logback.xml ログ出力設定ファイル
│   ├── messages 国際化対応用言語ファイル
│   └── routes ルート定義
├── gradle
├── gradlew
├── gradlew.bat
├── project sbt 設定ファイル群
│   ├── build.properties sbt プロジェクトの目印
│   ├── plugins.sbt Play 自身の定義を含む sbt プラグイン
│   ├── project
│   ├── scaffold.sbt scaffolding 用の sbt プラグイン使用時に使用
│   └── target
├── public 公開アセット
├── target ビルド成果物
└── test 単体、および機能テスト用のソースフォルダ

3.sbtシェル

1)sbtコマンド

作成したプロジェクト直下でsbtコマンドを実行するとsbtシェルに入り、sbtなしで様々なコマンドを実行することができます。

$ sbt

2)実行コマンド

> run

scalaシェルに入っていない場合は次のコマンドになります

$ sbt run

3) ビルド定義の再読み込み

> reload

ビルド定義(build.sbt、 project/.scala、 project/.sbt ファイル)を再読み込みする。 ビルド定義を変更した場合に必要。

4.開発環境チェック

1)Java

次のコマンドを実行します。

$ java -version

次のように表示されたら成功です。バージョンは1.8以上であれば大丈夫です。
(2019年1月9日時点の最新バージョン:1.8.0_192)

openjdk version "1.8.0_192"
OpenJDK Runtime Environment (build 1.8.0_192-amazon-corretto-preview2-b12)
OpenJDK 64-Bit Server VM (build 25.192-b12, mixed mode)

2)sbt

次のコマンドを実行します。

$ sbt sbtVersion

次のように表示されれば大丈夫です。(バージョンは1.0.0以上である必要があります。)

[info] Loading settings for project play-hands-on-build from plugins.sbt,scaffold.sbt ...
[info] Loading project definition from /Users/yuichi/Desktop/play-hands-on/project
[info] Loading settings for project root from build.sbt ...
[info] Set current project to play-hands-on (in build file:/Users/yuichi/Desktop/play-hands-on/)
[info] 1.2.8
[INFO] [01/09/2019 14:15:34.369] [Thread-3] [CoordinatedShutdown(akka://sbt-web)] Starting coordinated shutdown from JVM shutdown hook

3)プロジェクト

IntelliJを起動し次のように画面が表示されれば大丈夫です。

prepare.png

5.作成前の準備

1)サンプルファイルの削除

この時点でサンプルのファイルが入っているため削除します。
プロジェクトのルートディレクトリ配下のappディレクトリの下にあるファイルを全て削除します。
次のように選択し

5-1.png

右クリックし[Delete]を選択します。(safe deleteのチェックは外します。)

5-2.png

5-3.png

2)routesの不要ルートの削除

5-4.png

play-hands-on/conf/routesファイルで[GET /assets/*file ...]の行を残して他の行を削除します。
次のようになります。

GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

6.Hello World

目的:コントローラーの書き方、routesの書き方
6-0-1.png
次のような画面を作成します。
6-2-1.png

1)routesにhelloworld用のルートを追加

/play-hands-on/conf/routes

この一行を書いてみましょう

GET     /todo/helloworld                       controllers.TodoController.helloworld()

2)TodoControllerの作成

controllersパッケージにTodoControllerクラスを作成します。
IntelliJ上でcontrollersディレクトリを右クリックし、[New]の[Scala Class]をクリックしします。

6-1.png

表示されたダイアログに[TodoController]と入力しOKをクリックします。
6-2.png

/play-hands-on/app/controllers/TodoController.scala

importの部分はコピーしてそれ以外は書いてください

package controllers

//コピペ
import javax.inject._
import play.api.mvc._

import play.api.data._
import play.api.data.Forms._
//コピペ

//入力
class TodoController @Inject()(mcc: MessagesControllerComponents) 
extends MessagesAbstractController(mcc) {

  def helloworld() = Action { implicit request: MessagesRequest[AnyContent] =>
    Ok("Hello World")
  }

}
//入力
メソッド ステータスコード
Ok 200
BadRequest 400
Forbidden 403
NotFound 404
Redirect リダイレクト処理

3)サーバー起動

プロジェクト直下で次のコマンドを実行しsbtシェルに入ります。

$ sbt

runコマンドでサーバーを起動します。

> run

次のように表示されます。

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Enter to stop and go back to the console...)

4)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

Hello World画面


次の画面が表示されたら成功です!
6-2-1.png

7.リスト画面の作成(テンプレート)

目的:テンプレートの使い方、Scalaの変数の書き方
7-a-1.png

次のような画面を作成します。
7-0.png

1)routesにlist用のルートを追加

/play-hands-on/conf/routes

この一行を書いてみましょう

GET     /todo                       controllers.TodoController.list()

2)TodoControllerにlistメソッドを追加

次のlistメソッドを追加します。
/play-hands-on/app/controllers/TodoController.scala

こちらのメソッドを実装してください

  def list() = Action { implicit request: MessagesRequest[AnyContent] =>
    val message: String = "ここにリストを表示"
    Ok(views.html.list(message))
  }

varはmutableな変数を定義する時に使用します。再代入可であることを示します。
valはimmutableな変数を定義する時に使用します。再代入不可であることを示します。

3)listテンプレートの作成

/play-hands-on/app/viewsの下に[list]テンプレートを作成します。
IntelliJ上でviewsディレクトリを右クリックし、[New]の[File]をクリックします。

7-1.png

表示されたダイアログに[list.scala.html]と入力しOKをクリックします。
7-2.png

こちらのHTMLをコピーして貼り付けてください

ここを実装してください
<html>
    <head>
        <title>Todo</title>
    </head>
    <body>

        <section>
        ここを実装してください
        </section>

    </body>
</html>

/play-hands-on/app/views/list.scala.html

[ここを実装してください]と書いている箇所に次の内容と同じものを実装してください

@(message: String)

<html>
    <head>
        <title>Todo</title>
    </head>
    <body>

        <section>
        @message
        </section>

    </body>
</html>

4)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

リスト画面


次の画面が表示されたら成功です!
7-0.png

8.リスト画面の作成(case class)

目的:case class、Seq()、viewでのループ
8-0.png

1)サービスの作成

servicesパッケージを作成する

appディレクトリを右クリックし、[New]の[Package]をクリックします。
8-1-1.png

表示されたダイアログに[services]と入力しOKをクリックします。
8-2.png

Todoクラスを作成する

servicesパッケージの下にTodoクラスを作成します。
IntelliJ上でservicesディレクトリを右クリックし、[New]の[Scala Class]をクリックします。
8-3-1.png

表示されたダイアログに[Todo]と入力しOKをクリックします。
8-4.png

/play-hands-on/app/services/Todo.scala

こちらの内容を実装してください

package services

case class Todo(name: String)

case classのいいところ
- プロパティが公開される(todo.nameなど)
- インスタンスを作る時にnewが必要なくなる(val todo = Todo())
- equals,hashCode,toStringなどが実装される

2)TodoControllerの編集

/play-hands-on/app/controllers/TodoController.scala

servicesのパッケージを追加します

import services._

listメソッドを編集します

  def list() = Action { implicit request: MessagesRequest[AnyContent] =>
    val items: Seq[Todo] = Seq(Todo("Todo1"), Todo("Todo2"))
    Ok(views.html.list(items))
  }

3)listテンプレートの編集

/play-hands-on/app/views/list.scala.html

こちらのHTMLをコピーして上書きして貼り付けてください

ここを実装してください
<html>
    <head>
        <title>Todo</title>
    </head>
    <body>

        <section>
            <table>
                <thead>
                    <tr>
                        <th>名前</th>
                    </tr>
                </thead>
                <tbody>
                ここを実装してください
                </tbody>
            </table>
            <a href="/todo/new">登録画面</a>
        </section>

    </body>
</html>

/play-hands-on/app/views/list.scala.html

[ここを実装してください]と書いている箇所に次の内容と同じものを実装してください

@import services._
@(items: Seq[Todo])

<html>
    <head>
        <title>Todo</title>
    </head>
    <body>

        <section>
            <table>
                <thead>
                    <tr>
                        <th>名前</th>
                    </tr>
                </thead>
                <tbody>
                @items.map { todo =>
                    <tr>
                        <td>@todo.name</td>
                    </tr>
                }
                </tbody>
            </table>
            <a href="/todo/new">登録画面</a>
        </section>

    </body>
</html>

リバースルーティングを使用したリンクの書き方 href="@controllers.routes.TodoController.todoNew()"

4)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

リスト画面


次の画面が表示されたら成功です!
8-0.png

9.データベースの設定

目的:evolutions、anormの使い方

1)evolutionsについて

今回データベースを使用するにあたりDBマイグレーションツールを使用します。DBマイグレーションツールとは、スキーマのバージョン管理のようなもので、スキーマの変更に対してデータベースを追随させることができます。evolutionsはPlayframeworkで使われているDBマイグレーションツールです。

2)Anormについて

データベースアクセスにはAnormというライブラリを使用します。SQLを直接記述できSQL実行/結果解析をサポートします。

3)build.sbtの設定

プロジェクトルートディレクトリ配下のbuild.sbtを編集します。
9-1.png

次の4行を追加してください。
/play-hands-on/build.sbt

コピーして貼り付けてください

libraryDependencies += jdbc
libraryDependencies += evolutions
libraryDependencies += "org.playframework.anorm" %% "anorm" % "2.6.4"
libraryDependencies += "com.h2database" % "h2" % "1.4.196"

「Import Changes」をクリックするとライブラリの情報をIntelliJに読み込んで、ライブラリ内のクラスを自由に参照できるようになります。
pasted-2019.01.11-15.15.06.png

4)evolutionsの設定ファイルの作成

/play-hands-on/confディレクトリの下に[evolutions/default]ディレクトリを作成します。

9-2.png

IntelliJ上で[conf]ディレクトリを右クリックし、[New]の[Directory]をクリックします。
9-3.png

表示されたダイアログに[evolutions/default]と入力しOKをクリックします。
9-4.png

5)マイグレーションファイルの作成

作成したディレクトリにマイグレーションファイルを作成します。
IntelliJ上で[evolutions.default]ディレクトリを右クリックし、[New]の[File]をクリックします。
9-5.png

表示されたダイアログに[1.sql]と入力しOKをクリックします。
9-6.png

/play-hands-on/conf/evolutions/default/1.sql

コピーして貼り付けてください

# --- First database schema

# --- !Ups
create table todo (
  id                        bigint not null auto_increment,
  name                      varchar(255) not null,
  constraint pk_todo primary key (id))
;
create sequence todo_seq start with 1000;

insert into todo (id,name) values (1,'書類の整理');
insert into todo (id,name) values (2,'本の返却');

# --- !Downs
drop table if exists todo;

drop sequence if exists todo_seq;

6)データベースの設定

confディレクトリの下のapplication.confに次の2行を追加します。
9-7.png

/play-hands-on/conf/application.conf

コピーして貼り付けてください

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

H2 Databaseと言うJavaプラットフォーム上で動く、インメモリデータベースを使用するように設定されます。

7)データベースを作成しましょう

一旦、Ctrl + Dでサーバーを停止したのち、ビルド定義を再読み込みしサーバーを起動します。

> reload
> run

次のリンクをクリックします。

リスト表示画面


データベース作成画面が表示されます。
[Apply this script now!]ボタンをクリックしデータベースを作成しましょう。
9-0.png

10.リスト画面の作成(データベース)

目的:Scalaでのデータベースの使い方
10-a-2.png


10-0.png

1)モデルの編集

/play-hands-on/app/services/Todo.scala

全部コピーして上書きしてください

package services

import javax.inject.Inject

import anorm.SqlParser._
import anorm._
import play.api.db.DBApi

import scala.language.postfixOps

case class Todo(name: String)

@javax.inject.Singleton
class TodoService @Inject() (dbapi: DBApi) {

  private val db = dbapi.database("default")

  val simple = {
    get[String]("todo.name") map {
      case name => Todo(name)
    }
  }

  def list(): Seq[Todo] = {

    db.withConnection { implicit connection =>

      SQL(
        """
          select * from todo
        """
      ).as(simple *)

    }

  }

}

2)TodoControllerでTodoServiceを使えるように設定する

/play-hands-on/app/controllers/TodoController.scala

todoService: TodoService,を追加してください

class TodoController @Inject()(todoService: TodoService, mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc) {

3)TodoControllerのlistメソッドの編集

/play-hands-on/app/controllers/TodoController.scala

こちらの内容で編集してください

  def list() = Action { implicit request: MessagesRequest[AnyContent] =>
    val items: Seq[Todo] = todoService.list()
    Ok(views.html.list(items))
  }

4)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

リスト画面


次の画面が表示されたら成功です!
10-0.png

11.登録画面の作成(テンプレート)

目的:Playのフォームテンプレートヘルパーの使い方
11-a-1.png

画面表示
10-0-1.png

登録後
10-0-2.png

1)routesに登録画面表示と登録用のルートを追加

/play-hands-on/conf/routes

次の2行を追加してください

GET     /todo/new                   controllers.TodoController.todoNew()
POST    /todo                       controllers.TodoController.todoAdd()

2)TodoControllerにメソッドを追加

/play-hands-on/app/controllers/TodoController.scala

次の2つのメソッドを実装してください

  val todoForm: Form[String] = Form("name" -> nonEmptyText)

  def todoNew = Action { implicit request: MessagesRequest[AnyContent] =>
    Ok(views.html.createForm(todoForm))
  }

  def todoAdd() = Action { implicit request: MessagesRequest[AnyContent] =>
    val name: String = todoForm.bindFromRequest().get
    println(name)
    Ok("Save")
  }

3)createFormテンプレートの作成

/play-hands-on/app/viewsの下にcreateFormテンプレートを作成します。
IntelliJ上で[views]パッケージを右クリックし、[New]の[File]をクリックします。
11-1.png

表示されたダイアログに[createForm.scala.html]と入力しOKをクリックします。
11-2.png

/play-hands-on/app/views/createForm.scala.html

こちらのHTMLをコピーして貼り付けてください

ここを実装してください
<html>
    <head>
        <title>Todo</title>
    </head>
    <body>

        <h1>Todo登録</h1>

        ここを実装してください

    </body>
</html>

/play-hands-on/app/views/createForm.scala.html

[ここを実装してください]と書いている箇所に次の内容と同じものを実装してください

@(todoForm: Form[String])(implicit request: MessagesRequestHeader)

<html>
<head>
    <title>Todo</title>
</head>
<body>

<h1>Todo登録</h1>

@helper.form(action = routes.TodoController.todoAdd()) {
@helper.CSRF.formField

<fieldset>

    @helper.inputText(todoForm("name"), '_label -> "名前")

</fieldset>

<input type="submit" value="登録">

}

</body>
</html>

4)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

登録画面


次の画面が表示されたら成功です!
画面表示
10-0-1.png

登録後
10-0-2.png

12.登録画面の作成(データベース)

目的:データベースの利用(登録編)
12-b-1.png

画面表示
11-0-1.png

登録後
11-0-2.png

1)TodoServiceにメソッドを追加します。

/play-hands-on/app/services/Todo.scala
TodoServiceの中

コピーして追加してください

  def insert(todo: Todo) = {
    db.withConnection { implicit connection =>
      SQL(
        """
        insert into todo values ((select next value for todo_seq), {name})
        """
      ).on(
        'name -> todo.name
      ).executeUpdate()
    }
  }

2)TodoControllerのtodoAddメソッドを次のように編集します。

/play-hands-on/app/controllers/TodoController.scala

次の内容で編集してください

  def todoAdd() = Action { implicit request: MessagesRequest[AnyContent] =>
    val name: String = todoForm.bindFromRequest().get
    todoService.insert(Todo(name))
    Redirect(routes.TodoController.list())
  }

3)画面をブラウザで見てみましょう

こちらのリンクをクリックしてください

登録画面


次の画面が表示されたら成功です!
画面表示
11-0-1.png
登録後
11-0-2.png

13.更新処理の実装

1)routesにupdate用のルートを追加

/play-hands-on/conf/routes

次の2行を追加してください

GET     /todo/edit/:todoId                  controllers.TodoController.todoEdit(todoId:Long)
POST   /todo/:todoId                       controllers.TodoController.todoUpdate(todoId:Long)

2)TodoControllerにメソッドを追加

/play-hands-on/app/controllers/TodoController.scala

次の2つのメソッドを実装してください

  def todoEdit(todoId: Long) = Action { implicit request: MessagesRequest[AnyContent] =>
    todoService.findById(todoId).map { todo =>
      Ok(views.html.editForm(todoId, todoForm.fill(todo.name)))
    }.getOrElse(NotFound)
  }

  def todoUpdate(todoId: Long) = Action { implicit request: MessagesRequest[AnyContent] =>
    val name: String = todoForm.bindFromRequest().get
    todoService.update(todoId, Todo(Some(todoId), name))
    Redirect(routes.TodoController.list())
  }

todoAddメソッドを次のように編集します。Todoにidが増えたのでNoneを渡しています。

  def todoAdd() = Action { implicit request: MessagesRequest[AnyContent] =>
    val name: String = todoForm.bindFromRequest().get
    todoService.insert(Todo(id = None, name))
    Redirect(routes.TodoController.list())
  }

3)TodoServiceにメソッドを追加し、Todoにidを追加します。

/play-hands-on/app/services/Todo.scala

package services

import javax.inject.Inject

import anorm.SqlParser._
import anorm._
import play.api.db.DBApi

import scala.language.postfixOps

case class Todo(id:Option[Long], name: String)

@javax.inject.Singleton
class TodoService @Inject() (dbapi: DBApi) {

  private val db = dbapi.database("default")

  val simple = {
    get[Option[Long]]("todo.id") ~
    get[String]("todo.name") map {
      case id~name => Todo(id, name)
    }
  }

  def list(): Seq[Todo] = {

    db.withConnection { implicit connection =>

      SQL(
        """
          select * from todo
        """
      ).as(simple *)

    }

  }

  def insert(todo: Todo) = {
    db.withConnection { implicit connection =>
      SQL(
        """
        insert into todo values ((select next value for todo_seq), {name})
        """
      ).on(
        'name -> todo.name
      ).executeUpdate()
    }
  }

  def findById(id: Long): Option[Todo] = {
    db.withConnection { implicit connection =>
      SQL("select * from todo where id = {id}").on('id -> id).as(simple.singleOpt)
    }
  }

  def update(id: Long, todo: Todo) = {
    db.withConnection { implicit connection =>
      SQL(
        """
          update todo
          set name = {name}
          where id = {id}
        """
      ).on(
        'id -> id,
        'name -> todo.name
      ).executeUpdate()
    }
  }

}

4)editFormテンプレートの作成

/play-hands-on/app/views/editForm.scala.html

@(id: Long, todoForm: Form[String])(implicit request: MessagesRequestHeader)

<html>
<head>
    <title>Todo</title>
</head>
<body>

<h1>Todo更新</h1>

@helper.form(action = routes.TodoController.todoUpdate(id)) {
@helper.CSRF.formField

<fieldset>

    @helper.inputText(todoForm("name"), '_label -> "名前")

</fieldset>

<input type="submit" value="更新">

}

</body>
</html>

5)list.scala.htmlのリストをリンクにします

@import services._
@(items: Seq[Todo])

<html>
<head>
    <title>Todo</title>
</head>
<body>

<section>
    <table>
        <thead>
        <tr>
            <th>名前</th>
        </tr>
        </thead>
        <tbody>
        @items.map { todo =>
        <tr>
            <td><a href="@controllers.routes.TodoController.todoEdit(todo.id.get)">@todo.name</a></td>
        </tr>
        }
        </tbody>
    </table>
    <a href="@controllers.routes.TodoController.todoNew()">登録画面</a>
</section>

</body>
</html>

14.削除処理の実装

1)routesにdelete用のルートを追加

/play-hands-on/conf/routes

次の行を追加してください

POST   /todo/:todoId/delete                controllers.TodoController.todoDelete(todoId:Long)

2)TodoServiceに削除メソッドを追加します

/play-hands-on/app/services/Todo.scala

  def delete(id: Long) = {
    db.withConnection { implicit connection =>
      SQL("delete from todo where id = {id}").on('id -> id).executeUpdate()
    }
  }

3)TodoControllerにメソッドを追加

/play-hands-on/app/controllers/TodoController.scala

次のメソッドを実装してください

  def todoDelete(todoId: Long) = Action { implicit request: MessagesRequest[AnyContent] =>
    todoService.delete(todoId)
    Redirect(routes.TodoController.list())
  }

4)editFormテンプレートの編集

削除ボタンを追加します。
/play-hands-on/app/views/editForm.scala.html

@helper.form(action = routes.TodoController.todoDelete(id)) {
@helper.CSRF.formField
<input type="submit" value="削除" class="btn danger">
}
158
157
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
158
157