RailsエンジニアのためのPlay Framework入門

最近はGoやScalaでサービスを開発したり、既存のシステムを置き換えたりする事例が増えてきてますよね。私も仕事ではruby/railsがメインなのですが、新しくアプリケーションを開発するにあたって別の言語・フレームワークを検討する機会があり、少しだけScala(とJava)のフレームワークPlayを触ったので、**Railsでいう◯◯はPlayではどうやるの？**という点をまとめたいと思います。

ちなみに、この記事を書いてる時点で筆者のScala歴・Play歴は10時間くらいですので、「Play Framework入門」と銘打ったものの、「Play Framework紹介」に近いかもしれませんwご了承ください。

### サンプルコード
以下ではこちらのコードから色々抜粋して貼り付けてます。
https://github.com/suzan2go/hello_play
公式の[ScalaToDoList](https://www.playframework.com/documentation/2.2.x/ScalaTodoList)チュートリアル(2.2.Xと少し古いのですが…最新のドキュメントではチュートリアルが見つからず…)を参考に、play 2.4.4で動くように色々試行錯誤したToDoListのアプリです。

こんな感じ。
<img width="946" alt="貼り付けた画像_2015_12_06_14_07.png" src="https://qiita-image-store.s3.amazonaws.com/0/76861/e6bce390-cb8a-ec18-1eb4-b4f53de81c44.png">


# Play Frameworkを始める

## JDK 1.8のインストール
playの2015/12/8時点の最新版`2.4.4`を動かすには、`JDK 1.8`が必要です。Macの場合は以下からDLするか、`brew cask`でインストールできます。
[Java SE - Downloads](http://www.oracle.com/technetwork/java/javase/downloads/index.html)

```
brew cask install java
```

## Playのインストール
PlayをDLしましょう。以下のURLからDLできます。
https://www.playframework.com/download
Macの場合は`brew`でインストールできます。

```
brew install typesafe-activator
```
手順を調べると`brew install play`でインストールとなっているものがありますが、2.3以降は`brew install typesafe-activator`でインストールとなったようですので注意。

## 新しくアプリを作る(= `rails new`)
以下のコマンドで新しくアプリの開発を始められます。

```
activator new my-first-app play-scala
```
以下の様にファイルが作成されます。デフォルトでは`model`というディレクトリは作成されないので、自分で作っていく感じになります。というかこの辺りのフォルダの命名規約はあまり無いようで、playで作られたアプリをgithubで眺めていると全然こういう名前の配置になってなかったりします。(ちなみに`play-scala`の部分を`play-java`に変えると、`java`用の`テンプレート`が作成されます。)

```
├── LICENSE
├── README
├── activator
├── activator-launch-1.3.6.jar
├── app
│   ├── controllers
│   └── views
├── bin
├── build.sbt
├── conf
│   ├── application.conf
│   ├── evolutions
│   ├── logback.xml
│   └── routes
├── logs
│   └── application.log
├── my-first-scala.iml
├── project
│   ├── build.properties
│   ├── play-fork-run.sbt
│   ├── plugins.sbt
│   ├── project
│   ├── sbt-ui.sbt
│   └── target
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
├── target
│   ├── native_libraries
│   ├── resolution-cache
│   ├── scala-2.11
│   ├── streams
│   └── web
└── test
    ├── ApplicationSpec.scala
    └── IntegrationSpec.scala
```


またコマンドラインから、`activator ui`とすると、Webベースの開発環境を立ち上げることができます。ちょっと試しに使うくらいであれば、IDEを準備する時間も勿体無いなので、こちらを使っても良いかもしれません。
<img width="1280" alt="貼り付けた画像_2015_12_06_18_15.png" src="https://qiita-image-store.s3.amazonaws.com/0/76861/e86a0561-67f5-1fce-6885-468479a22310.png">

## 接続するデータベースの情報を書く(`=config/database.yml`)
`conf/application.conf` に書きます。

```
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
db.default.username=sa
db.default.password=""
```

## データベースのmigrationを書く(= `db/migrate/...`)
playにはevolutionという仕組みがあり、`conf/evolutions/default/..`というディレクトリの下に、`1.sql, 2.sql`という形でmigrationファイルを書いていく形になります。

```
# 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;

```

この状態でアプリケーションにアクセスすると以下のような画面になります。ここで`apply this script now`ボタンをクリックすると、データベースにmigrationが反映されます。

![Kobito.9vzAPy.png](https://qiita-image-store.s3.amazonaws.com/0/76861/be7d6789-be6c-cb41-1a6e-b4dee80b49c6.png "Kobito.9vzAPy.png")

## 外部ライブラリを導入する(= Gemfile )
sbtというツールを使って管理するようです。アプリケーションrootに配置された`build.sbt`には`slick`などアプリケーションで実際に使用するライブラリを、`project/plugins.sbt`には`coffee script`や`less`のコンパイルを行うためのものを記述するっぽい。

```
├── build.sbt
├── project
│   ├── build.properties
│   ├── play-fork-run.sbt
│   ├── plugins.sbt
│   ├── project
│   ├── sbt-ui.sbt
│   └── target
```

サンプルコードでは以下のように、`bootstrap`を`build.sbt`に書いています。

```
  "org.webjars" %% "webjars-play" % "2.4.0-1",
  "org.webjars" % "bootstrap" % "3.1.1-2"
```

これはwebjarといって、CSSやJSのライブラリを`JAR`ファイルにまとめたもので、こちらのサイトに公開されています([webjar](http://www.webjars.org/))。[rails-assets](https://rails-assets.org/)みたいなもんですかね。昨今のフロントエンド事情からするに、フロントエンドはこちらを使わずにnpm等で管理するのが良い気もしますがどうなんでしょうね。

webjarを使う場合にはルーティングに以下を追加します。
```
GET     /webjars/*file              controllers.WebJarAssets.at(file)
```


## ルーティングを書く(= `config/routes.rb`)
`conf`ファイルの下に`routes`というファイルがありますね。ここにルーティングを書いていきます。

```
├── conf
│   ├── application.conf
│   ├── evolutions
│   ├── logback.xml
│   └── routes
```

実際にどう書いていくかというと、以下の様な感じになります。
railsでいう、`get 'products/:id' => 'catalog#view`のように、ルーティングに対して対応するコントローラとアクションを指定していきます。

```
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index

GET     /tasks                      controllers.Application.tasks
POST    /tasks                      controllers.Application.newTask
POST    /tasks/:id/delete           controllers.Application.deleteTask(id: Long)

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)
GET     /webjars/*file              controllers.WebJarAssets.at(file)

```

上記のサンプルではApplicationコントローラに全て書いてしまっていますが、実際の開発では例えばTaskのコントローラは分けて書くらしいです。

## controllerを書く
`controllers` 配下の`Applicaton.scala`に、先ほど書いた`routesに`対応するアクションを書いていきます。

```
├── app
│   ├── controllers
│   │   └── Application.scala
```

見れば大体想像付くと思いますが、以下の様な処理になってます。

* indexでは、`tasks`にリダイレクト
* tasksでは、`index.html.scala`のテンプレートに対して、全てのタスクと、taskFormを渡しています。

※ taskFormについては後述

```

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

  val taskForm = Form(mapping(
    "id" -> ignored(0: Long),
    "label" -> nonEmptyText,
    "body" -> nonEmptyText)(Task.apply)(Task.unapply))

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

```

## modelを書く
デフォルトでは`app`配下に`models`というディレクトリはないので、自分でつくります。

```
├── app
│   ├── controllers
│   │   └── Application.scala
│   ├── models
│   │   └── Task.scala
```

以下では普通にコードの中でSQL書いてますが、これは`anorm`というライブラリをつかっているためです(play 2.3まではこちらがデフォルト)。play2.4からは`slick`というORMがデフォルトらしく、SQLを直書きしなくても大丈夫なようです。(本当はslickで書き直したかったのですが、力尽きました)

```
package models

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

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

object Task {

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

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

```	

## Viewを書く
Viewファイルは以下のようになっています。

```
├── app
│   └── views
│       ├── index.scala.html
│       └── main.scala.html
```

`main.scala.html`の中身を見ると分かりますが、こちらはrailsでいう`layout/application.html.erb`に、中身の`@content`が`<%= yield %>`に対応してます。

```
@(title: String)(content: Html)

<!DOCTYPE html>

<html lang="en">
  <head>
    <title>@title</title>
    <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
    <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
    <link rel='stylesheet' href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))'>
    <script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script>
  </head>
  <body>
    <div class="container">
      @content
    </div>
  </body>
</html>
```

次に`index.scala.html`の中身を見てみます。htmlとscalaのコードを混ぜて書いても大丈夫なのが凄いですね。
最初の行で、`controller`から受け取る引数を定義しています。

`@inputText(taskForm("label"))`となっていますが、ここでcontrollerで定義した`taskForm `が関係してきます。taskFormのところで、`"label" -> nonEmptyText`のように定義していますが、これによりこのフォームでは空文字が登録できないようvalidationがかかります。

```
@(tasks: List[Task], taskForm: Form[Task])(implicit messages: Messages)
@import helper._

@main("ToDo List") {
  <h1>@tasks.size tasks(s)</h1>
  
  <ul>
    @tasks.map { task => 
      <li>
        @task.label
        @task.body
        @form(routes.Application.deleteTask(task.id)){
          <input type="submit" value="delete" class="btn btn-danger">
        }
      </li>
    }
  </ul>
  
  <h2>Add a new task</h2>
  
  @form(routes.Application.newTask) {
    @inputText(taskForm("label"))
    @inputText(taskForm("body"))
    <input type="submit" value="Create" class="btn btn-primary">
  }
  
}
```

# まとめ(感想)
テストまではカバーできませんでしたが、何となくPlayの雰囲気だけでも伝わっていれば幸いです。
冒頭でも書きましたが、Play、Scalaは勉強を始めたばかりなので変なところ・間違っているところがあればご指摘頂けると嬉しいです！