t-cool

Utopian手習い #01

Utopian手習い

Common LispのWebフレームワーク Utopianを紹介します。

本記事では、サンプルを元に、ブログ記事を表示するところまで行います。

1. 準備

Common Lispの環境構築にはroswell、エディタにはlemを使います。

LinuxmacOSにroswellとlemをインストール後、読み進めてください。

まずは、Utopianをインストールしましょう。

ros install fukamachi/utopian
cd ~/.roswell/local-projects/fukamachi/utopian/

以上で、準備は完了です。

  1. 閲覧ページの実装

ブログのファイル構成

では、ブログの開発を始めましょう。まず、ブログのディレクトリに移動します。

cd ~/.roswell/local-projects/fukamachi/utopian/example/

ソースファイル群の構成を確認しましょう。

$ tree
.
├── Makefile
├── app.lisp
├── config
│   └── environments
│       └── local.lisp
├── db
│   ├── migrations
│   ├── myblog.db
│   └── schema.sql
├── models.lisp
├── myblog.asd
├── routes.lisp
└── views.lisp

makeコマンド用にMakefile、システムファイルとしてmyblog.asd、全体用にapp.lisp、モデル用にmodels.lisp、ビュー用にviews.lisp、ルーティング用にroutes.lisp、環境設定関連はconfigフォルダにファイルが配置されています。

では、Makefileから見ていきます。

all: server

server:
    ros -s myblog -e '(utopian/tasks:server myblog/app:blog-app)'

generate-migrations:
    ros -s myblog -e '(utopian/tasks:generate-migrations myblog/app:blog-app)'

migrate:
    ros -s myblog -e '(utopian/tasks:migrate myblog/app:blog-app)'

make serverでアプリケーションの起動、make generate-migrationsマイグレーションスクリプトの生成、make generate-migrateマイグレーションを実行してデータベースを更新します。

アプリケーションの起動

では、make serverコマンドで、アプリケーションを起動しましょう。

$ make server
ros -s myblog -e '(utopian/tasks:server myblog/app:blog-app)'
Hunchentoot server is going to start.
Listening on localhost:5000.

ブラウザを開いてhttp://localhost:5000にアクセスすると、次のように、初期画面が確認できます。

f:id:tcooooooool:20180914201619p:plain

f:id:tcooooooool:20180914201625p:plain

モデルの更新

では、models.lispを編集して、本文(content)を追加できるようにしましょう。

$ lem models.lisp

(defpackage #:myblog/models
  (:use #:cl
        #:utopian)
  (:export #:entry
           #:entry-title
       #:entry-content))  ; <= entry-contentでDBにアクセスできるように、シンボルをエクスポートする。
(in-package #:myblog/models)

(defmodel entry ()
  ((title :col-type :text)
   (content :col-type (or :text :null); <= entry-contentでDBにアクセスできるように、シンボルをエクスポートする。
            :initform "BODY")))

(defmodel entryのS式が、記事用のモデルです。

defmodelは、defclassの形式に沿って定義されています。

title(記事タイトル)とcontent(本文)のカラム型には文字列を指定しています。

  ((title :col-type :text)
   (content :col-type (or :text :null)
            :initform "BODY"))

マイグレーション

モデルを変更したあとは、make generate-migrationsmake migrateを実行して、データベースを更新します。

make generate-migrations
make migrate

ビューの更新

ユーザが操作をする画面(ビュー)に本文が追加できるように、views.lispを編集します。

entryのbodyの要素に(p (entry-body entry))を追加することで、本文を挿入できるようになります。

$ lem views.lisp

(defview entry ()
  (entry)  ; <== entryモデルからDBの情報をとってきます
  (:render
   (html
    (head
     (title (format nil "~A | Myblog" (entry-title entry))))
    (body
     (h1 (entry-title entry))
     (p "Entry page!")
     (p (entry-body entry))))))  ; <== この行を追加します。

ルーティングの更新

routes.lispで、ルーティングを変更します。

$ lem routes.lisp

(defpackage #:myblog/routes
  (:use #:cl
        #:utopian
        #:myblog/views
        #:myblog/models)
  (:import-from #:assoc-utils
                #:aget)
  (:export #:index
           #:entries
           #:entry))
(in-package #:myblog/routes)

;; トップ画面 (http://localhost:5000)
(defroute index ()
  (render))

;; 記事の一覧を表示する (http://localhost:5000/entries)
(defroute entries ()
  (render :entries (mito:select-dao 'entry)))

;; 個別に記事を表示する (http://localhost:5000/entry/記事の番号)
(defroute entry (params)
  (render :entry (mito:find-dao 'entry :id (aget params :id))))

app.lispの更新

app.lispで、アプリケーション全体の設定をします。

$ lem app.lisp

(defpackage #:myblog/app
  (:use #:cl
        #:utopian
        #:myblog/routes)
  (:export #:blog-app))
(in-package #:myblog/app)

(defapp blog-app
  ((:GET "/" #'index) ; http://localhost:5000/ へのリクエストを、index Viewに渡す。
   (:GET "/entries" #'entries) ; http://localhost:5000/entries へのリクエストを、entries Viewに渡す。
   (:GET "/entries/:id" #'entry)) ; http://localhost:5000/entries/記事の番号へのリクエストを、entry Viewに渡す。
   
   (:config #P"config/environments/"))

開発環境の切り替え

config/environments/local.lispで、開発段階の設定をします。今回の開発段階では、SQLite3を使います。

lem config/environments/local.lisp
(defpackage #:myblog/config/environments/local
  (:use #:cl))
(in-package #:myblog/config/environments/local)

'(:database (:sqlite3
             :database-name #P"db/myblog.db"))

DBの更新

今回は、まだ投稿用のコマンドを実装していないので、DB Browser for SQLite等のDBエディタでdb/myblog.dbを編集します。

f:id:tcooooooool:20180914201628p:plain

ページの確認

最後に、ページの変更を確認しましょう。

f:id:tcooooooool:20180914201632p:plain

今回は、これで終わりです。次回は、新しく記事を追加する機能を実装していきます。 f:id:tcooooooool:20180914201619p:plainf:id:tcooooooool:20180914201625p:plainf:id:tcooooooool:20180914201628p:plainf:id:tcooooooool:20180914201632p:plain