猛然發現前面的都是廢話居多,這個地方開始說到鏈接數據庫的問題了。
我也是一遍按着教程學習一邊進行翻譯,有時候不免會卡殼啊……java
這個地方的數據庫鏈接仍是有些問題的,由於如今版本比較新了,而書中的JDBC仍是0.2.3的,API都換了一套新的了,在這裏卡了半天不知個因此然來,具體的解決能夠看文章裏面的詳細描述。git
至於在Instarepl中查看數據庫,這步我一直沒有成功,不知道是否是Light Table的問題,若是有靠譜的light table的教程能不能也共享給小弟一下~感激涕零~(反正我是以爲它在mac下的bug仍是不少的……常常崩潰次次復現啊……)github
翻譯的很差還請見諒啦~下面就開始正題~!sql
讓咱們來看看如何爲咱們的「留言簿」建立UI。不要爲不能立刻理解代碼而擔憂,它將貫穿接下來的章節。請不要太過專一於函數的實現細節,而是關心咱們如何構建咱們的應用,而且咱們在不一樣的地方都使用了何種不一樣的邏輯。數據庫
在以前咱們已經使用Hiccup寫過一些HTML的標籤。如今咱們將使用Hiccup庫的輔助功能來更好的實現這部分。瀏覽器
爲了使用這部分的功能,咱們將命名空間改爲以下的樣子:函數
(ns guestbook.routes.home (:require [compojure.core :refer :all] [guestbook.views.layout :as layout] [hiccup.form :refer :all]))
如今,咱們就要來建立一個函數來渲染生成已經有的消息的HTML。這個函數提供一段HTML,其中包含了全部存在的評論的列表。就目前,咱們先簡單的硬編碼了幾條測試的評論信息在裏面。post
(defn show-guests [] [:ul.guests (for [{:keys [message name timestamp]} [{:message "Howdy" :name "Bob" :timestamp nil} {:message "Hello" :name "Bob" :timestamp nil}]] [:li [:blockquote message] [:p "-" [:cite name]] [:time timestamp]])])
接下來,咱們更新一下home
函數,讓它可以顯示出以前已經有的留言信息,來幫助接下來的訪客能更加好的進行留言。學習
(defn home [& [name message error]] (layout/common [:h1 "Guestbook"] [:p "Welcome to my guestbook"] [:p error] ;here we call our show-guests function ;to generate the list of existing comments (show-guests) [:hr] ;here we create a form with text fields called "name" and "message" ;these will be sent when the form posts to the server as keywords of ;the same name (form-to [:post "/"] [:p "Name:"] (text-field "name" name) [:p "Message:"] (text-area {:rows 10 :cols 40} "message" message) [:br] (submit-button "comment"))))
當咱們再去看瀏覽器上的頁面的時候,能夠發現測試用的消息已經和表單一塊兒顯示在上面了。值得注意的是,如今的home
函數已經支持了幾個可選的參數,咱們能夠將參數中的值渲染到頁面時去,當參數是nil
的時候會渲染一個空的字符串到頁面之中。測試
表單的建立經過向路由"/"進行POST來建立,因此咱們在路由中添加一條來處理這個行爲,這個路由將會觸發一個等會就會來完成的輔助函數——save-message
。
(defroutes home-routes (GET "/" [] (home)) (POST "/" [name message] (save-message name message)))
在這save-message
函數將要檢查姓名和信息是否有值,而後調用home
方法。
當兩個參數所須要的信息都被提供,那麼在終端上就會輸出出來,不然就是產生一個錯誤信息。
(defn save-message [name message] (cond (empty? name) (home name message "some dummy forgot to leave a name.") (empty? message) (home name message "don't you have something to say?") :else (do (println name message) (home))))
嘗試添加新的消息就能夠看到在控制檯上會將輸入的信息輸出出來,而後你能夠試着不去填寫姓名或者是反饋信息,就能夠看到錯誤信息也能被渲染出來了。
好了,如今已經添加了查看、添加消息的用戶界面,可是目前咱們尚未一個真實的存儲這些信息的地方。
由於須要存儲訪客POST過來的數據,因此咱們在這裏須要引入JDBC
和SQLite
,將他們的包依賴加入到project.clj
。如今 :dependencies
應該是如今下面所展現的樣子:
:dependencies [[org.clojure/clojure "1.5.1"] [compojure "1.1.5"] [hiccup "1.0.4"] [ring-server "0.3.0"] ;;JDBC dependencies [org.clojure/java.jdbc "0.2.3"] [org.xerial/sqlite-jdbc "3.7.2"]]
由於添加了新的依賴包,因此咱們須要在REPL中從新聯接一下咱們的項目。首先到connect tab
(聯接區)去取消鏈接,而後再按前面教的從新作一遍就行了。
如今,咱們就作好了全部的準備工做,能夠爲應用加上數據模型了。咱們在文件夾src/guestbook/models
下新建一個命名空間 guestbook.models.db
。右擊文件夾就能夠建立這個文件,命名爲db.clj
。
(ns guestbook.models.db (:require [clojure.java.jdbc :as sql]) (:import java.sql.DriverManager))
這裏值得注意的是:咱們使用:require
關鍵詞來引用其它的Clojure的命名空間,而使用關鍵詞:import
來引用JAVA的類。
接下來,咱們來建立數據庫的鏈接的描述,這個描述就是一個map,包含了JDBC引擎的類名,協議的名稱以及被使用的數據庫文件的名稱。
(def db {:classname "org.sqlite.JDBC", :subprotocol "sqlite", :subname "db.sq3"})
如今咱們已經有了關於數據庫信息的一個斷言,接下來就來寫一個函數來新建一個用來儲存消息的表:
(defn create-guestbook-table [] (sql/with-connection db (sql/create-table :guestbook [:id "INTEGER PRIMARY KEY AUTOINCREMENT"] [:timestamp "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"] [:name "TEXT"] [:message "TEXT"]) (sql/do-commands "CREATE INDEX timestamp_index ON guestbook (timestamp)")))
在這個函數之中,使用了一個with-connection
的聲名,它能夠保證在調用完SQL以後能夠正確的釋放和清除數據庫鏈接。在它裏面,咱們調用了create-function
,而後傳入了一個key
——也至關於表的名字,後面再跟着傳入一系列的vectors——這些就是這張表的列。在最下面,咱們使用時間戳來建立這張表的索引。
如今到Instarepl中去要引用guestbook.models.db
,而後運行(create-guestbook-table)
方法。
(use 'guestbook.models.db) (create-guestbook-table)
[
!!!! 須要注意一下引用的 java jdbc 的變化,我在看書的時候使用了0.3.3的版本,會報錯說沒有with-connnection這個函數,經查詢得知如今這個函數在clojure.java.jdbc.deprecated
中了 詳細信息…
這個地方坑了我好一會。
]
如今你應該已經使用repl建立了一張表出來了,建立好表以後就能夠把repl的live模式關閉了,否則重複建立表是會報錯的。
如今表也建立好了,就能夠寫一個函數來從數據庫中讀取消息了,添加到db.clj中:
(defn read-guests [] (sql/with-connection db (sql/with-query-results res ["SELECT * FROM guestbook ORDER BY timestamp DESC"] (doall res))))
在這裏咱們使用read-guests
來運行查詢語句而且得到返回的結果。在返回結果以前咱們調用了函數doall
,是由於res是一個懶惰的對象,它的值沒有存儲在內存中。
使用doall
能夠強制的把res中的值都取出來,若是咱們不這麼作的話,咱們就無法在數據庫鏈接關閉以前把結果取出來了。
咱們也須要新增一個函數,用來保存新的消息到留言簿的表中去。將這個函數命名爲insert-value
而且傳入姓名和消息,進行數據存儲。
(defn save-message [name message] (sql/with-connection db (sql/insert-values :guestbook [:name :message :timestamp] [name message (new java.util.Date)])))
到如今,咱們已經完成了數據庫的讀、寫操做的函數。咱們能夠先到REPL中去測試一下,首先須要從新執行(use 'guestbook.models.db)
使Instarepl可使用新增長的函數。可是這裏有一個問題:guestbook.models.db
和gusetbook.routes.home
兩個命名空間裏面都有函數save-message
。若是咱們想要從新載入guestbook.models.db
,那麼就會提示你說save-message
已經被定義了。爲了不這個問題,咱們能夠在從新載入guestbook.models.db
以前使用ns-unmap
命令來移出已經定義的save-mesage
方法。
(ns-unmap 'user 'save-message) (use 'guestbook.models.db)
如今,咱們執行以下語句,就能夠看到存入數據庫的數據是什麼了。
(save-message "Bob" "hello") (read-guests)
數據層作的差很少了,接下來又能夠回到home
把假數據給替換掉了。