Play Framework入門(2.8.8)　掲示板の作成まで

先日、Java&ScalaのフレームワークであるPlay Framework(以下Play)を使って掲示板サイトを作ったので、それの備忘録を残したいと思います。

この記事ではPlayFrameworkの公式とgithubにある公式サンプルを多数引用します。

https://www.playframework.com/getting-started

https://github.com/playframework/play-samples

```conf:実行環境
PC: Ubuntu20.04
java: 11.0.11
sbt: 1.4.9
Play: 2.8.8
```

# Play! Play Framework
Playとは、MVCでWebサイトを構築するフレームワークです。
MVCとは、Model、View、Controllerの頭文字を取ったものです。

- Model: データの管理、取得などを担当
- View:  データの表示を担当
- Controller: ModelとViewの制御を担当

https://qiita.com/s_emoto/items/975cc38a3e0de462966a

## Javaの導入

Playをインストールするには、まずJavaが必要です。
ubuntuであればこちらの記事を参考にしましょう

https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-on-ubuntu-20-04-ja

> Javaをインストールする最も簡単なオプションは、Ubuntuに含まれているバージョンを使用することです。デフォルトでは、Ubuntu 20.04には、JREとJDKのオープンソースバージョンであるOpen JDK 11が含まれています。

```terminal:Javaのインストールコマンド
sudo apt update
java -version
sudo apt install default-jdk
```

## sbtの導入
sbtとは ~~Java版のnpm,composer,pipのようなものです。~~ Scalaのビルドツールです

> sbt is a build tool for Scala, Java, and more. It requires Java 1.6 or later.

https://www.scala-sbt.org/0.13.0/docs/home.html

こちらの記事を参考に導入しましょう。

https://www.scala-sbt.org/1.x/docs/ja/Installing-sbt-on-Linux.html

Ubuntuであれば、書かれている通りに行えば導入できます。

> Ubuntu 及びその他の Debian ベースのディストリビューションは DEB フォーマットを用いるが、 ローカルの DEB ファイルからソフトウェアをインストールすることは稀だ。 これらのディストロは通常コマンドラインや GUI 上から使えるパッケージ・マネージャがあって (例: apt-get、aptitude、Synaptic など)、インストールはそれらから行う。 ターミナル上から以下を実行すると sbt をインストールできる (superuser 権限を必要とするため、sudo を使っている)。

```terminal:sbtの導入
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add
sudo apt-get update
sudo apt-get install sbt
```

ほかの方は下記の記事を参考に頑張ってください！

https://www.scala-sbt.org/1.x/docs/ja/Installing-sbt-on-Mac.html

https://www.scala-sbt.org/1.x/docs/ja/Installing-sbt-on-Windows.html

## Playの作成

こちらの記事を参考に、Java版のPlayを構築します。

```terminal:Playを作成
sbt new playframework/play-java-seed.g8
```

Playのファイル構造はこのような形になっています。

```
app                      → Application sources
 └ assets                → Compiled asset sources
    └ stylesheets        → Typically LESS CSS sources
    └ javascripts        → Typically CoffeeScript sources
 └ controllers           → Application controllers
 └ models                → Application business layer
 └ views                 → Templates
build.sbt                → Application build script
conf                     → Configurations files and other non-compiled resources (on classpath)
 └ application.conf      → Main configuration file
 └ routes                → Routes definition
dist                     → Arbitrary files to be included in your projects distribution
public                   → Public assets
 └ stylesheets           → CSS files
 └ javascripts           → Javascript files
 └ images                → Image files
project                  → sbt configuration files
 └ build.properties      → Marker for sbt project
 └ plugins.sbt           → sbt plugins including the declaration for Play itself
lib                      → Unmanaged libraries dependencieswot.dannbo@gmail.com

logs                     → Logs folder
 └ application.log       → Default log file
target                   → Generated stuff
 └ resolution-cache      → Info about dependencies
 └ scala-2.13
    └ api                → Generated API docs
    └ classes            → Compiled class files
    └ routes             → Sources generated from routes
    └ twirl              → Sources generated from templates
 └ universal             → Application packaging
 └ web                   → Compiled web assets
test                     → source folder for unit or functional tests
```
引用

https://www.playframework.com/documentation/2.8.x/Anatomy

## Welcom to Play!
さて、実際にPlayを動かしてみましょう。
Playのプロジェクトを開いて`sbt run`します。

```terminal:PLayの実行
cd Playのプロジェクトを作成したディレクトリ
sbt run
```

しばらく待ってlocalhost:9000をブラウザで開くとPlayのページが開きます。
これで導入成功です！

## もろもろのライブラリの導入
今回使うライブラリを導入していきます

- ebean
- evolutions
- guice


ebeanとは、Javaで使えるORマッパーです。

> O/Rマッピングとは、オブジェクト指向プログラミング言語におけるオブジェクトとリレーショナルデータベース（RDB）の間でデータ形式の相互変換を行うこと。そのための機能やソフトウェアを「O/Rマッパー」（O/R mapper）という。

https://e-words.jp/w/O-R%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0.html#:~:text=O%2FR%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%A8%E3%81%AF,O%2FR%20mapper%EF%BC%89%E3%81%A8%E3%81%84%E3%81%86%E3%80%82

evolutionはマイグレーションに使えるライブラリです。
データベースに初回に登録しておきたいデータや、データを移行させたい時にsqlのスクリプトを定義しておき、それが初回起動時に実行されます。

> 移動、移住、移転」を意味する英語の「migration」が語源。IT分野では、ソフトウェアやハードウェア、システム、データ、開発言語などを別のプラットフォームに移行したり、新しいシステムに切り替えたりすることを意味する。似た言葉に「リプレース」があるが、これは古くなったり破損したりしたハードウェアやソフトウェア、システムなどを、新しいものや同等の機能を持ったものに置き換えることを指す。

https://www.otsuka-shokai.co.jp/words/migration.html

https://www.playframework.com/documentation/2.8.x/Evolutions

guiceはテストで使うライブラリです。アプリケーションに依存性を注入する際などに使用します。

https://www.playframework.com/documentation/2.8.x/JavaDependencyInjection

これらを導入するには、sbtのbuildファイルを編集します。

```sbt:build.sbt
name := """sample"""
organization := "com.example"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean)

scalaVersion := "2.13.5"

libraryDependencies ++= Seq(
  guice,
  jdbc,
  evolutions,
  "javax.xml.bind" % "jaxb-api" % "2.3.1",
  "javax.activation" % "activation" % "1.1.1",
  "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.2",
  "com.h2database" % "h2" % "1.4.192",
)
```

```sbt:plugins.sbt
// The Play plugin
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8")

addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "5.0.2")
// Defines scaffolding (found under .g8 folder)
// http://www.foundweekends.org/giter8/scaffolding.html
// sbt "g8Scaffold form"
```
参考にしたコード
build.sbt

https://github.com/playframework/play-samples/blob/2.8.x/play-java-ebean-example/build.sbt

plugins.sbt

https://github.com/playframework/play-samples/blob/2.8.x/play-java-ebean-example/project/plugins.sbt

また、ついでにPlayの設定ファイルも書き換えましょう

```conf:application.conf
# This is the main configuration file for the application.
# https://www.playframework.com/documentation/latest/ConfigFile

# Default database configuration using H2 database engine in an in-memory mode
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play;"

ebean.default="models.*"

play.evolutions.enabled=true
```

参考

https://github.com/playframework/play-samples/blob/2.8.x/play-java-ebean-example/conf/application.conf

こちらではインメモリーデータベースであるh2の設定とebeanの設定とevolutionの設定を行っています。

## Evolutionのスクリプトの作成

```sql:1.sql
# --- First database schema

# --- !Ups

create table user (
    id int auto_increment primary key,
    name varchar(255) not null,
    text varchar(500) not null,
);

insert into user (id,name,text) values (default, '名無し', 'サンプルテキストです。');
insert into user (id,name,text) values (default, '名無し', 'サンプルテキスト２です。');

# --- !Downs

drop table users;
```

今回作ったのは最低限の設定です。
Userテーブルを一つ作り、id、name、textが入っています。
evolutionのファイルスクリプトの置き場は
`conf/evolutions/default/1.sql`になります。フォルダとファイルを作成しましょう。

参考

https://github.com/playframework/play-samples/tree/2.8.x/play-java-ebean-example/conf/evolutions/default

https://www.playframework.com/documentation/2.8.x/Evolutions

これでライブラリが使用出来るようになります。

## Modelの作成
アプリケーションで使うModelを作成しましょう。
appの中にmodelsディレクトリを作成し、その中に作成していきます。
2つのModelを作っていきます
User.javaがデータベースのデータを参照できます。
今回はModelでデータベースに接続するので、User.javaにebeanのFinderを使用してデータベースの値を取得するメソッドを作りました。
また、Playのバリデーションライブラリを使用して、値を必須にしました。

https://qiita.com/hysdsk/items/f38ce0ea503cc2463960

```java:User.java
package models;

import io.ebean.Model;
import io.ebean.Finder;

import play.data.validation.Constraints;

import javax.persistence.Id;
import javax.persistence.Entity;

@Entity
public class User extends Model {
    public static Finder<Long,User> finder = new Finder<>(User.class);

    @Id
    private Long id;
    
    @Constraints.Required
    private String name;

    @Constraints.Required
    private String text;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

```
参考

https://qiita.com/Hyu/items/5f00d4b27608ceb50230

UserForm.javaはFormで送れるデータを定義しています
こちらもバリデーションで値を必須、また値を255に制限しています。
idは、sqlの設定でdefaultの値が入るようになっていますので、今回のケースでは必要無いです。

```java:UserForm.java
package models;

import play.data.validation.Constraints;

public class UserForm {

    @Constraints.Required(message="必須入力です")
    @Constraints.MaxLength(value=255, message="255文字以下にしてください。")
    private String name;

    @Constraints.Required(message="必須入力です")
    @Constraints.MaxLength(value=255, message="255文字以下にしてください。")
    private String text;

    public UserForm() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}
```
参考

https://github.com/playframework/play-samples/blob/2.8.x/play-java-forms-example/app/controllers/WidgetData.java

## Controllerの作成
次にコントローラーを作成します。

行っていることは、FormFactoryでFormを作り、showFormdでViewへ渡しています。
View側でFormがcreateメソッドに送られた時には、Userクラスを用いてデータを定義し、Ebaenでデータベースにデータを保存しています。

```java:HomeController
package controllers;

import models.User;
import models.UserForm;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.data.Form;
import play.data.FormFactory;
import play.i18n.MessagesApi;
import play.mvc.*;

import io.ebean.*;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;

@Singleton
public class FormController extends Controller {
    private final Form<UserForm> form;

    private MessagesApi messagesApi;
    private List<User> users;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    public FormController(FormFactory formFactory, MessagesApi messagesApi) {
        this.users = User.finder.all();
        this.messagesApi = messagesApi;

        this.form = formFactory.form(UserForm.class);
    }

    public Result showForm(Http.Request request) {
        this.users = User.finder.all();
        return ok(views.html.board.render(users, form, request, messagesApi.preferred(request)));
    }

    public Result create(Http.Request request) {
        final Form<UserForm> boundForm = boardCreateForm.bindFromRequest(request);

        if (boundForm.hasErrors()) {
            logger.error("errors = {}", boundForm.errors());
            return badRequest(views.html.board.render(users, boundForm, request, messagesApi.preferred(request)));
        } else {
            UserForm data = boundForm.get();
            User addUser = new User();
            addUser.setName(data.getName());
            addUser.setText(data.getText());
            Ebean.save(addUser);
            return redirect(routes.FormController.showForm()).flashing("info", "書き込みました");
        }
    }
}
```

## Viewの作成

Viewでは、Scalaの記法を使って、Controllerから送られてくるデータを定義できます。
`@`を使うことで、ScalaでifやforなどをHTML内に書くことができます。
formの表示には`@helper`を使います。

https://www.playframework.com/documentation/ja/2.2.x/ScalaTemplates

```html:index.scala.html
@import play.mvc.Http.Request
@(users: List[User], form: Form[UserForm])(implicit request: Request, messages: play.i18n.Messages)

@main("掲示板") {
  <h1>掲示板</h1>

  @request.flash.asScala().data.map { case (name, value) =>
    <div class="@name">@value</div>
  }

  <table>
      @for(u <- users) {
          <tr><td>@u.name</td><td>@u.text</td></tr>
      }
  </table>

  <hr/>

  @if(form.hasGlobalErrors) {
      @form.globalErrors.asScala.map { error: play.data.validation.ValidationError =>
          <div>
              @error.key: @error.message
          </div>
      }
  }

  @helper.form(routes.FormController.create) {
      @helper.CSRF.formField

      @helper.inputText(form("name"))

      @helper.inputText(form("text"))

      <button type="submit">投稿</button>
  }
}
```

これで完成です！

localhost:9000を見てみると、FormとUserの名前とテキストが表示されていると思います。