このガイドでは、clails を使って簡単な TODO 管理アプリケーションを作成します。 データベースの作成、モデル・ビュー・コントローラーの実装、そして Web アプリケーションの起動までを順を追って説明します。
- Roswell がインストールされていること
- clails がインストールされていること
インストール方法については README.md を参照してください。
- Docker がインストールされていること
- Docker Compose がインストールされていること
まず、clails new コマンドで新しいプロジェクトを作成します。
clails new todoapp
cd todoappこれにより、clails アプリケーションの基本構造が作成されます。
プロジェクト作成時に以下のファイルが生成されます:
Makefile- Docker 環境での開発を簡単にするためのコマンド集docker/Dockerfile.dev- 開発用の Docker イメージ定義docker/docker-compose.dev.yml- Docker Compose 設定docker/dev.env- 開発環境の環境変数設定
make buildclails のブランチやタグを指定する場合は CLAILS_BRANCH を指定します (未指定の場合は develop が指定されます) 。
# branch
CLAILS_BRANCH=release/0.0.2 make build
# tag
CLAILS_BRANCH=v0.0.1 make buildイメージのビルドが完了したら、開発環境の準備は完了です。
make upコンテナがバックグラウンドで起動します。
Docker 環境では、make コマンドを使ってデータベース操作を実行します。
make db.createデフォルトでは SQLite3 データベースが作成されます。
MySQL や PostgreSQL を使用する場合は、プロジェクト作成時に --database オプションを指定してください。
利用可能な Make コマンド:
make build- Docker イメージをビルドmake rebuild- キャッシュを使わずに Docker イメージを再ビルドmake up- コンテナを起動make down- コンテナを停止make console- コンテナ内でシェルを起動make logs- アプリケーションのログを表示make db.create- データベースを作成make db.migrate- マイグレーションを実行make db.rollback- マイグレーションをロールバックmake db.seed- シードデータを投入
clails db:create コマンドでデータベースを作成します。
clails db:createデフォルトでは SQLite3 データベースが作成されます。
MySQL や PostgreSQL を使用する場合は、プロジェクト作成時に --database オプションを指定してください。
clails generate:scaffold コマンドで、Model、View、Controller を一括生成します。
コンテナ内でシェルを起動して実行します:
make console
# コンテナ内で
clails generate:scaffold todo
exitclails generate:scaffold todoこのコマンドにより、以下のファイルが生成されます:
app/models/todo.lisp- Model ファイルapp/views/todo/list.html- View ファイルapp/controllers/todo-controller.lisp- Controller ファイルdb/migrate/YYYYMMDD-HHMMSS-todo.lisp- Migration ファイルtest/models/todo.lisp- Model のテストファイルtest/controllers/todo-controller.lisp- Controller のテストファイル
生成された Migration ファイルを編集して、TODO テーブルの構造を定義します。
db/migrate/YYYYMMDD-HHMMSS-todo.lisp を開き、以下のように修正します:
(in-package #:todoapp-db)
(defmigration "todo"
(:up #'(lambda (connection)
(create-table connection :table "todo"
:columns '(("title" :type :string
:not-null T)
("done" :type :boolean
:default NIL)
("done-at" :type :datetime))))
:down #'(lambda (connection)
(drop-table connection :table "todo"))))このテーブル定義では:
title- TODO のタイトル(必須項目)done- 完了フラグ(デフォルト: false)done_at- 完了日時
が定義されています。
Migration ファイルを修正したら、clails db:migrate コマンドでテーブルを作成します。
make db.migrateclails db:migrateこれにより、データベースに TODO テーブルが作成されます。
app/models/todo.lisp を開き、TODO アプリに必要な機能を追加します。
(in-package #:cl-user)
(defpackage #:todoapp/models/todo
(:use #:cl
#:clails/model)
(:import-from #:clails/datetime
#:from-universal-time
#:format-datetime)
(:import-from #:local-time
#:now)
(:export #:<todo>
#:find-all
#:create-todo
#:find-by-id
#:mark-as-done
#:format-done-at))
(in-package #:todoapp/models/todo)
(defmodel <todo> (<base-model>) (:table "todo"))
(defun find-all ()
"Find all todo items.
@return [list] List of todo records
"
(let ((q (query <todo> :as :todo)))
(execute-query q nil)))
(defun create-todo (title)
"Create a new todo item with the given title.
@param title [string] Todo title
@return [<todo>] Created todo record
"
(let ((todo (make-record '<todo> :title title :done nil)))
(save todo)
todo))
(defun find-by-id (id)
"Find a todo item by ID.
@param id [integer] Todo ID
@return [<todo>] Todo record
@return [nil] NIL if not found
"
(let* ((q (query <todo> :as :todo :where (:= (:todo :id) :id-param)))
(results (execute-query q (list :id-param id))))
(car results)))
(defun mark-as-done (todo)
"Mark a todo item as done.
@param todo [<todo>] Todo record to mark as done
@return [<todo>] Updated todo record
"
(setf (ref todo :done) t)
(setf (ref todo :done-at) (now))
(save todo)
todo)
(defmethod format-done-at ((todo <todo>))
"Format the done-at timestamp of a todo item.
@param todo [<todo>] Todo record
@return [string] Formatted timestamp in MySQL format (yyyy-mm-dd hh:mm:ss)
@return [nil] NIL if done-at is not set
"
(let ((done-at (ref todo :done-at)))
(when done-at
(format-datetime (from-universal-time done-at)
:format :mysql))))このコードでは以下の機能を実装しています:
find-all- すべての TODO を取得create-todo- 新しい TODO を作成find-by-id- ID で TODO を検索mark-as-done- TODO を完了としてマークformat-done-at- TODO の DONE-AT があれば MySQL 形式の文字列(yyyy-mm-dd hh:mm:ss)で返す
app/views/todo/package.lisp を開き、 View で使用するメソッドを宣言します。
(in-package #:cl-user)
(defpackage #:todoapp/views/todo/package
(:use #:cl)
(:import-from #:clails/view/view-helper
#:*view-context*
#:view)
(:import-from #:todoapp/models/todo
#:format-done-at))
(in-package #:todoapp/views/todo/package)app/views/todo/list.html を開き、TODO の一覧表示と操作ができるように修正します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Todo List</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.done { text-decoration: line-through; color: #999; }
form { margin-top: 20px; }
input[type="text"] { padding: 5px; width: 300px; }
button { padding: 5px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:hover { background-color: #45a049; }
</style>
</head>
<body>
<h1>Todo List</h1>
<h2>Add New Todo</h2>
<form action="/todo" method="POST">
<input type="text" name="title" placeholder="Enter todo title" required>
<button type="submit">Add</button>
</form>
<h2>Todo Items</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Status</th>
<th>Done At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<cl:loop for="todo" in="(view :todos)">
<tr>
<td><%= (clails/model:ref todo :id) %></td>
<td class="<%= (if (clails/model:ref todo :done) "done" "") %>">
<%= (clails/model:ref todo :title) %>
</td>
<td><%= (if (clails/model:ref todo :done) "Done" "Pending") %></td>
<td><%= (or (format-done-at todo) "-") %></td>
<td>
<cl:unless test="(clails/model:ref todo :done)">
<form action="/todo" method="POST" style="display:inline;">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="id" value="<%= (clails/model:ref todo :id) %>">
<button type="submit">Mark as Done</button>
</form>
</cl:unless>
</td>
</tr>
</cl:loop>
</tbody>
</table>
<p style="margin-top: 20px; color: #666;">
Total: <%= (length (view :todos)) %> items
</p>
</body>
</html>このビューでは:
- TODO の追加フォーム
- TODO の一覧表示(テーブル形式)
- TODO を完了としてマークするボタン
- 完了した TODO の取り消し線表示
が実装されています。
HTML フォームで PUT を実現する方法:
HTML の <form> タグは GET と POST メソッドしかサポートしていません。
そのため、PUT や DELETE リクエストを送信するには、以下のように実装します:
<form action="/todo/123" method="POST">
<input type="hidden" name="_method" value="PUT">
<button type="submit">Mark as Done</button>
</form>clails は POST リクエスト内の _method パラメータをチェックし、値が "PUT" の場合は do-put メソッドに、"DELETE" の場合は do-delete メソッドにルーティングします。これにより、HTML フォームから PUT や DELETE リクエストを実現できます。
app/controllers/todo-controller.lisp を開き、リクエストを処理するように修正します。
(in-package #:cl-user)
(defpackage #:todoapp/controllers/todo-controller
(:use #:cl
#:clails/controller/base-controller)
(:import-from #:clails/controller/base-controller
#:param)
(:import-from #:todoapp/models/todo
#:find-all
#:create-todo
#:find-by-id
#:mark-as-done)
(:export #:<todo-controller>))
(in-package #:todoapp/controllers/todo-controller)
(defclass <todo-controller> (<web-controller>)
())
(defmethod do-get ((controller <todo-controller>))
"Get all todo items and display them."
(let ((todos (find-all)))
(set-view controller "todo/list.html" (list :todos todos))))
(defmethod do-post ((controller <todo-controller>))
"Create a new todo item."
(let ((title (param controller "title")))
(create-todo title)
(set-redirect controller "/todo")))
(defmethod do-put ((controller <todo-controller>))
"Update todo item to mark as done."
(let* ((id-str (param controller "id"))
(id (parse-integer id-str))
(todo (find-by-id id)))
(when todo
(mark-as-done todo))
(set-redirect controller "/todo")))このコントローラーでは:
do-get- TODO 一覧を表示do-post- 新しい TODO を作成do-put- TODO を完了としてマーク
が実装されています。
すべての実装が完了したら、サーバーを起動します。
Docker 環境では、make up でコンテナを起動すると自動的にサーバーが起動します。
make upサーバーのログを確認するには:
make logsコンテナを停止するには:
make downclails serverデフォルトでは http://localhost:5000 でサーバーが起動します。
ブラウザで http://localhost:5000/todo にアクセスします。
以下の操作ができます:
- TODO の追加: "Add New Todo" フォームにタイトルを入力して "Add" ボタンをクリック
- TODO の一覧表示: 登録されているすべての TODO が表示されます
- TODO の完了: "Mark as Done" ボタンをクリックすると TODO が完了状態になります
初期データを追加したい場合は、db/seeds.lisp を作成します。
(in-package #:cl-user)
(defpackage #:todoapp-db
(:use #:cl)
(:import-from #:clails/model
#:save
#:make-record)
(:import-from #:todoapp/models/todo
#:<todo>))
(in-package #:todoapp-db)
(defun run ()
"Create seed data for todo table."
(let ((todo1 (make-record '<todo> :title "Buy milk" :done nil))
(todo2 (make-record '<todo> :title "Read a book" :done nil))
(todo3 (make-record '<todo> :title "Write code" :done nil)))
(save todo1)
(save todo2)
(save todo3)
(format t "Created 3 todo items~%")))シードデータを読み込むには:
make db.seedclails db:seedこのクイックスタートでは、clails を使った TODO アプリケーションの作成方法を学びました。
- プロジェクトの作成(
clails new) - Docker イメージのビルド(
make build) - Docker コンテナの起動(
make up) - データベースの作成(
make db.create) - Scaffold の生成(コンテナ内で
clails generate:scaffold) - Migration ファイルの編集
- Migration の実行(
make db.migrate) - Model の実装
- View の実装
- Controller の実装
- サーバーの起動(
make upで自動起動)
- プロジェクトの作成(
clails new) - データベースの作成(
clails db:create) - Scaffold の生成(
clails generate:scaffold) - Migration ファイルの編集
- Migration の実行(
clails db:migrate) - Model の実装
- View の実装
- Controller の実装
- サーバーの起動(
clails server)
これらのステップを応用することで、より複雑な Web アプリケーションを作成できます。
- 開発環境の構築が簡単(Docker さえあれば OK)
- チーム全体で同じ環境を共有できる
- データベース(MySQL や PostgreSQL)も自動的にセットアップされる
- ホスト環境を汚さない
Makefileにより、よく使うコマンドを簡単に実行できる
- Model Guide - データベース操作とクエリの詳細
- View Guide - テンプレートエンジンの詳細
- Controller Guide - リクエスト処理とルーティングの詳細
- Testing Guide - テストの書き方