2017-08-08 55 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))