24
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Julia web frameworkのGenieでTODOリストを作る(登録のみ。更新は未完成)

Last updated at Posted at 2018-11-10

この記事の内容は古いです。続編を書きましたのでそちらをご参照ください。
(続)Julia web frameworkのGenieでTODOリストを作る【2019年11月版】

はじめに

最近、Juliaという言語が流行っていることを知りました。
https://twitter.com/hashtag/julia%E8%A8%80%E8%AA%9E?lang=ja
こんな記事もありました。
https://japan.zdnet.com/article/35124177/

Webアプリケーションフレームワークとかあるのかな?
Genieというのがありました。
http://genieframework.com/

ということで、GenieでよくあるサンプルのTODOリストを作ってみようと思いました。

TODOリスト機能

今回作成するWebアプリケーションの機能は下記の通りです。

  • タスク一覧表示
  • タスク新規作成
  • タスク更新

この機能は下記Kotlin本のサンプルがわかりやすかったので名前など同じ形式にしています。
Kotlin Webアプリケーション 新しいサーバサイドプログラミング

準備

Juliaインストール

インストールの詳細は公式ページや他の記事をご参照ください。すみません。
https://julialang.org/downloads/

私の環境はMacで、公式ページから.dmgファイルダウンロードではなく下記でインストールしました。

$ brew cask install julia
$ julia --version
julia version 1.0.1

現時点(2018/11/10)で公式ページでの最新バージョンはv1.0.2です。Homebrew版はv1.0.1のようです。(これを書いている途中でアップグレードしたので途中の記述がv1.0.0のままになっているところがあるかもしれません。)

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

一旦アプリケーションを作成するディレクトリに移動してJuliaを起動します。

$ cd mywork # アプリケーションを作成するディレクトリに移動します。
$ julia # Julia起動
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.0.1 (2018-09-29)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> 

Genie取得。
Genieの公式ページには書いていないのですが、using Pkg を実行しておかないとエラーが出ます。

julia> Pkg.clone("https://github.com/essenciary/Genie.jl") # Get Genie
ERROR: UndefVarError: Pkg not defined
julia> using Pkg

julia> Pkg.clone("https://github.com/essenciary/Genie.jl") # Get Genie
 Warning: Pkg.clone is only kept for legacy CI script reasons, please use `add`
 @ Pkg.API /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:445
  Updating registry at `~/.julia/registries/General`
(略)
Building MbedTLS ──→ `~/.julia/packages/MbedTLS/mkHpa/deps/build.log`

julia> using Genie
julia>

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

julia> Genie.REPL.new_app("todolist")
(略)
[2018-10-07 02:38:52|info]: Starting your brand new Genie app - hang tight!
 _____         _
|   __|___ ___|_|___
|  |  | -_|   | | -_|
|_____|___|_|_|_|___|


Starting Genie in >> DEV << mode 
(略)
[2018-10-07 02:39:20|info]: Web server running at 127.0.0.1:8000

genie> 

ここでWeb server running at 127.0.0.1:8000と表示されているので、ブラウザで
http://localhost:8000/
にアクセしてみます。
スクリーンショット 2018-10-07 2.46.45.png

起動しています!
プロンプトがgenie>になっているので、このままにして別のターミナルを開きます。

Juliaを起動したディレクトリに移動します。

$ cd mywork
$ ls
todolist

アプリケーションが生成されています。この後はこのディレクトリ以下のファイルを修正していきます。

  • genieを間違って抜けてしまったときは、下記で立ち上がります。
$ cd todolist
$ ./bin/server

公式チュートリアル

下記にチュートリアルがありとても詳しいです。今回これを参考にしました。
https://github.com/essenciary/Genie.jl
(これを読めばこの後読まなくていいかも。)

  • アプリケーションを生成するとチュートリアルのREADME.mdが出来るのですが、それを元にサンプルを作成していたらエラーが出てしまいました。上記URLはIssueのやりとりでの修正も反映しているので上記を参照した方がよいです。

ディレクトリ構成

Genieのルールではapp/resources/ディレクトリの下に機能のディレクトリ(小文字)を作成し、その下にコントローラーやビューを置くようです。

app/resources/
└── tasks
    ├── TasksController.jl
    └── views
        └── tasks.jl.html

これは>genieプロンプトで下記のようにコマンドを実行すると自動的にコントローラーまで作成されます。
(公式チュートリアルのbooks機能の例です)

genie> Genie.REPL.new_controller("Books")
[info]: New controller created at app/resources/books/BooksController.jl

今回は勉強のため手動で作成していきます。

機能作成(DBなしのサンプル表示まで)

タスク一覧表示

コントローラーの作成

TasksController.jlを作成します。ダミーのタスクを設定します。

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

ビューの作成

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><s>$(task.content)</s></li>"
      else
      "<li>$(task.content)</li>"
      end
    end
  end
 %>
</ul>
  • routeの設定

routes.jlにタスク一表示用ルートを追加します。

config/routes.jl
using Genie.Router
using TasksController # 追加

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

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

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

スクリーンショット 2018-10-30 23.04.27.png

表示されました!

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

機能作成(表示・新規作成・更新)

パッケージ追加

SearchLightというORMパッケージを追加します。
pkg>のプロンプトにするには、julia>プロンプトで]を入力します。

julia> ] # ←"]"を入力すると下記のプロンプトになります。
(v1.0) pkg>

pkg>プロンプトを抜けるときはCtrl-Cです。

  • 下記のコマンドでSearchLightを追加します。
(v1.0) pkg> add https://github.com/essenciary/SearchLight.jl
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
(略)
  Building SQLite ──────────→ `~/.julia/packages/SQLite/xWe8d/deps/build.log`
  Building MySQL ───────────→ `~/.julia/packages/MySQL/owtkt/deps/build.log`

(v1.0) pkg>

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

database.yml

configディレクトリの下にdatabase.ymlというデータベース設定ファイルがあらかじめあるのでそれを編集します。

config/database.yml
env: dev

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> using SearchLight
[ Info: Precompiling SearchLight [340e8cb6-72eb-11e8-37ce-c97ebeb32050]

julia> SearchLight.Configuration.load_db_connection()
Dict{String,Any} with 0 entries

ん?0 entries? チュートリアルと違う。ソースコード(SearchLight.jl/src/Configuration.jl)を見るとパスを引数に設定しているようなので確認してみます。

julia> joinpath(SearchLight.CONFIG_PATH, SearchLight.SEARCHLIGHT_DB_CONFIG_FILE_NAME)
"<$HOME>/mywork/config/database.yml"

configディレクトリのパスがおかしい。(本当は<$HOME>/mywork/todolist/config/database.yml。)

パッケージ追加するときにtodolistディレクトリに移動していなかったかも。
もう一度、パッケージ追加してみます。

$ cd mywork/todolist
$ julia
julia> ]
(v1.0) pkg> add https://github.com/essenciary/SearchLight.jl

julia> using SearchLight
julia> joinpath(SearchLight.CONFIG_PATH, SearchLight.SEARCHLIGHT_DB_CONFIG_FILE_NAME)
"<$HOME>/mywork/todolist/config/database.yml"

お、正しくなった。
もう一度、設定読み込み。

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接続

julia> SearchLight.Configuration.load_db_connection() |> SearchLight.Database.connect!
[ Info: Precompiling SQLite [0aa819cd-b072-5ff4-a722-6bc24af294d9]
WARNING: could not import Base.lcfirst into LegacyStrings
WARNING: could not import Base.next into LegacyStrings
WARNING: could not import Base.rsearch into LegacyStrings
WARNING: could not import Base.search into LegacyStrings
WARNING: could not import Base.ucfirst into LegacyStrings
[ Info: Precompiling IterableTables [1c8ee90f-4401-5389-894e-7a04a3dc0f4d]
SQLite.DB("db/tasks.sqlite")

WARNINGがたくさん出て心配ですが、tasks.sqliteは出来ました。

$ cd db 
$ ls
migrations   seeds        tasks.sqlite

DB migrations

SearchLightにはmigrations機能があるようです。RailsやLaravelみたいで便利ですね。
db_init()を実行します。

julia> SearchLight.db_init()
[info | SearchLight.Loggers]: SQL QUERY: CREATE TABLE `schema_migrations` (
    `version` varchar(30) NOT NULL DEFAULT '',
    PRIMARY KEY (`version`)
  )
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
 Warning: In the future DataFrame constructor called with a `DataFrame` argument will return a copy. Use `convert(DataFrame, df)` to avoid copying if `df` is a `DataFrame`.
   caller = |>(::DataFrames.DataFrame, ::Type) at operators.jl:813
 @ Base ./operators.jl:813
  5.415232 seconds (6.16 M allocations: 303.549 MiB, 5.52% gc time)
[info | SearchLight.Loggers]: Created table schema_migrations
true

モデルの作成

これは手動作成は大変そうなのでコマンドを実行します。コマンドは上記に引き続き行います。
Taskというモデルを作成します。

julia> SearchLight.Generator.new_resource("Task")
[info | SearchLight.Loggers]: New model created at <$HOME>/todolist/app/resources/tasks/Tasks.jl
[info | SearchLight.Loggers]: New table migration created at <$HOME>/todolist/db/migrations/2018110113054822_create_table_tasks.jl
[info | SearchLight.Loggers]: New validator created at <$HOME>/todolist/app/resources/tasks/TasksValidator.jl
[info | SearchLight.Loggers]: New unit test created at <$HOME>/todolist/test/unit/tasks_test.jl
[warn | SearchLight.Loggers]: Can't write to app info

create_tableファイルの修正

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

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

import SearchLight.Migrations: 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

チュートリアルのBookstringだけだったので、boolがいけるか心配です。
SearchLight.Migration.status()でステータスチェックを行います。

julia> SearchLight.Migration.status()
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
|   |                  Module name & status  |
|   |                             File name  |
|---|----------------------------------------|
|   |                 CreateTableTasks: DOWN |
| 1 | 2018110113054822_create_table_tasks.jl |

SearchLight.Migration.last_up()でテーブルを作成します。

julia> SearchLight.Migration.last_up()
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
[info | SearchLight.Loggers]: SQL QUERY: CREATE TABLE tasks (id INTEGER PRIMARY KEY , content VARCHAR , done BOOLEAN )
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
  0.003359 seconds (686 allocations: 59.109 KiB)
[info | SearchLight.Loggers]: SQL QUERY: CREATE  INDEX tasks__idx_content ON tasks (content)
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
  0.005106 seconds (689 allocations: 59.156 KiB)
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
[info | SearchLight.Loggers]: Executed migration CreateTableTasks up

CREATE TABLE tasks (id INTEGER PRIMARY KEY , content VARCHAR , done BOOLEAN )
と出ているのでboolで問題ないみたいです。(相変わらずWarningがたくさん出ますが。)

もう一度、SearchLight.Migration.status()でステータスチェックを行います。

julia> SearchLight.Migration.status()
 Warning: `SQLite.query(db, sql)` will return an `SQLite.Query` object in the future; to materialize a resultset, do `DataFrame(SQLite.query(db, sql))` instead
   caller = ip:0x0
 @ Core :-1
 Warning: `SQLite.Source(db, sql)` is deprecated in favor of `SQLite.Query(db, sql)` which executes a query and returns a row iterator
   caller = ip:0x0
 @ Core :-1
|   |                  Module name & status  |
|   |                             File name  |
|---|----------------------------------------|
|   |                   CreateTableTasks: UP |
| 1 | 2018110113054822_create_table_tasks.jl |

CreateTableTasksのstatusがUPになりました。

モデルの修正

自動で作成されたapp/resources/tasks/Tasks.jlを修正します。
自動生成されたコメントは削除してもいいのですが、勉強なのでそのまま残しています。コメントで#追加とある行に追加しています。(### constructorのTaskの部分と、下の方のnewの部分です。)

app/resources/tasks/Tasks.jl
module Tasks

using SearchLight, Nullables, SearchLight.Validation, TasksValidator

export Task

mutable struct Task <: AbstractModel
  ### INTERNALS
  _table_name::String
  _id::String
  _serializable::Vector{Symbol}

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

  ### VALIDATION
  # validator::ModelValidator

  ### CALLBACKS
  # before_save::Function
  # after_save::Function
  # on_save::Function
  # on_find::Function
  # after_find::Function

  ### SCOPES
  # scopes::Dict{Symbol,Vector{SearchLight.SQLWhereEntity}}

  ### constructor
  Task(;
    ### FIELDS
    id = DbId(),
    content = "", # 追加
    done = false  # 追加


    ### VALIDATION
    # validator = ModelValidator([
    #   ValidationRule(:title, TasksValidator.not_empty)
    # ]), 

    ### CALLBACKS
    # before_save = (m::Todo) -> begin
    #   @info "Before save"
    # end,
    # after_save = (m::Todo) -> begin
    #   @info "After save"
    # end,
    # on_save = (m::Todo, field::Symbol, value::Any) -> begin
    #   @info "On save"
    # end,
    # on_find = (m::Todo, field::Symbol, value::Any) -> begin
    #   @info "On find"
    # end,
    # after_find = (m::Todo) -> begin
    #   @info "After find"
    # end,

    ### SCOPES
    # scopes = Dict{Symbol,Vector{SearchLight.SQLWhereEntity}}()

  ) = new("tasks", "id", Symbol[],                                                ### INTERNALS
          id, content, done                                                       ### FIELDS #追加
          # validator,                                                                  ### VALIDATION
          # before_save, after_save, on_save, on_find, after_find                       ### CALLBACKS
          # scopes                                                                      ### SCOPES
          )   
end

end

Seeding

初期データを作成してみます。app/resources/tasks/Tasks.jlに次のfunctionを追加します。最初にサンプルで埋め込んだものと同じだとわからなくなるので別のデータにします。

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

Juliaは配列のindexは1からなんですね。
追加したseed関数を実行します。

julia> ]
(v1.0) pkg> activate .
(todolist) pkg> # Ctrl-C
julia> using Genie
julia> Genie.REPL.load_app()
 _____         _
|   __|___ ___|_|___
|  |  | -_|   | | -_|
|_____|___|_|_|_|___|


Starting Genie in >> DEV << mode 

genie> using Tasks
[ Info: Precompiling Tasks [top-level]

genie> Tasks.seed()
ERROR: UndefVarError: DatabaseAdapter not defined

エラーが出ました。なかなかチュートリアル通りにはいかないです。
もう一度、Genieに入り直して下記を実行したところうまくいきました。

$ cd todolist
$ ./bin/server

※出力は一部省略しています。

genie> using SearchLight
genie> SearchLight.Configuration.load_db_connection()
genie> SearchLight.Configuration.load_db_connection() |> SearchLight.Database.connect!
genie> ]
(todolist) pkg> activate .
(todolist) pkg> (Ctrl-C)
genie> using Tasks
genie> Tasks.seed()
$ cd db
$ sqlite3 tasks.sqlite 
sqlite> select * from tasks;
1|塩を買う|1
2|簡易書留を出す|0
3|本を5冊読む|0

データが入りました。

コントローラーの修正

サンプルが埋め込んであったTasksController.jlを修正します。

app/resources/tasks/TasksController.jl
module TasksController

using Genie.Renderer
using SearchLight
using Tasks

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

end

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

スクリーンショット 2018-11-04 22.39.02.png

表示されました!

  • TasksController.jlSearchLight.all(Tasks.Task)の部分はチュートリアルではSearchLight.all(Book)で動いたのですが、Taskだけだとなぜかエラーになり試行錯誤でTasks.Taskにしたら動きました。

  • よくGenie再起動後にUndefVarError(:DatabaseAdapter)が出ることがあったのですが下記を再実行すると出なくなりました。(次の「抜けていた修正」を行ったところエラーは出なくなりました。)

genie> using SearchLight
genie> SearchLight.Configuration.load_db_connection() |> SearchLight.Database.connect!

抜けていた修正

GitHubのissueを上げたところ作者からご丁寧な返信があり、チュートリアルの一部を読み飛ばしていることが判明。(作者もREADMEをアップデートすると書いてくださいました。こんな質問にご対応いただき感謝です。)
その箇所とはconfig/initializers/searchlight.jlのコメントアウトを外せというもの。
ファイルの#==#を削除します。

config/initializers/searchlight.jl
# Uncomment the code to enable SearchLight support

#= ←これを消す
using SearchLight, SearchLight.QueryBuilder
(略)
Core.eval(Genie.REPL, :(using SearchLight, SearchLight.Generator, SearchLight.Migration))
=# ←これを消す

Genieを再起動します。

タスク新規作成

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

サンプルデータ削除

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

$ cd db
$ sqlite3 tasks.sqlite
delete from tasks;
sqlite> select count(*) from tasks;
0

スクリーンショット 2018-11-10 18.57.19.png

ルートの追加

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

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

コントローラーの修正

TasksController.jlに下記のfunctionを追加します。create()の処理は後で書きます。

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

function create()
end

追加用ビューの作成

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

app/resources/books/views/new.jl.html
<form action="$(Genie.Router.link_to(: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
が、500 Internal Server Errorが表示されてしまいます。
実はチュートリアルも下記にアクセスすると同じエラーが出て先に進めませんでした。 チュートリアルは上記の「抜けていた修正」を行ったところエラーが消えました。(関係なさそうなので、まだ理由は分かっていません。)
http://localhost:8000/bgbooks/new

いろいろ試したところ、<form>タグの"method"を消したらエラーが出なくなりました。原因不明のままですが全然進まないので一旦デフォルトのGETで送信することにします。
FlaxというテンプレートエンジンがこのhtmlをJuliaに変換してbuild/FlaxViews/ディレクトリに格納するようですが、エラーが出た後.jlファイルを見るとJuilaの文法的におかしい変換になっていることがあり、それでエラーになってしまっている感じです。まだFlaxに不具合があるかもしれません。一旦htmlもシンプルにタグを少なくします。

new.jl.htmlの修正(methodを消してさらにシンプルにする)。

app/resources/books/views/new.jl.html
<form action="$(Genie.Router.link_to(:create_task))">
  内容:<input type="text" name="task_content" /></br>
  <input type="submit" value="作成" />
</form>

再度、
http://localhost:8000/tasks/new
にアクセスします。

スクリーンショット 2018-11-06 23.18.05.png

無事?表示されました。
methodを消したのでroutes.jlのmethodをPOSTからGETに修正しておきます。
また、登録後のリダイレクトを一覧表示画面にしたいので、/tasksget_tasksという名前を付けます。ついでにtasks/newにも名前を付けます。

config/routes.jl
(略)
route("/tasks", TasksController.index, named = :get_tasks) # namedを追加
route("/tasks/new", TasksController.new, named = :new_task) # namedを追加
route("/tasks/create", TasksController.create, method = GET, named = :create_task) # GETに修正

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

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

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

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

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

スクリーンショット 2018-11-06 23.55.48.png

内容に適当に(GETなので短めの)文字列を入れて[作成]ボタンをクリックします。

スクリーンショット 2018-11-06 23.55.56.png

一覧画面にリダイレクトされ入力した内容が表示されています!

では、一覧画面から作成画面に遷移できるようにリンクを付けます。(1行目に追加)

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

スクリーンショット 2018-11-07 0.09.59.png

タスク更新(未完成です)

最後にタスクの更新を作ります。
チュートリアルには更新処理(update)に関するサンプルがなく、いろいろ試したのですがエラーが出まくって解決できなく挫けました。参考までに試した方法だけ載せておきます。

ルートの追加

routes.jlに下記を追加します。(ここも一応GET)

config/routes.jl
route("/tasks/update", TasksController.update, method = GET, named = :update_task)

コントローラーの修正

TasksController.jlに下記のfunctionを追加します。

app/resources/tasks/TasksController.jl
function update()
  Tasks.Task(id = @params(:task_id), content = @params(:task_content), done = @params:(:task_done)) |> save && redirect_to(:get_tasks)
end

ビューの修正

タスク一覧に[完了]と[未完了に戻す]のリンクを付けました。

app/resources/tasks/views/tasks.jl.html
<a href="$(Genie.Router.link_to(:new_task))">作成</a>
<ul>
<%
  if length(@vars(:tasks)) == 0
    "タスクがありません。"
  else
    @foreach(@vars(:tasks)) do task
      if task.done
      "<li><s>$(task.content)</s><a href=" * Genie.Router.link_to(:update_task) * "?task_id=" * string(get(task.id)) * "&task_done=false" * ">&nbsp未完了に戻す</a></li>"
      else
      "<li>$(task.content)<a href=" * Genie.Router.link_to(:update_task) * "?task_id=" * string(get(task.id)) * "&task_done=true" * ">&nbsp完了</a></li>"
      end
    end
  end
 %>
</ul>

下記はデータベースに直接完了済のデータを入れて表示した状態です。

スクリーンショット 2018-11-10 22.34.40.png

ここの[完了]リンクをクリックすると下記のリンクにパラメータが飛んで更新するという仕組みを想定していました。
http://localhost:8000/tasks/update

エラー

画面には「false」とだけ出るので、genie>のプロンプトで下記を試したところUndefVarError(:s)というエラーで確かにfalseが返ってきました。

genie> using SearchLight
genie> using Tasks
genie> Tasks.Task(id = 2, done = true) |> save
[2018-11-10 22:42:42|warn]: UndefVarError(:s)
genie> false

save関数だとupdateしないと思いSearchLight.jlのソースを見るとupdate_by_or_create!!update_with!などの関数が見つかったのでいろいろ試したのですが、エラーの解決方法分からず一旦断念しました。(まだJulia自体がきちんと読めない・・・)

感想

公式チュートリアルを少し変形しただけですが、もとのサンプルにないことを書こうとするとJuliaの基本を知る必要があり勉強になりました。Genieのエラーが出てもまだまだ情報も少なく、解決するまでずいぶん時間を要しました。
Genieのissueを検索したら同じようなエラーで質問している方もいてそこで解決したこともあります。私もどうしても解決できなくてissueで質問したところ作者からすぐにリプライが来ました。(GitHubのissueではあまり質問しないほうがいいという記事を後で見つけました。質問に親切にリプライしてくれた作者に感謝です。)
結果的に更新処理は未完成のままなのでもう少し勉強して解決したいと思います。
Julia自体だけでなくGenieももっと流行って欲しいですね!

参考

2018/9/24に英語版ですがJulia 1.0に対応した書籍も発売になっています。Kindle版を買ってみたところ一通り基本文法は学べそうな感じです。
Julia 1.0 Programming: Dynamic and high-performance programming to build fast scientific applications, 2nd Edition (English Edition)

24
22
0

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
24
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?