Spring Boot for Kotlin 2

# 開発準備
Spring Inirializrで以下のプロジェクトを作る。

[Spring Initializr](https://start.spring.io/)

* ビルドツール: Gradle
* 言語: Kotlin
* Spring Bootのバージョン: 1.5.7
* Group: com.example
* Artifact: todolist
* Dependencies: Web, Thymeleaf

# タスクの一覧表示
## タスクのデータクラスを作成

```kotlin:Task.kt
package com.example.todolist

data class Task(
        val id: Long,
        val content: String,
        val done: Boolean
)

```

## コントローラーの作成
ダミータスクをプレーンテキストを返すコントローラー

```kotlin:TaskController.kt
package com.example.todolist

import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("tasks")
class TaskController {
    @GetMapping
    fun index(model: Model): String {
        val tasks = listOf(
                Task(1, "Test Task1", false),
                Task(2, "Test Task2", true),
                Task(3, "Test Task3", false)
        )
        return tasks.toString()
    }
}

```

@RequestMapping("tasks")の指定により、このクラスに定義するメソッドへのパスは"/tasks"から始まることを設定している。
@GetMapping("")の指定があるため、/tasksというパスへのGETリクエストに反応する。

## 動作確認
以下のコマンドでアプリケーションを起動。

```bash
./gradlew bootRun
```

以下のURLにブラウザにアクセス。

```
http://localhost:8080/tasks
```

ブラウザに以下の文字列が表示される。

```
[Task(id=1, content=Test Task1, done=false), Task(id=2, content=Test Task2, done=true), Task(id=3, content=Test Task3, done=false)]
```


# タスク一覧をHTMLで返す
## Controllerの修正

```kotlin:TaskController.kt
package com.example.todolist

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping

@Controller
@RequestMapping("tasks")
class TaskController {
    @GetMapping
    fun index(model: Model): String {
        val tasks = listOf(
                Task(1, "Test Task1", false),
                Task(2, "Test Task2", true),
                Task(3, "Test Task3", false)
        )
        model.addAttribute("tasks", tasks)
        return "tasks/index"
    }
}

```

修正点

* importとアノテーションをRestControllerからControllerに変更
* model.addAttributeでテンプレートにデータを引き渡す処理を追加
* returnはテンプレートの名称を返すように変更


## テンプレートの追加

* Thymeleafというテンプレートエンジンを使用
* テンプレートファイルはsrc/main/resources/templates/に配置する

```html:tasks/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>タスク一覧</title>
</head>
<body>
<ul th:unless="${tasks.isEmpty()}">
    <li th:each="task: ${tasks}">
        <span th:unless="${task.done}" th:text="${task.content}"></span>
        <s th:if="${task.done}" th:text="${task.content}">

    </li>
</ul>
</body>
</html>

```

* Thymeleafのテンプレート要素はth:から始まる属性をつける
* th:if は条件が当てはまるときに表示する
* th:unless は th:if の逆
* th:each は 要素の繰り返し
* th:text は要素の中身を出力する

## 動作確認
ブラウザで以下のURLにアクセスする。

```
http://localhost:8080/task
```

ブラウザに以下が表示される

* Test Task1
* ~~Test Task2~~
* Test Task3


## 保存されているタスクを表示するように
TaskRepositoryインターフェースの定義

```kotlin:TaskRepository.kt
package com.example.todolist

interface TaskRepository {
    fun create(content: String): Task
    fun update(task: Task)
    fun findAll(): List<Task>
    fun findById(id: Long): Task?
}

```

メモリ上(変数)にタスクを保存するTaskRepositoryの実装をする。

```kotlin:InMemoryTaskRepository.kt
package com.example.todolist

import org.springframework.stereotype.Repository

@Repository
class InMemoryTaskRepository : TaskRepository {
    private val tasks: MutableList<Task> = mutableListOf()

    private val maxId: Long
        get() = tasks.map(Task::id).max() ?: 0

    override fun create(content: String): Task {
        val id = maxId + 1
        val task = Task(id, content, false)
        tasks += task
        return task
    }

    override fun update(task: Task) {
        tasks.replaceAll{ t ->
            if (t.id == task.id) task
            else t
        }
    }

    override fun findAll(): List<Task> = tasks.toList()

    override fun findById(id: Long): Task? = tasks.find { it.id == id }
}

```

上記を使うためにControllerの修正を行う。

```kotlin:TaskController.kt
package com.example.todolist

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping

@Controller
@RequestMapping("tasks")
class TaskController(private val taskRepository: TaskRepository) {
    @GetMapping
    fun index(model: Model): String {
        val tasks = taskRepository.findAll()
        model.addAttribute("tasks", tasks)
        return "tasks/index"
    }
}

```

テンプレートを修正し、タスクがない旨を表す。

```html:tasks/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>タスク一覧</title>
</head>
<body>
<p th:if="${tasks.isEmpty()}">タスクがありません。</p>
<ul th:unless="${tasks.isEmpty()}">
    <li th:each="task: ${tasks}">
        <span th:unless="${task.done}" th:text="${task.content}"></span>
        <s th:if="${task.done}" th:text="${task.content}">

    </li>
</ul>
</body>
</html>

```

※ pタグを追加

# タスク新規作成
タスクを作成する機能を実装する。

## フォームクラス作成
こういうクラスをフォームクラスと呼ぶらしい。
Symfonyのformhelperみたいなもんか？

```kotlin:TaskCreateFrom.kt
package com.example.todolist

import org.hibernate.validator.constraints.NotBlank
import javax.validation.constraints.Size

class TaskCreateForm {
    @NotBlank
    @Size(max = 20)
    var content: String? = null
}

```

## コントローラの修正
importの追加
```kotlin:TaskController.kt
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.servlet.mvc.support.RedirectAttributes
import org.springframework.validation.BindingResult
import org.springframework.validation.annotation.Validated

```

tasks/newページの作成

```kotlin:TaskController.kt
// 以下のメソッドを追加
@GetMapping("new")
fun new(form: TaskCreateForm): String {
    return "tasks/new"
}
```

タスクを新規作成する処理を追加

```kotlin:TaskController.kt
// 以下のメソッドを追加
@PostMapping("")
fun create(@Validated form: TaskCreateForm, bindingResult: BindingResult): String {
    if (bindingResult.hasErrors()) {
        return "tasks/new"
    }
    val content = requireNotNull(form.content)
    taskRepository.create(content)
    return "redirect:/tasks"
}
```

## テンプレート追加/修正
tasks/new.htmlの追加

```html:tasks/new.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>タスク作成</title>
</head>
<body>
<form th:method="post" th:action="@{./}" th:object="${taskCreateForm}">
    <div>
        <label>
            内容： <input type="text" th:field="*{content}" />
        </label>
        <p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">エラーメッセージ</p>
    </div>
    <div>
        <input type="submit" value="作成" />
    </div>
</form>
</body>
</html>

```

tasks/index.htmlの修正

```html:tasks/index.html
<!-- 次の行を追加 -->
<a th:href="@{tasks/new}">作成</a>
```


# リンク
* [Spring Boot for Kotlin 1](https://qiita.com/Miracle-T-Shirt09/items/4d01804e50daef53bb48)