Common Lisp – destructuring-bind
元の記事はこちらです:
Common Lisp – destructuring-bind / Kyle Burton著
Common Lisp – destructuring-bind
リストの分解
;; これが定番の使い方です。リストを分解します。 (destructuring-bind (first second) '(1 2) (format t "first:~A second:~A ~&" first second)) ;;; => first:1 second:2
ドット表記
;; ドット表記のリストも分解できます。 (destructuring-bind (first . second) '(1 . 2) (format t "first:~A second:~A ~&" first second)) ;;; => first:1 second:2
ドット表記で残りの引数を全て捕捉
;; destructuring-bindの第一引数はラムダリストですが、 ;; ドット記法を使うと、残り全ての引数を捕捉することができます。 (destructuring-bind (first second . stuff) '(1 2 3 4 5) (format t "first:~A second:~A rest:~A ~&" first second stuff)) ;;; => first:1 second:2 rest:(3 4 5)
&restで残りの引数を全て捕捉
;; また、レスト引数を使って残り全ての引数を捕捉することもできます。 (destructuring-bind (first second &rest stuff) '(1 2 3 4 5) (format t "first:~A second:~A rest:~A ~&" first second stuff)) ;;; => first:1 second:2 rest:(3 4 5)
&optionalでデフォルト値を設定
;; オプショナル引数を使って、引数にデフォルト値を設定することもできます。 (destructuring-bind (first second &optional (third 'default)) '(1 2) (format t "first:~A second:~A third:~A ~&" first second third)) ;;; => first:1 second:2 third:DEFAULT (destructuring-bind (first second &optional (third 'default)) '(1 2 3) (format t "first:~A second:~A third:~A ~&" first second third)) ;;; => first:1 second:2 third:3
キーワード引数の利用
;; また、キーワード引数を使うこともできます。 (destructuring-bind (first second &key third) '(1 2 :third 3) (format t "first:~A second:~A third:~A ~&" first second third)) ;;; => first:1 second:2 third:3
木構造を逆パース
;; 最後に、木構造を逆パース(unparse)するためにも使うことができます。 ;; 分解したいデータ構造に対して、あなた自身の変数宣言を適用することができます。 ;; このテクニックは、XMLをS式に変換した後、データを処理する際に役立ちます。 (destructuring-bind (a (b (c d e (f g) h i j)) &rest remainder) '(1 (2 (3 4 5 (6 7) 8 9 10)) 11 12 13 14 15) (format t "a:~A b:~A c:~A d:~A e:~A f:~A g:~A h:~A i:~A j:~A remainder:~A ~&" a b c d e f g h i j remainder)) ;;; => a:1 b:2 c:3 d:4 e:5 f:6 g:7 h:8 i:9 j:10 remainder:(11 12 13 14 15)
Caveman2 - README 訳
Caveman(2018年8月14日時点)のREADMEの和訳です。READMEの更新に合わせて、こちらの和訳も更新します。
Caveman2 - 軽量なWebアプリケーションフレームワーク
利用方法
(defparameter *web* (make-instance '<app>)) @route GET "/" (defun index () (render #P"index.tmpl")) @route GET "/hello" (defun say-hello (&key (|name| "Guest")) (format nil "Hello, ~A" |name|))
Caveman2とは?
Caveman1との相違点
Caveman2は、ゼロから書き直しました。
重要な点は、次の通りです:
ゼロから書き直した理由
「ningleとCaveman、どちらを使うべきですか?」
「違いは何ですか?」
と聞かれることがよくありました。
両者の役割が似ていることが原因だったと思います。
どちらも小さなフレームワークであり、データベースをサポートしています。
Caveman2は、小さなWebフレームワークではありません。CL-DBIをサポートし、デフォルトでデータベース接続を管理します。
デザインの目標
Cavemanは、Webアプリケーションの開発で共通する部品を集めたコレクションです。
Cavemanは、3つのルールのもと、開発されました。
- 拡張できること
- 実用的であること
- 何も強要しないこと
はじめに
あなたがここにきたということは、Caveman(洞窟男)のように暮らすことに、興味があるのでしょうか?
洞窟にはディズニーランドはありませんが、始めるには良い場所です。
さて、洞窟に入りましょう。
インストール
現在、Caveman2はQuicklispで入手できます。
(ql:quickload :caveman2)
プロジェクトのテンプレートを生成する
(caveman2:make-project #P"/path/to/myapp/" :author "<Your full name>") ;-> writing /path/to/myapp/.gitignore ; writing /path/to/myapp/README.markdown ; writing /path/to/myapp/app.lisp ; writing /path/to/myapp/db/schema.sql ; writing /path/to/myapp/shlyfile.lisp ; writing /path/to/myapp/myapp-test.asd ; writing /path/to/myapp/myapp.asd ; writing /path/to/myapp/src/config.lisp ; writing /path/to/myapp/src/db.lisp ; writing /path/to/myapp/src/main.lisp ; writing /path/to/myapp/src/view.lisp ; writing /path/to/myapp/src/web.lisp ; writing /path/to/myapp/static/css/main.css ; writing /path/to/myapp/t/myapp.lisp ; writing /path/to/myapp/templates/_errors/404.html ; writing /path/to/myapp/templates/index.tmpl ; writing /path/to/myapp/templates/layout/default.tmpl
ルーティング
Caveman2は、ルーティングを定義するために、2通りの方法を提供しています。
@route
かdefroute
、どちらを使うかは、あなた次第です。
@route
は、アノテーションのマクロであり、cl-annotを用いています。
「メソッド」 「URL文字列」 「関数」 を受け取ります。
@route GET "/" (defun index () ...) ;; 無名のroute @route GET "/welcome" (lambda (&key (|name| "Guest")) (format nil "Welcome, ~A" |name|))
@route
は、引数のリスト以外は、Caveman1の@url
と似ています。
必要がない場合は、引数を指定する必要はありません。
defroute
は単なるマクロであり、@route
と同じ機能を提供します。
(defroute index "/" () ...) ;; 無名のroute (defroute "/welcome" (&key (|name| "Guest")) (format nil "Welcome, ~A" |name|))
Cavemanは、ningleを元に作られているので、Sinatraのようなルーティングも使えます。
;; GET request (default) @route GET "/" (lambda () ...) (defroute ("/" :method :GET) () ...) ;; POST request @route POST "/" (lambda () ...) (defroute ("/" :method :POST) () ...) ;; PUT request @route PUT "/" (lambda () ...) (defroute ("/" :method :PUT) () ...) ;; DELETE request @route DELETE "/" (lambda () ...) (defroute ("/" :method :DELETE) () ...) ;; OPTIONS request @route OPTIONS "/" (lambda () ...) (defroute ("/" :method :OPTIONS) () ...) ;; For all methods @route ANY "/" (lambda () ...) (defroute ("/" :method :ANY) () ...)
ルートパターンには、値を引数に入れるために、キーワードを含むことができます。
(defroute "/hello/:name" (&key name) (format nil "Hello, ~A" name))
上のコントローラーでは、 "/hello/Eitaro"にアクセスがきたときname
は"Eitaro"になり、"/hello/Tomohiro"にアクセスがきたときname
は"Tomohiro"になります。
(&key name)
はCommon Lispのラムダリストとほぼ同じですが、他のkeyを含むこともできます。
(defroute "/hello/:name" (&rest params &key name) ;; ... )
ルートパターンには、ワイルドカード引数を含めることもできます。splat
でアクセス可能です。
(defroute "/say/*/to/*" (&key splat) ; /say/hello/to/world にマッチします splat ;=> ("hello" "world") )) (defroute "/download/*.*" (&key splat) ; /download/path/to/file.xml にマッチします splat ;=> ("path/to/file" "xml") ))
URLのルールに正規表現を使うときには、:regexp t
を明記してください。
(defroute ("/hello/([\\w]+)" :regexp t) (&key captures) (format nil "Hello, ~A!" (first captures)))
通常、ルート(routes)は、定義された順番通りにマッチします。
はじめにマッチしたルートが呼びだされ、残りは無視されます。
next-route
を使うことで、次にマッチするルートに処理を渡すことができます。
(defroute "/guess/:who" (&key who) (if (string= who "Eitaro") "You got me!" (next-route))) (defroute "/guess/*" () "You missed!")
defroute
の結果として、次のフォーマットを返すことができます。
- 文字列
- パス名
- Clackのレスポンスのリスト(Status、Headers、Bodyを含みます)
クエリー問い合わせ/POSTパラメータ
角括弧"[" & "]"
を含むパラメータのキーは、クエリー問い合わせとしてパースされます。
ルータ(routers)で_parsed
としてパースされたパラメータにアクセスすることができます。
<form action="/edit"> <input type="name" name="person[name]" /> <input type="name" name="person[email]" /> <input type="name" name="person[birth][year]" /> <input type="name" name="person[birth][month]" /> <input type="name" name="person[birth][day]" /> </form>
(defroute "/edit" (&key _parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=)))) ;=> "((\"name\" . \"Eitaro\") (\"email\" . \"e.arrows@gmail.com\") (\"birth\" . ((\"year\" . 2000) (\"month\" . 1) (\"day\" . 1))))" ;=> "((\"name\" . \"Eitaro\") (\"email\" . \"e.arrows@gmail.com\") (\"birth\" . ((\"year\" . 2000) (\"month\" . 1) (\"day\" . 1))))"
空白のキーは、多値を含むことを表しています。
<form action="/add"> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="submit" value="Add" /> </form>
(defroute "/add" (&key _parsed) (format nil "~S" (assoc "items" _parsed :test #'string=))) ;=> "(((\"name\" . \"WiiU\") (\"price\" . \"30000\")) ((\"name\" . \"PS4\") (\"price\" . \"69000\")))"
テンプレート
Cavemanは、デフォルトのテンプレートエンジンとして、Djulaを採用しています。
{% extends "layouts/default.html" %} {% block title %}Users | MyApp{% endblock %} {% block content %} <div id="main"> <ul> {% for user in users %} <li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul> </div> {% endblock %}
(import 'myapp.view:render) (render #P"users.html" '(:users ((:url "/id/1" :name "nitro_idiot") (:url "/id/2" :name "meymao")) :has-next-page T))
Djulaを用いて、データベースから何か取得したり、関数を実行するには、どうしたらいいでしょうか。
レンダー(render)に、実行された引数を渡すときには、明示的にlist関数
を使う必要があります。
(import 'myapp.view:render) (render #P"users.html" (list :users (get-users-from-db)))
JSON API
次は、JSON APIの使用例です。
(defroute "/user.json" (&key |id|) (let ((person (find-person-from-db |id|))) ;; person => (:|name| "Eitaro Fukamachi" :|email| "e.arrows@gmail.com") (render-json person))) ;=> {"name":"Eitaro Fukamachi","email":"e.arrows@gmail.com"}
render-json
は、スケルトンプロジェクトの一部です。 コードは、"src/view.lisp"にあります。
静的なファイル
"static/"フォルダーにある画像、css、js、favicon.ico、robot.txtは、デフォルトでサーブされます。
/images/logo.png => {PROJECT_ROOT}/static/images/logo.png /css/main.css => {PROJECT_ROOT}/static/css/main.css /js/app/index.js => {PROJECT_ROOT}/static/js/app/index.js /robot.txt => {PROJECT_ROOT}/static/robot.txt /favicon.ico => {PROJECT_ROOT}/static/favicon.ico
"PROJECT_ROOT/app.lisp"を書き直すことで、このルールを変更することができます。
詳細は、Clack.Middleware.Staticを参照してください。
環境設定
Cavemanは、環境設定を切り替えるために、Envyを採用しています。
Envyを使うと、複数の環境を定義して、環境設定を環境変数で切り替えることができます。
次は、典型的な利用方法です。
(defpackage :myapp.config (:use :cl :envy)) (in-package :myapp.config) (setf (config-env-var) "APP_ENV") (defconfig :common `(:application-root ,(asdf:component-pathname (asdf:find-system :myapp)))) (defconfig |development| '(:debug T :databases ((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db" *application-root*))))) (defconfig |production| '(:databases ((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb :mysql :database-name "jobs" :username "whoami" :password "1234")))) (defconfig |staging| `(:debug T ,@|production|))
全ての環境設定は、plistです。APP_ENV
を設定することで、どの開発環境を使うかを選ぶことができます。
現在の環境設定から値を得るには、myapp.config:config
に入手したいキーと一緒に呼び出します。
(import 'myapp.config:config) (setf (osicat:environment-variable "APP_ENV") "development") (config :debug) ;=> T
データベース
:databases
を環境設定に加えると、Cavemanはデータベースのサポートを有効化します。
:databases
は、データベースの設定を含んだalistです。
(defconfig |production| '(:databases ((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb :mysql :database-name "jobs" :username "whoami" :password "1234"))))
myapp.db
にあるdb
は、上で設定された各々のデータベースに接続するための関数です。
次のように使えます。
(use-package '(:myapp.db :sxql :datafly)) (defun search-adults () (with-connection (db) (retrieve-all (select :* (from :person) (where (:>= :age 20))))))
接続は、Lispセッションの間は有効であり、それぞれのHTTPリクエストで再利用されます。
retrieve-all
とクエリ言語は、dataflyとSxQLからきています。
詳細は、それぞれのドキュメントをご参照ください。
HTTPヘッダー/HTTPステータスを設定する
HTTPリクエストの間、特別な変数を利用できます。
*request*
と*response*
は、リクエストとレスポンスを表します。
Clackを使い慣れている場合は、Clack.Requestと Clack.Responseのサブクラスのインスタンスもあります。
(use-package :caveman2) ;; Get a value of Referer header. (http-referer *request*) ;; Set Content-Type header. (setf (getf (response-headers *response* :content-type) "application/json") ;; Set HTTP status. (setf (status *response*) 304)
全ての"*.json"へのリクエストに対して、Content-Typeとして"application/json"を設定するには、next-route
が便利です。
(defroute "/*.json" () (setf (getf (response-headers *response*) :content-type) "application/json") (next-route)) (defroute "/user.json" () ...) (defroute "/search.json" () ...) (defroute ("/new.json" :method :POST) () ...)
Using session
セッションデータとは、ユーザー固有のデータを記憶するためのものです。
*session*
は、セッションデータを表すハッシュテーブルです。
次の例では、セッションでの:counter
を増やして、それぞれの訪問者に表示しています。
(defroute "/counter" () (format nil "You came here ~A times." (incf (gethash :counter *session* 0))))
Caveman2は、デフォルトで、セッションデータをメモリに保存します。
変更するには、"PROJECT_ROOT/app.lisp"にある:store
を:session
に指定します。
To change it, specify :store
to :session
in .
次の例では、保存するためにRDBMSを使っています。
'(:backtrace :output (getf (config) :error-log)) nil) - :session + (:session + :store (make-dbi-store :connector (lambda () + (apply #'dbi:connect + (myapp.db:connection-settings))))) (if (productionp) nil (lambda (app)
注: あなたのアプリの:depends-on
として、:lack-session-store-dbi
を追加するのを忘れないように気をつけてください。
それはClack/Lackの一部ではありません。
詳しい情報は、Lack.Session.Store.DBi
のコードをご覧ください。
HTTPステータスコードを投げる
(import 'caveman2:throw-code) (defroute ("/auth" :method :POST) (&key |name| |password|) (unless (authorize |name| |password|) (throw-code 403)))
エラーページを特定する
404, 500等のエラーページを特定するには、アプリ内でon-exception
メソッドを定義してください。
(defmethod on-exception ((app <web>) (code (eql 404))) (declare (ignore app code)) (merge-pathnames #P"_errors/404.html" *template-directory*))
サーバを起動する
あなたのアプリは、起動と停止のために、start
とstop
という名前の関数をもっています。
あなたのアプリが"myapp"という名前だとすると、次のようになります。
(myapp:start :port 8080)
Cavemanは、Clack/Lackを元にしているので、Hunchentoot、mod_lisp、FastCGIのどのサーバを使うかを選択することができます。
(myapp:start :server :hunchentoot :port 8080) (myapp:start :server :fcgi :port 8080)
ローカル環境ではHunchentoot、本番環境ではFastCGIかWooを使うことをおすすめします。
clackupコマンドを使って、アプリを起動することもできます。 clackup command.
$ ros install clack
$ which clackup
/Users/nitro_idiot/.roswell/bin/clackup
$ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
ホットデプロイ
Cavemanにはホットデプロイの機能はありませんが、PerlモジュールのServer::Starterを使うと簡単にできます。
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
注: Server::Starter
は、サーバが特定のFDにバインドできる必要があるので、start_server
コマンドで動作するのは、:fcgi
と:woo
だけです。
サーバを再起動するには、HUPシグナル(kill -HUP <pid>
)を、start_server
プロセスに送ってください。
エラーログ
Cavemanは、エラーのバックトレースを、:error-log
で特定したファイルに書き出します。
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log" :databases ((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db" *application-root*)))))
他のテンプレートエンジンを使う
CL-WHO
(import 'cl-who:with-html-output-to-string) (defroute "/" () (with-html-output-to-string (output nil :prologue t) (:html (:head (:title "Welcome to Caveman!")) (:body "Blah blah blah.")))) ;=> "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> ; <html><head><title>Welcome to Caveman!</title></head><body>Blah blah blah.</body></html>"
CL-Markup
(import 'cl-markup:xhtml) (defroute "/" () (xhtml (:head (:title "Welcome to Caveman!")) (:body "Blah blah blah."))) ;=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><title>Welcome to Caveman!</title></head><body>Blah blah blah.</body></html>"
cl-closure-template
{namespace myapp.view} {template renderIndex} <!DOCTYPE html> <html> <head> <title>"Welcome to Caveman!</title> </head> <body> Blah blah blah. </body> </html> {/template}
(import 'myapp.config:*template-directory*) (closure-template:compile-cl-templates (merge-pathnames #P"index.tmpl" *template-directory*)) (defroute "/" () (myapp.view:render-index))
(ql:quickload :clack-middleware-clsql) (import 'clack.middleware.clsql:<clack-middleware-clsql>) (builder (<clack-middleware-clsql> :database-type :mysql :connection-spec '("localhost" "db" "fukamachi" "password")) *web*)* [Clack.Middleware.Clsql](http://quickdocs.org/clack/api#system-clack-middleware-clsql) * [CLSQL: Common Lisp SQL Interface](http://clsql.b9.com/) ### Postmodern ClackアプリでPostmodernを使うためには、`Clack.Middleware.Postmodern`を使ってください。 Cavemanでは、"PROJECT_ROOT/app.lisp"の`builder`にミドルウェアを追加してください。
(ql:quickload :clack-middleware-postmodern) (import 'clack.middleware.postmodern:<clack-middleware-postmodern>) (builder (<clack-middleware-postmodern> :database "database-name" :user "database-user" :password "database-password" :host "remote-address") *web*)* [Clack.Middleware.Postmodern](http://quickdocs.org/clack/api#system-clack-middleware-postmodern) * [Postmodern](http://marijnhaverbeke.nl/postmodern/) -->
参考
- Clack - Web application environment.
- Lack - The core of Clack.
- ningle - Super micro web application framework Caveman bases on.
- Djula - HTML Templating engine.
- CL-DBI - Database independent interface library.
- SxQL - SQL builder library.
- Envy - Configuration switcher.
- Roswell - Common Lisp implementation manager.
作者
- Eitaro Fukamachi (e.arrows@gmail.com)
ライセンス
Licensed under the LLGPL License.
ningle - README 訳
ningle(2018年8月14日時点)のREADMEの和訳です。READMEの更新に合わせて、こちらの和訳も更新します。
ningle
ningleは、Common Lisp製の軽量Webアプリケーションフレームワークです。
使い方
(defvar *app* (make-instance 'ningle:<app>)) (setf (ningle:route *app* "/") "Welcome to ningle!") (setf (ningle:route *app* "/login" :method :POST) #'(lambda (params) (if (authorize (cdr (assoc "username" params :test #'string=)) (cdr (assoc "password" params :test #'string=))) "Authorized!" "Failed...Try again."))) (clack:clackup *app*)
ここまで進めてブラウザで http://localhost:5000/ にアクセスすると、ningleが"Welcome to ningle!"と表示してくれます。
インストール
(ql:quickload :ningle)
ningleについて
ningleは、Cavemanのフォークプロジェクトです。Cavemanではプロジェクトの雛形を生成しますが、ningleでは生成しません。
ningleは軽量のフレームワークなので、Clackについて少し知識が必要です。Clackは、ningleの元になっているサーバ・インターフェイスです。
はじめに
ルーティング
ningleはSinatraのようなルーティングシステムをもっています。
;; GET request (デフォルト) (setf (ningle:route *app* "/" :method :GET) ...) ;; POST request (setf (ningle:route *app* "/" :method :POST) ...) ;; PUT request (setf (ningle:route *app* "/" :method :PUT) ...) ;; DELETE request (setf (ningle:route *app* "/" :method :DELETE) ...) ;; OPTIONS request (setf (ningle:route *app* "/" :method :OPTIONS) ...)
ルーティングのパターンには、引数に値を設定するために、キーワードを含むことができます。
(setf (ningle:route *app* "/hello/:name") #'(lambda (params) (format nil "Hello, ~A" (cdr (assoc :name params)))))
上のコントローラでは、"/hello/Eitaro"や"/hello/Tomohiro"にアクセスしたときに呼び出されます。(cdr (assoc :name params))`は、"Eitaro"と"Tomohiro"になります。
ワイルドカードを含めることも可能です。(assoc :splat params)
でアクセスできます。
(setf (ningle:route *app* "/say/*/to/*") #'(lambda (params) ; matches /say/hello/to/world (cdr (assoc :splat params)) ;=> ("hello" "world") )) (setf (ningle:route *app* "/download/*.*") #'(lambda (params) ; matches /download/path/to/file.xml (cdr (assoc :splat params)) ;=> ("path/to/file" "xml") ))
正規表現を使うことも可能です:
(setf (ningle:route *app* "/hello/([\\w]+)" :regexp t) #'(lambda (params) (format nil "Hello, ~A!" (first (cdr (assoc :captures params))))))
必要条件
Routeは、様々な整合条件を含むことができます。例えば、Acceptの場合は次のようになります:
(setf (ningle:route *app* "/" :accept '("text/html" "text/xml")) #'(lambda (params) (declare (ignore params)) "<html><body>Hello, World!</body></html>")) (setf (ningle:route *app* "/" :accept "text/plain") #'(lambda (params) (declare (ignore params)) "Hello, World!"))
独自に条件を定義することも簡単にできます。
(setf (ningle:requirement *app* :probability) #'(lambda (value) (<= (random 100) value))) (setf (ningle:route *app* "/win_a_car" :probability 10) #'(lambda (params) (declare (ignore params)) "You won!")) (setf (ningle:route *app* "/win_a_car") #'(lambda (params) (declare (ignore params)) "Sorry, you lost."))
リクエストとレスポンス
ningleには特別な変数が2つあります。*request*
と*response*
です。これらは、毎回のリクエストの度に、Lack.RequestとLack.Responseに束縛されます。
例えば、これらを使うことで、それぞれのコントローラにおいて、レスポンスのステータスコードやContent-Type等を変更できます。
(setf (lack.response:response-headers *response*) (append (lack.response:response-headers *response*) (list :content-type "application/json"))) (setf (lack.response:response-headers *response*) (append (lack.response:response-headers *response*) (list :access-control-allow-origin "*"))) (setf (lack.response:response-status *response*) 201)
Context
ningleには、context
という便利な機能があります。context
は、内部のハッシュテーブルにアクセスするために使います。
(setf (context :database) (dbi:connect :mysql :database-name "test-db" :username "nobody" :password "nobody")) (context :database) ;;=> #<DBD.MYSQL:<DBD-MYSQL-CONNECTION> #x3020013D1C6D>
セッションを利用する
ningle自体にセッションの仕組みはありませんが、Lack.Builderと一緒にLack.Middleware.Sessionを使うことをおすすめします。
(import 'lack.builder:builder) (clack:clackup (builder :session *app*))
もちろん、ningleとあわせて、他のLackミドルウェアを使うこともできます。
参考
作者
- Eitaro Fukamachi (e.arrows@gmail.com)
著作権
Copyright (c) 2012-2014 Eitaro Fukamachi (e.arrows@gmail.com)
ライセンス
Licensed under the LLGPL License.