2017-08-08 57 views
1

我正在學習Clojure並享受它,但在記錄中發現不一致性令我感到困惑:爲什麼創建新記錄時默認映射構造函數(map-> Whatever)不檢查數據完整性?例如:Clojure:從地圖創建記錄時確保數據完整性?

user=> (defrecord Person [first-name last-name]) 
#<[email protected] user.Person> 
user=> (map->Person {:first-name "Rich" :last-name "Hickey"}) 
#user.Person {:first-name "Rich" :last-name "Hickey"} 
user=> (map->Person {:first-game "Rich" :last-name "Hickey"}) 
#user.Person {:first-game "Rich" :first-name nil :last-name "Hickey"} 

相信地圖不需要定義記錄定義中的所有領域,它也允許包含額外的字段不在記錄定義的一部分。我也明白,我可以定義我自己的構造函數,它包裝了默認的構造函數,我認爲一個:post條件可以用來檢查正確(和全面)的記錄創建(尚未成功實現該功能)。

我的問題是:是否有一個習慣Clojure的方式來驗證從地圖記錄建設期間的數據?而且,我在這裏錯過了關於唱片的東西嗎?

謝謝。

+0

我真的很少有需要轉換地圖 一個記錄。我只是使用原始的構造函數。什麼是用例?如果你正在序列化,你可以使用EDN來保證安全。 – Carcigenicate

+0

我只是在重構一箇舊的代碼問題,它使用地圖來現在使用記錄來理解他們的屬性。 JSON - >地圖矢量將成爲JSON - >記錄矢量。 Thx用於EDN參考。將調查。 – ericky

回答

5

我認爲您的綜合性要求已經非常具體,因此我所知道的內置任何內容都涵蓋了此內容。

你現在可以做的一件事是使用clojure.spec爲你的構造函數提供一個s/fdef(然後用它來測試它)。

(require '[clojure.spec.alpha :as s] 
     '[clojure.spec.test.alpha :as stest]) 

(defrecord Person [first-name last-name]) 

(s/fdef map->Person 
    :args (s/cat :map (s/keys :req-un [::first-name ::last-name]))) 

(stest/instrument `map->Person) 

(map->Person {:first-name "Rich", :last-name "Hickey"}) 
(map->Person {:first-game "Rich", :last-name "Hickey"}) ; now fails 

(如果規格針對::first-name定義和::last-name那些也將被檢查。)

+0

謝謝 - 我認爲spec可能會涉及到這個答案,我會進一步瞭解它。我對如何避免這種情況感興趣:'(map-> Person {:first-name「Rich」,:last-name「Hickey」,:pet「Spot」}); =>#user.Person {:first-name「Rich」,:last-name「Hickey」,:pet「Spot」}' – ericky

+0

@ericky以下規範確保只有鍵':a'和':b' (s /和(s/keys:req-un [:: a :: b]) #(= 2(count%)))' – Josh

+0

@ericky有沒有簡單的將密鑰限制爲一組封閉的允許密鑰並禁止所有其他密鑰的方式,這是故意的。據我所知,Clojure設計師主張「開放」規範(可能更普遍的系統是「開放」的擴展)。根據該觀點,應該始終可以使用其他密鑰來豐富您的數據,並且您的系統應該能夠容忍這種情況(允許豐富的數據通過它)... – glts

1

另一種選擇是to use Plumatic Schema創建一個包裝「構造」功能指定允許的密鑰。例如:

(def FooBar {(s/required-key :foo) s/Str (s/required-key :bar) s/Keyword}) 

(s/validate FooBar {:foo "f" :bar :b}) 
;; {:foo "f" :bar :b} 

(s/validate FooBar {:foo :f}) 
;; RuntimeException: Value does not match schema: 
;; {:foo (not (instance? java.lang.String :f)), 
;; :bar missing-required-key} 

第一行定義一個接受的模式只映射,如:

{ :foo "hello" :bar :some-kw } 

您包裝器的構造看起來是這樣的:

(def NameMap {(s/required-key :first-name) s/Str (s/required-key :last-name) s/Str}) 

(s/defn safe->person 
    [name-map :- NameMap] 
    (map->Person name-map)) 

(s/defn safe->person-2 
    [name-map] 
    (assert (= #{:first-name :last-name} (set (keys name-map)))) 
    (map->Person name-map))