（続）Julia web frameworkのGenieでTODOリストを作る【2019年11月版】

# はじめに

約1年前（2018年11月）に下記の記事を書きました。
[Julia web frameworkのGenieでTODOリストを作る（登録のみ。更新は未完成）](https://qiita.com/yshutaro/items/16cb2e02655911fed83b)

しかし、[Genie](https://genieframework.com/) 自体の更新スピードが速く半年くらいで記事の内容が古くなってしまいました。
（参考）[JuliaのWebフレームワークGenieを使おうとしたらハマった。](https://qiita.com/zukako0930/items/7878a03713395c21c56f#_reference-9360b8d2dc9266c3ea05)

前回、更新処理も未完成だったので再度チャレンジしてみました。

アプリを起動したときに表示されたGenieのバーションは`Genie v0.19.0`です。（後述する[起動の補足](#起動の補足)で再起動したら`v0.19.2`と表示されました。）`v1.0`が出るまではまたすぐに古くなってしまうと思いますのでご注意ください。

# 公式ドキュメント

https://genieframework.github.io/Genie.jl/

## チュートリアル

`Books`アプリケーションのチュートリアルはこちらにあります。今回もこれを参考にしました。
https://genieframework.github.io/Genie.jl/guides/Working_With_Genie_Apps.html
https://genieframework.github.io/Genie.jl/guides/Working_With_Genie_Apps_Intermediary_Topics.html

# 準備

## Juliaバージョン

公式ドキュメントには`V1.0`以上が必要とあります。

> Genie is compatible with Julia v1.0 and up.

私の環境では`1.2.0`を使用しました。

```shell
$ julia --version
julia version 1.2.0
```

OSはMacです。

```shell
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15
BuildVersion:   19A603
```

## Genieパッケージインストール

Juliaを起動し、Juliaモードで`]`を入力してパッケージモードになります

```shell
$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.2.0 (2019-08-20)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> ] # 右角括弧を入力してパッケージモードになります。
```

パッケージモードで下記のコマンドを入力します。

```julia
(v1.2) pkg> add Genie
（略）
(v1.2) pkg> # Backspaceでパッケージモードを抜けます。
julia> 
```

# 新規アプリケーション作成

アプリケーションを作成するディレクトリ（以下`$WORK`）に移動してJuliaを起動します。

```shell
$ cd $WORK # アプリケーションを作成するディレクトリに移動します。
$ julia # Julia起動
```

アプリケーションを生成します。前回同様ここでは名前を`todolist`にしています。

```julia
julia> using Genie

julia> Genie.newapp("todolist")
（略）

 _____         _
|   __|___ ___|_|___
|  |  | -_|   | | -_|
|_____|___|_|_|_|___|

| Web: https://genieframework.com
| GitHub: https://github.com/genieframework/Genie.jl
| Docs: https://genieframework.github.io/Genie.jl
| Gitter: https://gitter.im/essenciary/Genie.jl
| Twitter: https://twitter.com/GenieMVC

Genie v0.19.0
Active env: DEV

Web Server starting at http://0.0.0.0:8000 
Web Server running at http://0.0.0.0:8000 

julia> 
```

起動したようです。前回はプロンプトが`genie>`でしたが今回は`julia>`のままになっています。
ブラウザで
http://localhost:8000/
にアクセしてみます。

<img width="1188" alt="スクリーンショット 2019-11-06 13.00.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/cc3aadde-057b-0dce-c059-48a54a9fa90d.png">


起動しています！

# ディレクトリ構成

別のターミナルを開いてディレクトリを確認します。

```shell
$ cd $WORK
$ ls
todolist
```

アプリケーションの`todolist`ディレクトリが出来ています。アプリケーションディレクトリに移動します。

```shell
$ cd todolist
$ ls -1
Manifest.toml
Project.toml
bin
bootstrap.jl
config
genie.jl
public
routes.jl
src
````

前回下記のように書きました。
> Genieのルールではapp/resources/ディレクトリの下に機能のディレクトリ（小文字）を作成し、その下にコントローラーやビューを置くようです。

存在したはずの`app`ディレクトリが見当たらないなと思ったらドキュメントに補足がありました。

> When creating a default Genie app, the app/ folder might be missing. This will be automatically created the first time you add a resource via Genie’s generators.
> （Google翻訳）
> デフォルトのGenieアプリを作成するときに、app /フォルダーが欠落している可能性があります。 これは、Genieのジェネレーターを介してリソースを初めて追加したときに自動的に作成されます。

どうやらディレクトリ構成は変わっていないようです。
上記の補足もあるのでGenieのコマンドを使ってコントローラーから作成していきます。

### 起動の補足

Genieを間違って抜けてしまったときは、アプリケーションディレクトリに移動し、`bin/repl`を実行後`up()`関数を実行します。（`bin/server`だけでも起動しますが、`julia> `のプロンプトになりませんでした。）

```shell
$ cd $WORK/todolist
$ bin/repl
```

```julia
 _____         _
|   __|___ ___|_|___
|  |  | -_|   | | -_|
|_____|___|_|_|_|___|

| Web: https://genieframework.com
| GitHub: https://github.com/genieframework/Genie.jl
| Docs: https://genieframework.github.io/Genie.jl
| Gitter: https://gitter.im/essenciary/Genie.jl
| Twitter: https://twitter.com/GenieMVC

Genie v0.19.2
Active env: DEV

Ready! 
julia> up()
Web Server starting at http://0.0.0.0:8000 
Web Server running at http://0.0.0.0:8000 
Dict{Symbol,Task} with 1 entry:
  :ws => Task (runnable) @0x000000011243ed10

julia> 
```

明示的に終了させる場合は`exit()`関数を実行します。

```julia
julia> exit()
```

# 機能作成（DBなしのサンプル表示まで）

## タスク一覧表示

### コントローラーの作成

アプリケーション作成後、または上記[起動の補足](#起動の補足)で起動後の`julia> `プロンプトの状態で`Genie.newcontroller()`を実行します。

```julia
julia> Genie.newcontroller("Tasks")
[ Info: （日時）  New controller created at ./app/resources/tasks/TasksController.jl
```

ディレクトリを確認すると`todolist`の下に`app`ディレクトリが作成され、コントローラーを含むいくつかのファイルが出来ています。

```shell
$ pwd
$WORK/todolist/app
$ tree
.
├── helpers
│   ├── ValidationHelper.jl
│   └── ViewHelper.jl
├── layouts
│   └── app.jl.html
└── resources
    └── tasks
        └── TasksController.jl
```

`TasksController.jl`を修正し、ダミーのタスクを設定します。

注：structとTaskの間にはスペースがありますがQiitaの表示ではくっついてしまっています。
前回`index`関数の`html!`だった所は`html`になっています。`html!`だとエラーになります。

```app/resources/tasks/TasksController.jl
module TasksController
using Genie.Renderer

struct Task
  id::Int
  content::String
  done::Bool
end

const SampleTasks = Task[
  Task(1,"歯医者に行く",false),
  Task(2,"はがきを出す",true),
  Task(3,"定期券を買う",false),
]

function index()
  html(:tasks, :tasks, tasks = SampleTasks)
end

end
```

### ビューの作成

コントローラーを作成した`julia> `プロンプトの状態で下記を実行します。

```julia
julia> mkdir(joinpath("app", "resources", "tasks", "views"))
"app/resources/tasks/views"

julia> touch(joinpath("app", "resources", "tasks", "views", "tasks.jl.html"))
"app/resources/tasks/views/tasks.jl.html"
```

`$WORK/app/resources/tasks/views`ディレクトリに`tasks.jl.html`が作られたのでこれを修正します。
`app/layouts/app.jl.html`に全体のテンプレートがあるので、body部分だけ書きます。

```app/resources/tasks/views/tasks.jl.html
<ul>
<%
  if length(@vars(:tasks)) == 0
    "タスクがありません。"
  else
    @foreach(@vars(:tasks)) do task
      if task.done
      "<li>
$(task.content)
</li>"
      else
      "<li>$(task.content)</li>"
      end
    end
  end
 %>
</ul>
```

### routeの設定

routes.jlにタスク一表示用ルートを追加します。（前回は`config`ディレクトリの下にありましたが今回はアプリケーションディレクトリの直下に存在しました。）

エディタ（vim)で直接編集しようとしたところパーミッションが読み取り専用になっていました。（-r--r--r--）
チュートリアルではエディタによる直接修正または下記のコマンドを実行とあります。

```julia
julia> edit("routes.jl")
```

私は直接修正して強制上書きしました。

```routes.jl
using Genie.Router
using TasksController # 追加

route("/") do
  serve_static_file("welcome.html")
end

# 追加（タスク表示）
route("/tasks", TasksController.index)
```

### 確認

http://localhost:8000/tasks
にアクセスします。

+ 一旦`/tasks Not found`になってしまったので[起動の補足](#起動の補足)で再起動しました。

<img width="920" alt="スクリーンショット 2019-10-30 14.17.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/7828977a-038b-09c4-93f9-e0378dfa72ed.png">

表示されました！

次に、データベースをセットアップし、サンプル表示ではなく、タスクの表示・新規作成・更新を行えるように修正してきます。

# 機能作成（表示・新規作成・更新）

## パッケージ追加

`SearchLight`というORMパッケージを追加します。上記に引き続き`julia> `プロンプトでパッケージモードになります。

```julia
julia> ] # 右角括弧を入力してパッケージモードになります。
(todolist) pkg> 
```

プロンプトにアプリケーション名が付いた`(todolist) pkg> `になります。アプリケーションのトップディレクトリに`Project.toml`というファイルがありこれでパッケージを管理しているようです。

```julia
(todolist) pkg> add SearchLight

(todolist) pkg> # Backspaceでパッケージモードを抜けます。
julia> 
```

## データベースセットアップ

### dbディレクトリ作成

`julia> `プロンプトで下記を実行します。

```julia
julia> Genie.Generator.db_support()
```

実行後、アプリケーションディレクトリに`db`ディレクトリが作成されます。

```shell
$ pwd
$WORK/todolist/db
$ tree
.
├── connection.yml
├── migrations
└── seeds

2 directories, 1 file
```

### DB接続設定

`db/connection.yml`を修正します。チュートリアルに倣い下記のようにします。

```db/connection.yml
env: ENV["GENIE_ENV"]

dev:
  adapter: SQLite
  database: db/tasks.sqlite
  host:
  username:
  password:
  port:
  config:

prod:
  adapter:
  database:
  host:
  username:
  password:
  port:
  config:

test:
  adapter:
  database:
  host:
  username:
  password:
  port:
  config: 
```

`julia> `プロンプトに戻り下記を実行します。

```julia
julia> using SearchLight
（略）
julia> SearchLight.Configuration.load_db_connection()
Dict{String,Any} with 7 entries:
  "host"     => nothing
  "password" => nothing
  "config"   => nothing
  "username" => nothing
  "port"     => nothing
  "database" => "db/tasks.sqlite"
  "adapter"  => "SQLite"
```

正しく設定が読み込めているようです。

### DB接続＋DB作成

続いて下記を実行します。

```julia
julia> SearchLight.Configuration.load_db_connection() |> SearchLight.Database.connect!
```

下記が表示されます。

```julia
SQLite.DB("db/tasks.sqlite")
```

ファイルの確認

```shell
$ pwd
$WORK/todolist/db
$ ls
connection.yml migrations     seeds          tasks.sqlite
```

`tasks.sqlite`ファイルが出来ています。

## DB Migration

続いて`SearchLight.init()`を実行します。

```julia
julia> SearchLight.init()
┌ Info: （日時） CREATE TABLE `schema_migrations` (
│     `version` varchar(30) NOT NULL DEFAULT '',
│     PRIMARY KEY (`version`)
└   )
  0.598814 seconds (1.31 M allocations: 65.443 MiB, 5.10% gc time)
[ Info: （日時） Created table schema_migrations
true
```

DBを確認してみます。

```shell
$ pwd
$WORK/todolist/db
$ sqlite3 tasks.sqlite 
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE `schema_migrations` (
    `version` varchar(30) NOT NULL DEFAULT '',
    PRIMARY KEY (`version`)
  );
```

### モデルの作成

引き続き上記の`julia> `プロンプトで下記を実行し、`Task`というモデルを作成します。

```julia
julia> SearchLight.Generator.newresource("Task")
[ Info: （日時） New model created at $WORK/todolist/app/resources/tasks/Tasks.jl
[ Info: （日時） New table migration created at $WORK/todolist/db/migrations/2019103006094007_create_table_tasks.jl
[ Info: （日時） New validator created at $WORK/todolist/app/resources/tasks/TasksValidator.jl
[ Info: （日時） New unit test created at $WORK/todolist/test/unit/tasks_test.jl
```

### create_tableファイルの修正

自動で作成された`*_create_table_tasks.jl`ファイルを修正します。

※注意：自動生成されたファイルの3行目のimport文が間違っています。Migrationsだとこの後エラーになります。（GitHubでissueをあげておきました。）
誤：import SearchLight.Migrations
正：mport SearchLight.Migration

```db/migrations/（自動生成）_create_table_tasks.jl
module CreateTableTasks

import SearchLight.Migration: create_table, column, primary_key, add_index, drop_table

function up()
  create_table(:tasks) do
    [
      primary_key()
      column(:content, :string)
      column(:done, :bool)
    ]
  end

  add_index(:tasks, :content)
end

function down()
  drop_table(:tasks)
end

end
```

### Migration実行

Migration実行前に`SearchLight.Migration.status()`でステータスチェックを行います。

```julia
julia> SearchLight.Migration.status()
|   |                  Module name & status  |
|   |                             File name  |
|---|----------------------------------------|
|   |                 CreateTableTasks: DOWN |
| 1 | 2019103006094007_create_table_tasks.jl |
```

`DOWN`になっています。

`SearchLight.Migration.last_up()`でMigration実行します。

```julia
julia> SearchLight.Migration.last_up()
[ Info: （日時） CREATE TABLE tasks (id INTEGER PRIMARY KEY , content VARCHAR , done BOOLEAN )
  0.002634 seconds (35 allocations: 3.266 KiB)
[ Info: （日時） CREATE  INDEX tasks__idx_content ON tasks (content)
  0.001000 seconds (35 allocations: 3.266 KiB)
[ Info: （日時） Executed migration CreateTableTasks up
```

再度ステータスチェックを行います。

```julia
julia> SearchLight.Migration.status()
|   |                  Module name & status  |
|   |                             File name  |
|---|----------------------------------------|
|   |                   CreateTableTasks: UP |
| 1 | 2019103006094007_create_table_tasks.jl |
```

`UP`になっています。

DBの内容を直接確認してみます。

```shell
sqlite3 tasks.sqlite
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE `schema_migrations` (
    `version` varchar(30) NOT NULL DEFAULT '',
    PRIMARY KEY (`version`)
  );
CREATE TABLE tasks (id INTEGER PRIMARY KEY , content VARCHAR , done BOOLEAN );
CREATE INDEX tasks__idx_content ON tasks (content);
```

テーブルとインデックスが作成されています。

### モデルの修正

自動で作成された`app/resources/tasks/Tasks.jl`を修正します。
`Task`structにフィールドを追加し、初期データ用関数を追加します。初期データは最初にサンプルで埋め込んだものと同じだとわからなくなるので別のデータにします。



```app/resources/tasks/Tasks.jl
module Tasks

using SearchLight

export Task

mutable struct Task <: AbstractModel
  ### INTERNALS
  _table_name::String
  _id::String

  ### FIELDS
  id::DbId
  content::String # 追加
  done::Bool      # 追加

  ### constructor
  Task(;
    ### FIELDS
    id = DbId(),  # カンマ追加
    content = "", # 追加
    done = false  # 追加
  ) = new("tasks", "id",                                                 ### INTERNALS
          id, content, done                                              ### FIELDS
          )
end

function seed()
  SampleTasks = [
    ("塩を買う", true),
    ("簡易書留を出す", false),
    ("本を5冊読む", false)
  ]
  for task in SampleTasks
    Task(content = task[1], done = task[2]) |> SearchLight.save!
  end
end

end
```

### seedの実行

チュートリアルによればDBの設定はアプリケーションの起動時に読み込むようなので、`julia> `プロンプトで`exit()`を実行してアプリケーションを一旦終了します。

```julia
julia> exit()
```

`bin/repl`でアプリーションを起動しますが、まだ`up()`は実行しません。

```shell
$ bin/repl
（略）
julia> 
```

次を実行し、seedを実行します。

```julia
julia> using Tasks

julia> Tasks.seed()
[ Info: （日時） INSERT  INTO tasks ( "content", "done" ) VALUES ( '塩を買う', true )
  0.056246 seconds (179.13 k allocations: 9.518 MiB, 13.86% gc time)
[ Info: （日時） ; SELECT CASE WHEN last_insert_rowid() = 0 THEN -1 ELSE last_insert_rowid() END AS id
  0.148794 seconds (292.32 k allocations: 14.856 MiB, 8.55% gc time)
[ Info: （日時） SELECT "tasks"."id" AS "tasks_id", "tasks"."content" AS "tasks_content", "tasks"."done" AS "tasks_done" FROM "tasks" WHERE ("tasks"."id" = 1) ORDER BY tasks.id ASC LIMIT 1
  0.106621 seconds (340.23 k allocations: 17.029 MiB)
[ Info: （日時） INSERT  INTO tasks ( "content", "done" ) VALUES ( '簡易書留を出す', false )
  0.002415 seconds (35 allocations: 3.266 KiB)
[ Info: （日時） ; SELECT CASE WHEN last_insert_rowid() = 0 THEN -1 ELSE last_insert_rowid() END AS id
  0.000091 seconds (52 allocations: 3.922 KiB)
[ Info: （日時） SELECT "tasks"."id" AS "tasks_id", "tasks"."content" AS "tasks_content", "tasks"."done" AS "tasks_done" FROM "tasks" WHERE ("tasks"."id" = 2) ORDER BY tasks.id ASC LIMIT 1
  0.000138 seconds (66 allocations: 4.656 KiB)
[ Info: （日時） INSERT  INTO tasks ( "content", "done" ) VALUES ( '本を5冊読む', false )
  0.001347 seconds (35 allocations: 3.266 KiB)
[ Info: （日時） ; SELECT CASE WHEN last_insert_rowid() = 0 THEN -1 ELSE last_insert_rowid() END AS id
  0.000103 seconds (52 allocations: 3.922 KiB)
[ Info: （日時） SELECT "tasks"."id" AS "tasks_id", "tasks"."content" AS "tasks_content", "tasks"."done" AS "tasks_done" FROM "tasks" WHERE ("tasks"."id" = 3) ORDER BY tasks.id ASC LIMIT 1
  0.000142 seconds (66 allocations: 4.656 KiB)
```

次のコマンドでデータの確認を行います。

```julia
julia> using SearchLight

julia> all(Tasks.Task)
3-element Array{Tasks.Task,1}:
 
Tasks.Task
|               KEY |    VALUE |
|-------------------|----------|
| content :: String | 塩を買う |
|      done :: Bool |     true |
|        id :: DbId |        1 |
                           
 
Tasks.Task
|               KEY |          VALUE |
|-------------------|----------------|
| content :: String | 簡易書留を出す |
|      done :: Bool |          false |
|        id :: DbId |              2 |

 
Tasks.Task
|               KEY |       VALUE |
|-------------------|-------------|
| content :: String | 本を5冊読む |
|      done :: Bool |       false |
|        id :: DbId |           3 |

```

※チュートリアルでは`all(Book)`だけで確認できましたが、`all(Tasks)`としたところエラーとなりました。`all(Tasks.Task)`だとうまくいきました。


直接DBを確認してみます。

```shell
$ pwd
$WORK/todolist/db
$ sqlite3 tasks.sqlite
（略）
sqlite> select * from tasks;
1|塩を買う|1
2|簡易書留を出す|0
3|本を5冊読む|0
```

データが入りました。

## タスク表示

### コントローラーの修正

サンプルが埋め込んであった`TasksController.jl`を修正します。サンプルを削除し、`SearchLight.all(Tasks.Task)`を返すようにします。`using SearchLight`と`using Tasks`も追加します。

```app/resources/tasks/TasksController.jl
module TasksController

using Genie.Renderer
using SearchLight # 追加
using Tasks # 追加

struct Task
  id::Int
  content::String
  done::Bool
end

function index()
  html(:tasks, :tasks, tasks = SearchLight.all(Tasks.Task))
end

end
```

`up()`を実行し起動します。

```julia
julia> up()
Web Server starting at http://0.0.0.0:8000 
Web Server running at http://0.0.0.0:8000 
Dict{Symbol,Core.Task} with 1 entry:
  :ws => Task (runnable) @0x000000011b9a0fd0
```

http://localhost:8000/tasks
にアクセスします。

<img width="920" alt="スクリーンショット 2019-10-31 10.46.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/ef4ac6d5-5a45-7ae2-e9b8-a3952e6068ec.png">

表示されました！

## タスク新規作成

データベースの内容が表示できたので、タスクを新規作成する機能を作ります。

### サンプルデータ削除

一旦サンプルのデータは削除します。

```shell
$ pwd
$WORK/todolist/db
$ sqlite3 tasks.sqlite
sqlite> delete from tasks;
sqlite> select count(*) from tasks;
0
```

http://localhost:8000/tasks

<img width="920" alt="スクリーンショット 2019-10-31 10.52.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/e58cd236-7060-19c2-d38d-1091104915c3.png">

### ルートの追加

routes.jlに下記を追加します。

```routes.jl
# 追加
route("/tasks/new", TasksController.new)
route("/tasks/create", TasksController.create, method = POST, named = :create_task)
```

### コントローラーの修正

`TasksController.jl`に下記のfunctionを追加します。create()の処理は後で書きます。
（ここも前回と違い`html!`だとエラーになります。）

```app/resources/tasks/TasksController.jl
function new()
  html(:tasks, :new)
end

function create()
end
```

### 新規登録用ビューの作成

`new.jl.html`を作成します。

```julia
julia> touch("app/resources/tasks/views/new.jl.html")
"app/resources/tasks/views/new.jl.html"
```

空ファイルができるのでエディタで編集します。

```app/resources/tasks/views/new.jl.html
<form action="$(Genie.Router.linkto(:create_task))" method="POST">
  <div>
    <label>
        内容:<input type="text" name="task_content"/>
    </label>
  </div>
  <div>
    <input type="submit" value="作成" />
  </div>
</form>
```

ここで一旦下記にアクセスしてみます。
http://localhost:8000/tasks/new

<img width="920" alt="スクリーンショット 2019-10-31 11.08.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/ddedd57a-0097-13ca-913f-90ae9fabe42f.png">

表示されました！
前回はここでエラーが出て苦労したのですが今回はなにも問題ありませんでした。
Not foundになる場合は[起動の補足](#起動の補足)で再起動します。

### ルートの修正

登録後のリダイレクトを一覧表示画面にしたいので、`/tasks`に`get_tasks`という名前を付けます。リンク用に`tasks/new`にも名前を付けます。

```routes.jl
route("/tasks", TasksController.index, named = :get_tasks) # namedを追加
route("/tasks/new", TasksController.new, named = :new_task) # namedを追加
```

### コントローラーの修正(create)

保留していた`create()`関数を修正します。タスクの初期は未完了なのでdoneはfalse固定です。
`using Genie.Router`も追加します。

```app/resources/tasks/TasksController.jl
using Genie.Router # 追加

# 修正
function create()
  Tasks.Task(content = @params(:task_content), done = false) |> save && redirect(:get_tasks)
end
```

http://localhost:8000/tasks/new
にアクセスし、内容に適当に文字列を入れて[作成]ボタンをクリックします。

<img width="1083" alt="スクリーンショット 2019-11-06 14.01.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/19d7ca24-259c-2e60-3f34-b8cc5163b7c8.png">


<img width="1083" alt="スクリーンショット 2019-11-06 14.03.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/6804c27b-4ff7-98a2-aa68-a23736884979.png">


一覧画面にリダイレクトされ入力した内容が表示されています！
※実はここで500 Internal Server Errorが発生して調べたところバグぽっかったのでissueをあげたら速攻で直してくれました。

では、一覧画面から作成画面に遷移できるように`tasks.jl.html`にリンクを付けます。（1行目に追加）

```app/resources/tasks/views/tasks.jl.html
<a href="$(Genie.Router.linkto(:new_task))">作成</a>
（略）
```

http://localhost:8000/tasks
にアクセスします。

<img width="1108" alt="スクリーンショット 2019-11-06 14.17.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/345c00dc-ddee-e3ca-82d6-034e006c78cd.png">


## タスク更新

### ルートの追加

`routes.jl`に下記を追加します。

```routes.jl
route("/tasks/:id::Int/edit", TasksController.edit, method = GET, named = :edit_task)
route("/tasks/:id::Int/update", TasksController.update, method = POST, named = :update_task)
```

`id`の指定の方法はここを参考にしました。
https://github.com/GenieFramework/Genie.jl/blob/master/docs/documentation/12--Advanced_Routing_Techniques.md

### コントローラーの修正

TasksController.jlに下記のfunctionを追加します。`payload`関数でURLに含まれる`id`を取得します。`payload`関数のため`using Genie.Requests`も追加します。
`update()`の処理は後ほど書きます。

```app/resources/tasks/TasksController.jl
using Genie.Requests # 追加

function edit()
  id = payload(:id)
  task = SearchLight.findoneby(Tasks.Task , SQLWhereExpression("id = ?", id))
  html(:tasks, :edit, task = task)
end

function update()
end
```

### タスク一覧ビューの修正

タスク一覧のタスクを`tasks/edit`へのリンクにして、`id`をURLに含めるようにしました。
※ `linto`関数の引数にパラメータを入れると`/tasks/1/edit`のようになります。

```app/resources/tasks/views/tasks.jl.html
<a href="$(Genie.Router.linkto(:new_task))">作成</a>
<ul>
<%
  if length(@vars(:tasks)) == 0
    "タスクがありません。"
  else
    @foreach(@vars(:tasks)) do task
      if task.done
        """<a href="$(Genie.Router.linkto(:edit_task, id = task.id))"><li>
$(task.content)
</li></a>"""
      else
        """<a href="$(Genie.Router.linkto(:edit_task, id = task.id))"><li>$(task.content)</li></a>"""
      end
    end
  end
 %>
</ul>

```

### タスク更新用ビューの作成

`edit.jl.html`を作成します。

```julia
julia> touch("app/resources/tasks/views/edit.jl.html")
"app/resources/tasks/views/edit.jl.html"
```

空ファイルができるのでエディタで編集します。`update_task`のURLに`id`を含めるように引数に指定します。

```app/resources/tasks/views/edit.jl.html
<form action="$(Genie.Router.linkto(:update_task, id = (get(@vars(:task))).id))" method="POST">
  <div>
    <label>
       内容:<input type="text" name="task_content" value="$((get(@vars(:task))).content)" />
    </label>
  </div>
  <div>
    <label>
        <%
          if (get(@vars(:task))).done == true
            """完了:<input type="checkbox" name="task_done" value="1" checked="checked" />"""
          else 
            """完了:<input type="checkbox" name="task_done" value="1" />"""
          end
         %>
    </label>
  </div>
  <div>
    <input type="hidden" name="task_id" value="1" />
  </div>
  <div>
    <input type="submit" value="更新" />
  </div>
</form>
```

ここで一旦確認のため
http://localhost:8000/tasks
にアクセスします。
ルートの追加なので`Route named `edit_task` is not defined`の`500 Internal Server Error`が発生するかもしれません。その場合は[起動の補足](#起動の補足)で再起動します。

<img width="1084" alt="スクリーンショット 2019-11-06 11.25.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/ddd4afe2-fbf7-1b2a-9b8b-7e96ca287736.png">


タスク名がリンクになっていますので、クリックします。

<img width="1084" alt="スクリーンショット 2019-11-06 11.27.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/ec128493-30b7-fa44-9e1f-d55a8ee04dba.png">

更新画面が表示されました！まだ更新処理を書いていないので追記していきます。

### コントローラーの修正2

`update()`関数の処理を書きます。更新処理後にタスク一覧画面にリダイレクトするようにします。

```app/resources/tasks/TasksController.jl
function update()
  id = payload(:id)
  done = false
  try
    @params(:task_done)
    done = true
  catch
    done = false
  end
  Tasks.Task(id = id, content = @params(:task_content), done = done) |> save && redirect(:get_tasks)
end
```

チェックボックスにチェックを入れないとパラメータが飛ばないので、`@params(:task_done)`で例外が発生します。上記のような書き方にしましたがもっとシンプルに書ける方法もあると思います。

もう一度、タスク更新画面に遷移後、タスク名を修正して完了にチェックを入れて[更新]ボタンをクリックします。

<img width="1084" alt="スクリーンショット 2019-11-06 11.31.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/fedac03d-e673-f658-ed0e-731eab1313b8.png">

<img width="1084" alt="スクリーンショット 2019-11-06 11.31.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/18bdff83-5e4b-2675-0c0d-9c6e9a3c81c9.png">

無事に更新されています！
タスクの完了を元に戻してみます。

<img width="1084" alt="スクリーンショット 2019-11-06 11.37.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/a5fa460f-6392-fdc0-2f47-936fef129f91.png">

<img width="1084" alt="スクリーンショット 2019-11-06 11.37.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66839/5df21844-88fc-0a78-1a83-f1432ae8cd13.png">

うまく動いています！

# 実装していないこと

+ バリデーション
バリデーションを行っていないので空文字でタスク作成できてしまいます。その場合は選択もできないのでゴミになります。`TasksValidator.jl`が自動生成されるのでドキュメントやソースをみて書き方を調べようと思います。

+ 削除
削除は実装していないので間違って作成すると消せません。

# 感想

今回は無事、更新処理まで書くことが出来ました。1年前と書き方が変わっていたりしましたが、エラーの発生は減っていたので早く進められたと思います。何かのご参考になれば幸いです。