この記事の内容は古いです。続編を書きましたのでそちらをご参照ください。
(続)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/
にアクセしてみます。
起動しています!
プロンプトが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
を作成します。ダミーのタスクを設定します。
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部分だけ書きます。
<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
にタスク一表示用ルートを追加します。
using Genie.Router
using TasksController # 追加
route("/") do
serve_static_file("/welcome.html")
end
# 追加(タスク表示)
route("/tasks", TasksController.index)
http://localhost:8000/tasks
にアクセスします。
表示されました!
次に、データベースをセットアップし、サンプル表示ではなく、タスクの表示・新規作成・更新を行えるように修正してきます。
機能作成(表示・新規作成・更新)
パッケージ追加
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
というデータベース設定ファイルがあらかじめあるのでそれを編集します。
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
ファイルを修正します。
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
チュートリアルのBook
はstring
だけだったので、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の部分です。)
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
を追加します。最初にサンプルで埋め込んだものと同じだとわからなくなるので別のデータにします。
# 追加
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
を修正します。
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
にアクセスします。
表示されました!
-
TasksController.jl
のSearchLight.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
のコメントアウトを外せというもの。
ファイルの#=
と=#
を削除します。
# 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
ルートの追加
routes.jl
に下記を追加します。
# 追加
route("/tasks/new", TasksController.new)
route("/tasks/create", TasksController.create, method = POST, named = :create_task)
コントローラーの修正
TasksController.jl
に下記のfunctionを追加します。create()
の処理は後で書きます。
function new()
html!(:tasks, :new)
end
function create()
end
追加用ビューの作成
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を消してさらにシンプルにする)。
<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
にアクセスします。
無事?表示されました。
methodを消したのでroutes.jl
のmethodをPOSTからGETに修正しておきます。
また、登録後のリダイレクトを一覧表示画面にしたいので、/tasks
にget_tasks
という名前を付けます。ついでにtasks/new
にも名前を付けます。
(略)
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
も追加します。
using Genie.Router # 追加
# 修正
function create()
Tasks.Task(content = @params(:task_content), done = false) |> save && redirect_to(:get_tasks)
end
http://localhost:8000/tasks/new
にアクセスします。
内容に適当に(GETなので短めの)文字列を入れて[作成]ボタンをクリックします。
一覧画面にリダイレクトされ入力した内容が表示されています!
では、一覧画面から作成画面に遷移できるようにリンクを付けます。(1行目に追加)
<a href="$(Genie.Router.link_to(:new_task))">作成</a>
<ul>
(略)
タスク更新(未完成です)
最後にタスクの更新を作ります。
チュートリアルには更新処理(update)に関するサンプルがなく、いろいろ試したのですがエラーが出まくって解決できなく挫けました。参考までに試した方法だけ載せておきます。
ルートの追加
routes.jl
に下記を追加します。(ここも一応GET)
route("/tasks/update", TasksController.update, method = GET, named = :update_task)
コントローラーの修正
TasksController.jl
に下記のfunctionを追加します。
function update()
Tasks.Task(id = @params(:task_id), content = @params(:task_content), done = @params:(:task_done)) |> save && redirect_to(:get_tasks)
end
ビューの修正
タスク一覧に[完了]と[未完了に戻す]のリンクを付けました。
<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" * "> 未完了に戻す</a></li>"
else
"<li>$(task.content)<a href=" * Genie.Router.link_to(:update_task) * "?task_id=" * string(get(task.id)) * "&task_done=true" * "> 完了</a></li>"
end
end
end
%>
</ul>
下記はデータベースに直接完了済のデータを入れて表示した状態です。
ここの[完了]リンクをクリックすると下記のリンクにパラメータが飛んで更新するという仕組みを想定していました。
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)