Web Development with Clojure CH1 (3)

猛然發現前面的都是廢話居多,這個地方開始說到鏈接數據庫的問題了。
我也是一遍按着教程學習一邊進行翻譯,有時候不免會卡殼啊……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過來的數據,因此咱們在這裏須要引入JDBCSQLite,將他們的包依賴加入到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.dbgusetbook.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把假數據給替換掉了。

相關文章
相關標籤/搜索