著者: msky
この章では、 Crystal を用いて Web 開発を行う方法について解説します。
まず、 Crystal では HTTP でのサービスをどのように処理するかについて解説します。 前の章でも既に解説済みですが、改めて Crystal での Web 開発の基本となる部分について解説します。
以下のコードで解説します。
link:./examples/server.cr[role=include]
ポート 8080 での HTTP アクセスを Listen する最小のプログラムです。
以下のコードを実行することで簡易 HTTP サーバとして機能します。
localhost:8080 にアクセスすると Hello world
が表示されます。
$ crystal server.cr
Listening on http://0.0.0.0:8080
基本的に Crystal の HTTP サーバが返却するパラメータは レスポンス本文、ヘッダ、ステータスコードの3種です。
その3種を持っている限りにおいて、処理をスタックすることが出来ます。
この辺りの規約は Ruby でいうところの Rack に似ているといえます。
HTTP ハンドラを実装するには Rack 同様に call メソッドを定義し、引数に HTTP::Server::Context
型のオブジェクトを渡すという形式になっています。
以下に例を記載します。
link:./examples/http_handler.cr[role=include]
上記の例で言いますと、 InterruptTestFirst
InterruptTestSecond
の順にスタックに積まれます。
実行されるのは先入れ後出しなので、後に積まれた InterruptTestSecond
からになります。
curl -X GET http://localhost:8080
を実行することで以下の内容がコンソールに出力されます。
bb
aa
主に Crystal で Web 開発を行う上での基本的な内容ですが、実際に開発する場合は何らかの Web フレームワークを用いることになるかと思います。 次項から Web フレームワークについて解説します。
Crystal で現在最も活発に開発されている Web フレームワークが Kemal です。 Ruby でいうところの Sinatra に似た設計思想で作られており、シンプルな実装で Web アプリを作成することが出来ます。 本書では主に Kemal を用いて簡単な Web アプリを作成しながら Web 開発の方法を解説していきます。
- Web サイト
- GitHub
また Kemal 以外の主なフレームワークとして以下のものがあります。
フルスタックの Web フレームワークです。 様々なコードジェネレータや、 OR/M を備えています。 Rails 的な規約重視のフレームワークです。
それでは本章から Kemal による Web 開発を、サンプルを交えながら説明します。
また本サンプルに使用する Kemal のバージョンは 0.24.0
を想定しています。
適当な作業用のディレクトリ以下で、以下のコードを実行してみます。ディレクトリが作成され幾つかのファイルがひな形から作成されます。 本稿ではサンプルアプリを kemal-sample として進めます。
$ crystal init app kemal-sample
$ cd kemal-sample
shard.yml ファイルに以下の内容を追記します。
link:./projects/kemal-sample/shard.yml[role=include]
以下のコマンドを実行することで Kemal 本体をインストールすることが出来ます。
$ shards install
インストールまで問題なく動かせたら続いて簡単なサンプルを作成してみましょう。
カレントの src ディレクトリ以下の kemal-sample.cr
に以下の追記を行います。
まず行頭に以下の行を足します。
require "kemal"
続いて module Kemal::Sample
内に以下の内容を追記します。
link:./projects/kemal-sample/src/kemal-sample.cr[role=include]
末尾の行に以下の内容を追記します。
Kemal.run
画面側の処理を作成します。
src
以下に views ディレクトリを作成し、以下の hello.ecr
ファイルを views
ディレクトリ内に作成します。
link:./projects/kemal-sample/src/views/hello.ecr[role=include]
Kemal の基本的な使い方として、 REST の動詞 GET POST PUT DELETE を以下の形で記述します。
get "/path" do |env|
# 処理
end
post "/path" do |env|
# 処理
end
詳しい内容については後述します。 先ずは記述したら、以下のコマンドでビルド、実行します。
$ crystal build src/kemal-sample.cr
$ ./kemal-sample
ブラウザで http://localhost:3000/hello
でアクセスします。
Hello World
と表示されていれば問題ありません。
通常、 Web アプリでは DB が必須です。 Kemal で作ったアプリから DB にアクセスすることも可能です。 ライブラリを使用することで PostgreSQL か、もしくは MySQL を使用することが出来ます。 今回は PostgreSQL を使用します。
カラム名 |
型 |
id |
serial |
title |
text |
content |
text |
DB を作成します。
$ createdb kemal_sample_development -O your_owner
DDL を作成しロードします。
link:./projects/kemal-sample/sql/create_articles.sql[role=include]
$ psql -U your_owner -d kemal_sample_development -f sql/create_articles.sql
作成した後、 shard.yml ファイルに以下の内容を追記します。
link:./projects/kemal-sample/shard.yml[role=include]
編集後、以下のコマンドを実行します。
$ shards install
これからいよいよ Web アプリらしく記事の一覧ページと詳細ページ、新規投稿ページをそれぞれ作成していきます。 まず全ページで共通で使用するテンプレートは別に作成します。 テンプレートヘッダに新規投稿ページと投稿リストページヘのリンクを表示し、ページ内に各ページのコンテンツを表示するように修正していきます。
まずひな形のページを application.ecr
という名前で src/views
ディレクトリ内に作成します。
link:./projects/kemal-sample/src/views/application.ecr[role=include]
本サンプルでは BootStrap を使用します。
CDN を使用しますので特にダウンロード不要ですが、ダウンロードする場合は別途 http://getbootstrap.com/
から必要なファイルをダウンロードし、プロジェクトカレントの /public
以下に配置してください。
ページ修飾用の CSS を custom.css
という名前で public/css
ディレクトリ内に作成します。本サンプルでは rails tutorial をそのまま参考にします。
link:./projects/kemal-sample/public/css/custom.css[role=include]
記事一覧ページを index.ecr
という名前で src/views
ディレクトリ内に作成します。
link:./projects/kemal-sample/src/views/index.ecr[role=include]
記事投稿用のページを new.ecr
という名前で src/views/articles
ディレクトリ内に新規作成します。
以下のファイルを追加します。
link:./projects/kemal-sample/src/views/articles/new.ecr[role=include]
一覧ページから記事タイトルをクリックした時に遷移する詳細ページを show.ecr
という名前で src/views/articles
ディレクトリ内に作成します。
データへの更新については後述します。
link:./projects/kemal-sample/src/views/articles/show.ecr[role=include]
kemal-sample.cr を以下の内容で修正します。
行頭を以下の内容に修正します。
require "kemal"
require "db"
require "pg"
続いて module Kemal::Sample
内に以下の内容を追記します。
link:./projects/kemal-sample/src/kemal-sample.cr[role=include]
修正は以上です。次項から処理について解説します。
まずクエリは以下のように記述します。
link:./projects/kemal-sample/src/kemal-sample.cr[role=include]
query メソッドに SQL クエリを記述し、ループ内で結果を格納していきます。
1件だけ取得したい場合は query_one
もしくは query_one?
を使います。後者は1件もデータが無い場合がありうる場合に使います。
sql = "select id, title, body from articles where id = $1::int8"
article["id"], article["title"], article["body"] =
db.query_one(sql, params, as: {Int32, String, String})
query_one
の戻り値は、 as
で指定した型の Tuple
になります。
パラメータを SQL 文に渡す場合は上記例で言うと
$1::int8
と、連番で指定していきます。 これは PostgreSQL の例です。 MySQL の場合は?で指定します。 指定できる型番は以下の形式です。
-
text
-
boolean
-
int8 int4 int2
-
float4 float8
-
timestamptz date timestamp
-
json and jsonb
-
uuid
-
bytea
-
numeric/decimal
-
varchar
-
regtype
-
geo types
-
array types: int8 int4 int2 float8 float4 bool text
更新系のクエリは以下のような記述で行います。 データの投入は以下のように行います。
db.exec("insert into articles(title, content) values($1::text, $2::text)", params)
SQL 文に update や delete も設定ができます。
View の構造はテンプレート形式で設定することが出来ます。
render "src/views/articles/show.ecr", "src/views/application.ecr"
の形式で ecr ファイルをレンダリングすることで、 Rails のようにファイルを入れ子の形で描画することが出来ます。
Web アプリ再起動後に http://localhost:3000/
にアクセスすると、まだ記事が投稿されていないので空欄になっています。
http://localhost:3000/articles/new
にアクセスすると、タイトルと本文を入力する画面が表示されており、入力後に一覧に遷移すると成功です。
記事タイトルをクリックすると詳細画面に遷移すると成功です。
Kemal は RESTful に対応しており、 GET POST 以外にも、 PUT DELETE にも対応しております。
記事の編集をサンプルに追加しながら説明します。
新規ページで src/views/articles/edit.ecr
を追加します。
データの編集画面
link:./projects/kemal-sample/src/views/articles/edit.ecr[role=include]
ブラウザによっては form が get post 以外には対応していない場合、以下の hidden フィールドの設定で PUT や DELETE で送信することが出来ます。
kemal-sample.cr
を以下の内容で修正します。
link:./projects/kemal-sample/src/kemal-sample.cr[role=include]
これで、データの追加から一覧表示、編集までの処理が行えるようになります。 また、 DELETE を追加する場合は以下のように行います。
delete "/articles/:id" do |env|
id = env.params.url["id"].to_i32
params = [] of Int32
params << id
db.exec("delete from articles where id = $1::int8", params)
db.close
env.redirect "/"
end
これでデータの作成、表示、更新、削除の機能を持ったアプリを作ることが出来ます。 本稿では省略しますが、セッションを用いて認証機能を追加することも出来ます。
本稿のコードでは取り上げませんでしたが、 Web アプリを書く上で注意すべき事項を以下に記載します。
データの原子性を保つ上で、トランザクション制御は必須です。 以下の設定でトランザクションのコミット、およびロールバックを設定します。
database_url = "postgres://localhost:5432/kemal_sample_development"
db = DB.open(database_url)
db.transaction do |tx|
result = tx.connection.exec("insert into articles(title, content) values('hoge', 'huga')")
# エラー時
if !result
tx.rollback
else
tx.commit
end
end