├── .gitignore
├── LICENSE.md
├── README.md
├── package.json
├── pom.xml.asc
├── project.clj
├── resources
└── public
│ ├── css
│ └── style.css
│ └── index.html
├── src
└── entitydb
│ ├── core.cljs
│ ├── relations.cljs
│ └── util.cljs
└── test
└── entitydb
├── core.cljs
└── test.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | /resources/public/js/compiled/**
2 | figwheel_server.log
3 | pom.xml
4 | *jar
5 | /lib/
6 | /classes/
7 | /out/
8 | /target/
9 | .lein-deps-sum
10 | .lein-repl-history
11 | .lein-plugins/
12 | .repl
13 | .nrepl-port
14 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 | -----------
3 |
4 | Copyright (c) 2016 Mihael Konjevic (http://retroaktive.me)
5 | Permission is hereby granted, free of charge, to any person
6 | obtaining a copy of this software and associated documentation
7 | files (the "Software"), to deal in the Software without
8 | restriction, including without limitation the rights to use,
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following
12 | conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 | OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EntityDB
2 |
3 | This project is **DEPRECATED**.
4 |
5 | Last deployed version is [keechma/entitydb "0.1.6"].
6 |
7 | New EntityDB is available at https://github.com/keechma/keechma-entitydb
8 |
9 | EntityDB is a library that handles storage of any kind of entities in your application. Entity is anything that is "identifiable". Usually the id attribute is used, but you can use anything that makes sense for your application.
10 |
11 | ## Documentation
12 |
13 | [Documentation](http://keechma.com/04-entitydb.html) and [API docs](http://keechma.com/api/keechma.edb.html).
14 |
15 | ## Setup
16 |
17 | ## License
18 |
19 | Copyright © 2016 Mihael Konjevic
20 |
21 | Distributed under the MIT License.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "keechma",
3 | "version": "0.0.1",
4 | "description": "Frontend micro framework for ClojureScript and Reagent",
5 | "author": "",
6 | "license": "ISC",
7 | "devDependencies": {
8 | "karma": "^0.13.16",
9 | "karma-chrome-launcher": "^0.2.2",
10 | "karma-cljs-test": "^0.1.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/pom.xml.asc:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP SIGNATURE-----
2 | Version: GnuPG v1
3 | Comment: GPGTools - https://gpgtools.org
4 |
5 | iQEcBAABCgAGBQJaEEpKAAoJEJ5sZof6g9jcoL8H/1cpq9/EqGpLpdr5v8FdLOAe
6 | tva8yyIqEhXO4NIc4lx2GUuifBFWR2Xy7hHnmu/f1QXGvg5BarG6gahIVo2yJfF+
7 | DtkGfXbtMEWQqFoB2I9wKk8pQUPtl+Ln+WINhDBUKRpjKsE60r1tuGGkWmqw1c5y
8 | PJKFvXKTlC/oGzn13yf1EOnmKXSfNTKTQnGyha1EiZZxmiyCzd7w7CPkx6I1pntC
9 | UXVbT29zHTxsWfDH2Lt5MbIJxsnRNGGpAa6SAf1yxiPP8sS60MdxIh6uh+Nen3CN
10 | FjAtfJI2EP10mvhNewonEBb7raCt9uQ0GfYLLmr2RnmjR5GCryI5Zw8+zmIKvmY=
11 | =TkP/
12 | -----END PGP SIGNATURE-----
13 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject keechma/entitydb "0.1.6"
2 | :description "EntityDB - In memory entity store for ClojureScript applications."
3 | :url "http://keechma.com/"
4 | :license {:name "MIT"}
5 |
6 | :min-lein-version "2.5.3"
7 |
8 | :dependencies [[org.clojure/clojure "1.9.0-RC1"]
9 | [org.clojure/clojurescript "1.9.946"]
10 | [lein-doo "0.1.6"]]
11 |
12 | :plugins [[lein-figwheel "0.5.14"]
13 | [lein-cljsbuild "1.1.7"]
14 | [lein-doo "0.1.6"]
15 | [lein-codox "0.9.3"]]
16 |
17 | :source-paths ["src"]
18 |
19 | :codox {:language :clojurescript
20 | :metadata {:doc/format :markdown}
21 | :namespaces [entitydb.core]}
22 |
23 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
24 |
25 | :cljsbuild {:builds
26 | [{:id "dev"
27 | :source-paths ["src"]
28 |
29 | ;; If no code is to be run, set :figwheel true for continued automagical reloading
30 | :figwheel {:on-jsload "entitydb.core/on-js-reload"}
31 |
32 | :compiler {:main entitydb.core
33 | :asset-path "js/compiled/out"
34 | :output-to "resources/public/js/compiled/entitydb.js"
35 | :output-dir "resources/public/js/compiled/out"
36 | :source-map-timestamp true}}
37 | ;; This next build is an compressed minified build for
38 | ;; production. You can build this with:
39 | ;; lein cljsbuild once min
40 | {:id "min"
41 | :source-paths ["src"]
42 | :compiler {:output-to "resources/public/js/compiled/entitydb.js"
43 | :main entitydb.core
44 | :optimizations :advanced
45 | :pretty-print false}}
46 | {:id "test"
47 | :source-paths ["src" "test"]
48 | :compiler {:output-to
49 | "resources/public/js/compiled/test.js"
50 | :optimizations :none
51 | :main entitydb.test.test}}]}
52 |
53 | :figwheel {;; :http-server-root "public" ;; default and assumes "resources"
54 | ;; :server-port 3449 ;; default
55 | ;; :server-ip "127.0.0.1"
56 |
57 | :css-dirs ["resources/public/css"] ;; watch and update CSS
58 |
59 | ;; Start an nREPL server into the running figwheel process
60 | ;; :nrepl-port 7888
61 |
62 | ;; Server Ring Handler (optional)
63 | ;; if you want to embed a ring handler into the figwheel http-kit
64 | ;; server, this is for simple ring servers, if this
65 | ;; doesn't work for you just run your own server :)
66 | ;; :ring-handler hello_world.server/handler
67 |
68 | ;; To be able to open files in your editor from the heads up display
69 | ;; you will need to put a script on your path.
70 | ;; that script will have to take a file path and a line number
71 | ;; ie. in ~/bin/myfile-opener
72 | ;; #! /bin/sh
73 | ;; emacsclient -n +$2 $1
74 | ;;
75 | ;; :open-file-command "myfile-opener"
76 |
77 | ;; if you want to disable the REPL
78 | ;; :repl false
79 |
80 | ;; to configure a different figwheel logfile path
81 | ;; :server-logfile "tmp/logs/figwheel-logfile.log"
82 | })
83 |
--------------------------------------------------------------------------------
/resources/public/css/style.css:
--------------------------------------------------------------------------------
1 | /* some style */
2 |
3 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Figwheel template
11 |
Checkout your developer console.
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/entitydb/core.cljs:
--------------------------------------------------------------------------------
1 | (ns entitydb.core
2 | (:require [entitydb.util :as util :refer [update-values]]
3 | [entitydb.relations :as relations]
4 | [clojure.set :as set]))
5 |
6 | (declare get-item-by-id)
7 | (declare insert-named-item)
8 | (declare insert-collection)
9 | (declare insert-related)
10 | (declare get-named-item)
11 | (declare get-collection)
12 | (declare insert-meta)
13 | (declare remove-meta)
14 | (declare get-collection-meta)
15 | (declare remove-collection-or-named-item)
16 | (declare remove-collection)
17 |
18 | (def ^:private meta-store :__meta-store__)
19 |
20 | (def relations :entitydb/relations)
21 |
22 | (defn insert-item
23 | "Inserts an item into the EntityDB collection.
24 |
25 | ```clojure
26 | (def schema {:foos {:id :id}})
27 | (def entity-db-v1 {})
28 |
29 | (def item {:id 1 :name \"Foo\"})
30 | (def item-meta {:is-loading false})
31 |
32 | (def entity-db-v2 (insert-item schema entity-db-v1 :foos item item-meta))
33 | ;; Returns the new version of the entity-db with the item inserted
34 | ;; inserted into the store
35 | ```
36 | "
37 | ([schema db entity-kw item] (insert-item schema db entity-kw item nil))
38 | ([schema db entity-kw item meta]
39 | (let [id (util/get-item-id schema entity-kw item)
40 | relations (relations/get-relations schema entity-kw)
41 | db-with-inserted-relations (insert-related schema db relations entity-kw id item)
42 | processed-item (relations/remove-related-from-item
43 | (keys relations)
44 | (util/call-middleware-set schema entity-kw item))
45 | merged-item (merge (or (get-in db [entity-kw :store id]) {}) processed-item)]
46 | (-> db-with-inserted-relations
47 | (insert-meta entity-kw id meta)
48 | (assoc-in [entity-kw :store id] merged-item)))))
49 |
50 | (defn insert-item-when-not-nil
51 | "Inserts an entity into the EntityDB if the entity is not nil."
52 | [schema db entity-kw item]
53 | (if-not (nil? item)
54 | (insert-item schema db entity-kw item)
55 | db))
56 |
57 | (defn insert-named-item
58 | "Inserts an item into the EntityDB, and references it from the named item slot.
59 |
60 | Item will be stored in the internal store, and named item slot will contain only
61 | the identity of the item.
62 |
63 | ```clojure
64 | (def entity-db-v1 {})
65 | (def schema {:foos {:id :id}})
66 |
67 | (def entity-db-v2 (insert-named-item schema entity-db-v1 :foos :current {:id 1 :name \"foo\"}))
68 | ;; Returns the new version of the entity-db with the entity saved in the store and
69 | ;; referenced from the `:current` named item slot.
70 |
71 | (get-named-item schema entity-db-v2 :foos :current)
72 | ;; Returns the entity referenced from the `:current` named slot.
73 |
74 | ```
75 | "
76 | ([schema db entity-kw collection-key item]
77 | (insert-named-item schema db entity-kw collection-key item nil))
78 | ([schema db entity-kw collection-key item meta]
79 | (if (and (nil? item) (nil? meta))
80 | db
81 | (let [id (util/get-item-id schema entity-kw item)
82 | meta-key (if (nil? item) collection-key id)]
83 | (-> db
84 | (remove-meta entity-kw collection-key)
85 | (assoc-in [entity-kw :c-one collection-key] id)
86 | ((partial insert-item-when-not-nil schema) entity-kw item)
87 | (insert-meta entity-kw meta-key meta))))))
88 |
89 | (defn insert-collection
90 | "Inserts a collection of items into the EntityDB. Each item will be
91 | stored in the internal store map, and the collection will be stored as a vector
92 | of entity identities.
93 |
94 | ```clojure
95 | (def entity-db-v1 {})
96 | (def schema {:foos {:id :id}})
97 |
98 | (def collection [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}])
99 |
100 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list collection))
101 | ;; Returns the new version of entity db. Each item will be stored
102 | ;; in the internal store map and collection will contain only the
103 | ;; item ids.
104 |
105 | (get-collection schema entity-db-v2 :foos :list)
106 | ;; Returns a collection of items named `:list`. Although internally collections
107 | ;; stores only a vector of ids, this function will return a vector of entities.
108 | ;;
109 | ;; [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}]
110 |
111 | ```
112 | "
113 | ([schema db entity-kw collection-key data]
114 | (insert-collection schema db entity-kw collection-key data nil))
115 | ([schema db entity-kw collection-key data meta]
116 | (if (and (empty? data) (nil? meta))
117 | (remove-collection db entity-kw collection-key)
118 | (let [id-fn (util/get-id-fn schema entity-kw)
119 | ids (into [] (map id-fn data))]
120 | (-> db
121 | (assoc-in [entity-kw :c-many collection-key] ids)
122 | (insert-meta entity-kw collection-key meta)
123 | (as-> db (reduce (fn [db item]
124 | (insert-item schema db entity-kw item))
125 | db data)))))))
126 |
127 | (defn append-collection
128 | "Appends items to an existing collection.
129 |
130 | ```clojure
131 | (def entity-db-v1 {})
132 | (def schema {:foos {:id :id}})
133 |
134 | (def collection [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}])
135 |
136 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list collection))
137 | ;; Returns the new version of entity db. Each item will be stored
138 | ;; in the internal store map and collection will contain only the
139 | ;; item ids.
140 |
141 | (get-collection schema entity-db-v2 :foos :list)
142 | ;; Returns a collection of items named `:list`. Although internally collections
143 | ;; stores only a vector of ids, this function will return a vector of entities.
144 | ;;
145 | ;; [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}]
146 |
147 |
148 | (def entity-db-v3 (append-collection schema entity-db-v2 :foos :list [{:id 3 :name \"baz}]))
149 |
150 | (get-collection schema entity-db-v3 :foos :list)
151 | ;; Returns [{:id 1 :name \"foo\"} {:id 2 :name \"bar} {:id 3 :name \"baz\"}]
152 |
153 | ```
154 | "
155 | ([schema db entity-kw collection-key data]
156 | (let [current-meta (get-collection-meta schema db entity-kw collection-key)]
157 | (append-collection schema db entity-kw collection-key data current-meta)))
158 | ([schema db entity-kw collection-key data meta]
159 | (let [c-path [entity-kw :c-many collection-key]
160 | current-ids (get-in db c-path)
161 | db-with-items (insert-collection schema db entity-kw collection-key data meta)
162 | new-ids (get-in db-with-items c-path)]
163 | (assoc-in db-with-items c-path (remove nil? (flatten [current-ids new-ids]))))))
164 |
165 | (defn prepend-collection
166 | "Prepends items to an existing collection.
167 |
168 | ```clojure
169 | (def entity-db-v1 {})
170 | (def schema {:foos {:id :id}})
171 |
172 | (def collection [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}])
173 |
174 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list collection))
175 | ;; Returns the new version of entity db. Each item will be stored
176 | ;; in the internal store map and collection will contain only the
177 | ;; item ids.
178 |
179 | (get-collection schema entity-db-v2 :foos :list)
180 | ;; Returns a collection of items named `:list`. Although internally collections
181 | ;; stores only a vector of ids, this function will return a vector of entities.
182 | ;;
183 | ;; [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}]
184 |
185 |
186 | (def entity-db-v3 (prepend-collection schema entity-db-v2 :foos :list [{:id 3 :name \"baz\"}]))
187 |
188 | (get-collection schema entity-db-v3 :foos :list)
189 | ;; Returns [{:id 3 :name \"baz\"} {:id 1 :name \"foo\"} {:id 2 :name \"bar\"}]
190 |
191 | ```
192 | "
193 | ([schema db entity-kw collection-key data]
194 | (let [current-meta (get-collection-meta schema db entity-kw collection-key)]
195 | (prepend-collection schema db entity-kw collection-key data current-meta)))
196 | ([schema db entity-kw collection-key data meta]
197 | (let [c-path [entity-kw :c-many collection-key]
198 | current-ids (get-in db c-path)
199 | db-with-items (insert-collection schema db entity-kw collection-key data meta)
200 | new-ids (get-in db-with-items c-path)]
201 | (assoc-in db-with-items c-path (remove nil? (flatten [new-ids current-ids]))))))
202 |
203 | (defn ^:private insert-related [schema db relations entity-kw id item]
204 | (reduce-kv
205 | (fn [db relation-kw [relation-type related-entity-kw]]
206 | (let [collection-key (relations/get-related-collection-key entity-kw id relation-kw)
207 | relation-data (relation-kw item)
208 | remove-collection-type-map {:one :c-one :many :c-many}
209 | insert-collection-fn (if (= relation-type :one)
210 | insert-named-item
211 | insert-collection)]
212 | (cond
213 | (fn? relation-data)
214 | db
215 |
216 | (not (contains? item relation-kw))
217 | db
218 |
219 | (and (contains? item relation-kw) (nil? (seq relation-data)))
220 | (remove-collection-or-named-item db related-entity-kw (remove-collection-type-map relation-type) collection-key)
221 |
222 | :else
223 | (insert-collection-fn schema db related-entity-kw collection-key relation-data))))
224 | db relations))
225 |
226 | (defn insert-meta
227 | "Inserts meta data for an entity or collection into the store."
228 | [db entity-kw meta-key meta]
229 | (let [schema {meta-store {:id (partial util/get-meta-id entity-kw meta-key)}}]
230 | (if (nil? meta)
231 | (remove-meta db entity-kw meta-key)
232 | (insert-item schema (util/add-empty-layout db meta-store) meta-store meta))))
233 |
234 | (defn ^:private remove-item-id-from-named-items [collections id]
235 | (into {} (filter (fn [[key val]]
236 | (not= val id)) collections)))
237 |
238 | (defn ^:private remove-item-id-from-collections [collections id]
239 | (update-values collections (fn [val]
240 | (filterv (partial not= id) val))))
241 |
242 | (defn remove-item
243 | "Removes item from the store. It will also remove it from any named-item slots or collections.
244 |
245 | ```clojure
246 | (def entity-db-v1 {})
247 | (def schema {:foos {:id :id}})
248 |
249 | (def foo-entity {:id 1 :name \"Bar\"})
250 |
251 | ;; insert `foo-entity` in the `:current` named item slot
252 | (def entity-db-v2 (insert-named-item schema entity-db-v1 :foos :current foo-entity))
253 |
254 | ;; insert `foo-entity` as a part of the `:list` collection
255 | (def entity-db-v3 (insert-collection schema entity-db-v2 :foos :list [foo-entity]))
256 |
257 | ;; get `foo-entity` from the entity-db
258 | (get-item-by-id schema entity-db-v3 :foos 1)
259 | ;; returns `foo-entity`
260 |
261 | (def entity-db-v4 (remove-item schema entity-db :foos 1))
262 |
263 | (get-named-item schema entity-db-v4 :foos :current)
264 | ;; returns `nil`
265 |
266 | (get-collection schema entity-db-v4 :foos :list)
267 | ;; returns []
268 | ```
269 | "
270 | [schema db entity-kw id]
271 | (let [c-one-without-item-id (remove-item-id-from-named-items (get-in db [entity-kw :c-one]) id)
272 | c-many-without-item-id (remove-item-id-from-collections (get-in db [entity-kw :c-many]) id)
273 | store-without-item (dissoc (get-in db [entity-kw :store]) id)
274 | db (-> db
275 | (remove-meta entity-kw id)
276 | (assoc-in [entity-kw :store] store-without-item)
277 | (assoc-in [entity-kw :c-one] c-one-without-item-id)
278 | (assoc-in [entity-kw :c-many] c-many-without-item-id))
279 | relations (relations/get-relations schema entity-kw)]
280 | (reduce-kv (partial relations/remove-related-collections entity-kw id) db relations)))
281 |
282 | (defn ^:private remove-collection-or-named-item [db entity-kw collection-type collection-key]
283 | (let [collections-without (dissoc (get-in db [entity-kw collection-type]) collection-key)]
284 | (-> db
285 | (remove-meta entity-kw collection-key)
286 | (assoc-in [entity-kw collection-type] collections-without))))
287 |
288 | (defn remove-named-item
289 | "Removes the named-item slot. Entity will still be stored in the internal store, but
290 | won't be available through the named-item slot.
291 |
292 | ```clojure
293 | (def entity-db-v1 {})
294 | (def schema {:foos {:id :id}})
295 |
296 | (def foo-entity {:id 1 :name \"bar\"})
297 |
298 | (def entity-db-v2 (insert-named-item schema entity-db-v1 :foos :current foo-entity))
299 |
300 | (get-named-item schema entity-db-v1 :foos :current)
301 | ;; Returns `{:id 1 :name \"bar\"}`
302 |
303 | (def entity-db-v3 (remove-named-item schema entity-db-v2 :foos :current))
304 |
305 | (get-named-item schema entity-db-v2 :foos :current)
306 | ;; Returns `nil`
307 |
308 | (get-item-by-id schema entity-db-v2 :foos 1)
309 | ;; Returns `{:id 1 :name \"bar\"}`
310 | ```
311 | "
312 | [db entity-kw collection-key]
313 | (remove-collection-or-named-item db entity-kw :c-one collection-key))
314 |
315 | (defn remove-collection
316 | "Removes the collection. Entities referenced from the collection will still be stored in
317 | the internal store, but won't be available through the collection API.
318 |
319 | ```clojure
320 | (def entity-db-v1 {})
321 | (def schema {:foos {:id :id}})
322 |
323 | (def foo-entity {:id 1 :name \"bar\"})
324 |
325 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list [foo-entity]))
326 |
327 | (get-collection schema entity-db-v2 :foos :list)
328 | ;; Returns `[{:id 1 :name \"bar\"}]`
329 |
330 | (def entity-db-v3 (remove-collection schema entity-db-v2 :foos :list))
331 |
332 | (get-collection schema entity-db-v2 :foos :list)
333 | ;; Returns `nil`
334 |
335 | (get-item-by-id schema entity-db-v2 :foos 1)
336 | ;; Returns `{:id 1 :name \"bar\"}`
337 | ```
338 | "
339 | [db entity-kw collection-key]
340 | (remove-collection-or-named-item db entity-kw :c-many collection-key))
341 |
342 | (defn empty-collection
343 | "Empties a collection, but leaves the meta intact. If the new meta is provided it will
344 | be merged into the current meta. Entities referenced from the collection will still be stored in
345 | the internal store, but won't be available through the collection API.
346 |
347 | ```clojure
348 | (def entity-db-v1 {})
349 | (def schema {:foos {:id :id}})
350 |
351 | (def foo-entity {:id 1 :name \"bar\"})
352 |
353 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list [foo-entity]))
354 |
355 | (get-collection schema entity-db-v2 :foos :list)
356 | ;; Returns `[{:id 1 :name \"bar\"}]`
357 |
358 | (def entity-db-v3 (empty-collection schema entity-db-v2 :foos :list))
359 |
360 | (get-collection schema entity-db-v2 :foos :list)
361 | ;; Returns `[]`
362 |
363 | (get-item-by-id schema entity-db-v2 :foos 1)
364 | ;; Returns `{:id 1 :name \"bar\"}`
365 | ```
366 | "
367 |
368 | ([db entity-kw collection-key]
369 | (empty-collection db entity-kw collection-key {}))
370 | ([db entity-kw collection-key meta]
371 | (-> db
372 | (assoc-in [entity-kw :c-many collection-key] [])
373 | (insert-meta entity-kw collection-key meta))))
374 |
375 | (defn remove-meta
376 | "Removes any meta data stored on the entity or collection"
377 | [db entity-kw id]
378 | (let [meta-key (util/get-meta-id entity-kw id)
379 | current-meta (get-in db [meta-store :store meta-key])]
380 | (if (nil? current-meta)
381 | db
382 | (let [store (get-in db [meta-store :store])
383 | store-without-item (dissoc store meta-key)]
384 | (assoc-in db [meta-store :store] (or store-without-item {}))))))
385 |
386 | (defn get-item-meta
387 | "Gets meta data for an entity."
388 | [schema db entity-kw id]
389 | (if (= entity-kw meta-store)
390 | nil
391 | (get-item-by-id schema db meta-store (util/get-meta-id entity-kw id))))
392 |
393 | (defn get-named-item-meta
394 | "Returns the meta data for an entity referenced in the named item slot."
395 | [schema db entity-kw collection-key]
396 | (let [item (get-named-item schema db entity-kw collection-key false)
397 | meta-key (if (nil? item)
398 | collection-key
399 | (util/get-item-id schema entity-kw item))]
400 | (get-item-meta schema db entity-kw meta-key)))
401 |
402 | (def get-collection-meta
403 | "Returns the meta data for a collection."
404 | get-item-meta)
405 |
406 | (defn ^:private get-related-items-fn [schema db entity-kw id]
407 | (fn [item relation-kw [relation-type related-entity-kw]]
408 | (let [collection-key (relations/get-related-collection-key entity-kw id relation-kw)
409 | get-related-fn (if (= relation-type :one)
410 | get-named-item
411 | get-collection)
412 | data-fn (partial get-related-fn schema db related-entity-kw collection-key)]
413 | (assoc item relation-kw data-fn))))
414 |
415 | (defn resolve-relations [item pull-relations]
416 | (if (seq pull-relations)
417 | (reduce
418 | (fn [acc r]
419 | (let [relation (if (vector? r) r [r])
420 | [attr & attr-pull-relations] relation
421 | attr-getter (get acc attr)]
422 | (if-let [resolved (when (fn? attr-getter) (attr-getter))]
423 | (if (vector? resolved)
424 | (assoc acc attr (map #(resolve-relations % attr-pull-relations) resolved))
425 | (assoc acc attr (resolve-relations resolved attr-pull-relations)))
426 | acc)))
427 | item pull-relations)
428 | item))
429 |
430 | (defn get-item-by-id
431 | "Gets an entity from the store by the id"
432 | ([schema db entity-kw id] (get-item-by-id schema db entity-kw id []))
433 | ([schema db entity-kw id pull-relations]
434 | (let [relations (relations/get-relations schema entity-kw)
435 | item (get-in db [entity-kw :store id])]
436 | (if (nil? item)
437 | nil
438 | (-> item
439 | (with-meta (get-item-meta schema db entity-kw id))
440 | (as-> item (reduce-kv (get-related-items-fn schema db entity-kw id) item relations))
441 | (resolve-relations pull-relations))))))
442 |
443 | (defn get-collection
444 | "Gets collection by it's key. Internally collections store only entity ids, but
445 | this function will return a collection of entities based on the ids stored in the collection
446 |
447 |
448 | ```clojure
449 | (def entity-db-v1 {})
450 | (def schema {:foos {:id :id}})
451 |
452 | (def collection [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}])
453 |
454 | (def entity-db-v2 (insert-collection schema entity-db-v1 :foos :list collection))
455 | ;; Returns the new version of entity db. Each item will be stored
456 | ;; in the internal store map and collection will contain only the
457 | ;; item ids.
458 |
459 | (get-collection schema entity-db-v2 :foos :list)
460 | ;; Returns a collection of items named `:list`. Although internally collections
461 | ;; stores only a vector of ids, this function will return a vector of entities.
462 | ;;
463 | ;; [{:id 1 :name \"foo\"} {:id 2 :name \"bar\"}]
464 | ```
465 | "
466 | ([schema db entity-kw collection-key]
467 | (get-collection schema db entity-kw collection-key []))
468 | ([schema db entity-kw collection-key pull-relations]
469 | (let [ids (get-in db [entity-kw :c-many collection-key])]
470 | (with-meta
471 | (into [] (map #(get-item-by-id schema db entity-kw % pull-relations) ids))
472 | (get-collection-meta schema db entity-kw collection-key)))))
473 |
474 | (defn get-named-item
475 | "Gets an entity referenced from the named item slot. Internally named slots store
476 | only entity ids but this function will return an entity based on the id."
477 | ([schema db entity-kw collection-key]
478 | (get-named-item schema db entity-kw collection-key false []))
479 | ([schema db entity-kw collection-key include-meta]
480 | (get-named-item schema db entity-kw collection-key include-meta []))
481 | ([schema db entity-kw collection-key include-meta pull-relations]
482 | (let [id (get-in db [entity-kw :c-one collection-key])
483 | item (get-item-by-id schema db entity-kw id pull-relations)]
484 | (if include-meta
485 | (with-meta
486 | item
487 | (get-named-item-meta schema db entity-kw collection-key))
488 | item))))
489 |
490 | (defn ^:private vacuum-entity-db [db entity-kw]
491 | (let [store (get-in db [entity-kw :store])
492 | ids (keys store)
493 | locked-one-ids (vals (get-in db [entity-kw :c-one]))
494 | locked-many-ids (vals (get-in db [entity-kw :c-many]))
495 | locked-ids (flatten [locked-one-ids locked-many-ids])
496 | to-remove-ids (set/difference (set ids) (set locked-ids))
497 | db-without-meta (reduce (fn [db id]
498 | (remove-meta db entity-kw id))
499 | db
500 | to-remove-ids)]
501 | (assoc-in db-without-meta
502 | [entity-kw :store]
503 | (select-keys store locked-ids))))
504 |
505 | (defn vacuum
506 | "Removes orphaned entities from the EntityDB. Any entity that is not referenced
507 | in a collection or in a named item slot will be removed from the EntityDB"
508 | [db]
509 | (let [entity-kws (keys db)
510 | entity-kws-without-meta (filterv (fn [k] (not (= k meta-store))) entity-kws)]
511 | (reduce vacuum-entity-db db entity-kws-without-meta)))
512 |
513 | (defn get-relation-path [schema parent-entity-kw attr parent]
514 | [parent-entity-kw (util/get-item-id schema parent-entity-kw parent) attr])
515 |
516 | (defn wrap-collection-fn-with-relation-path [relation-fn include-schema?]
517 | (fn [schema db parent-entity-kw attr parent & args]
518 | (let [relation (get-in schema [parent-entity-kw :relations attr])
519 | relation-collection? (= :many (first relation))
520 | relation-kw (last relation)]
521 | (when (not relation-collection?)
522 | (throw (ex-info (str attr " is not a collection relation") {})))
523 | (let [path (get-relation-path schema parent-entity-kw attr parent)
524 | default-args (if include-schema? [schema db relation-kw path] [db relation-kw path])]
525 | (apply relation-fn (into default-args args))))))
526 |
527 |
528 | (def insert-related-collection (wrap-collection-fn-with-relation-path insert-collection true))
529 | (def remove-related-collection (wrap-collection-fn-with-relation-path remove-collection false))
530 | (def prepend-related-collection (wrap-collection-fn-with-relation-path prepend-collection true))
531 | (def append-related-collection (wrap-collection-fn-with-relation-path append-collection true))
532 |
533 |
534 | (defn make-dbal
535 | "Returns a map with all public functions. These functions will have `schema`
536 | partially applied to them so you don't have to pass the schema around."
537 | [schema]
538 | {:insert-item (partial (util/ensure-layout insert-item) schema)
539 | :insert-named-item (partial (util/ensure-layout insert-named-item) schema)
540 | :insert-collection (partial (util/ensure-layout insert-collection) schema)
541 | :insert-related-collection (partial insert-related-collection schema)
542 | :append-collection (partial (util/ensure-layout append-collection) schema)
543 | :append-related-collection (partial append-related-collection schema)
544 | :prepend-collection (partial (util/ensure-layout prepend-collection) schema)
545 | :prepend-related-collection (partial prepend-related-collection schema)
546 | :insert-meta insert-meta
547 | :remove-item (partial (util/ensure-layout remove-item) schema)
548 | :remove-named-item remove-named-item
549 | :remove-collection remove-collection
550 | :remove-related-collection (partial remove-related-collection schema)
551 | :remove-meta remove-meta
552 | :get-item-by-id (partial get-item-by-id schema)
553 | :get-named-item (partial get-named-item schema)
554 | :get-collection (partial (util/ensure-layout get-collection) schema)
555 | :get-item-meta (partial get-item-meta schema)
556 | :get-named-item-meta (partial get-named-item-meta schema)
557 | :get-collection-meta (partial get-collection-meta schema)
558 | :vacuum vacuum})
559 |
--------------------------------------------------------------------------------
/src/entitydb/relations.cljs:
--------------------------------------------------------------------------------
1 | (ns entitydb.relations
2 | (:require [clojure.string :refer [join]]
3 | [entitydb.util :as util]))
4 |
5 |
6 | (defn get-relations [schema entity-kw]
7 | (or (get-in schema [entity-kw :relations]) {}))
8 |
9 | (defn get-related-collection-key [entity-kw id relation-kw]
10 | [entity-kw id relation-kw])
11 |
12 | (defn remove-related-from-item [related-entity-kws item]
13 | (reduce (fn [item related-entity-kw]
14 | (dissoc item related-entity-kw)) item related-entity-kws))
15 |
16 |
17 | (defn remove-related-collections [entity-kw id db relation-kw [relation-type related-entity-kw]]
18 | (let [collection-key (get-related-collection-key entity-kw id relation-kw)
19 | collection-type (if (= relation-type :one)
20 | :c-one
21 | :c-many)
22 | collections-without-related (dissoc (get-in db [related-entity-kw collection-type]) collection-key)]
23 | (assoc-in db [related-entity-kw collection-type] collections-without-related)))
24 |
--------------------------------------------------------------------------------
/src/entitydb/util.cljs:
--------------------------------------------------------------------------------
1 | (ns entitydb.util)
2 |
3 | (defn passthrough-item [item] item)
4 |
5 | (defn add-empty-layout [db entity-kw]
6 | (if (nil? (entity-kw db))
7 | (assoc db entity-kw {:store {}
8 | :c-one {}
9 | :c-many {}})
10 | db))
11 |
12 | (defn ensure-layout [dbal-fn]
13 | (fn [schema db entity-kw & args]
14 | (let [db-with-layout (add-empty-layout db entity-kw)]
15 | (apply dbal-fn (concat [schema db-with-layout entity-kw] args)))))
16 |
17 |
18 | (defn call-middleware [get-or-set schema entity-kw item]
19 | (let [middlewares (or (get-in schema [entity-kw :middleware get-or-set])
20 | [passthrough-item])
21 | pipeline (apply comp (reverse middlewares))]
22 | (pipeline item)))
23 |
24 |
25 | (def call-middleware-set (partial call-middleware :set))
26 | (def call-middleware-get (partial call-middleware :get))
27 |
28 | (defn get-id-fn [schema entity-kw]
29 | (or (get-in schema [entity-kw :id]) :id))
30 |
31 | (defn get-item-id [schema entity-kw item]
32 | (let [id-fn (get-id-fn schema entity-kw)]
33 | (id-fn item)))
34 |
35 | (defn get-meta-id [entity-kw id]
36 | [entity-kw id])
37 |
38 | (defn update-values [m f & args]
39 | (reduce (fn [r [k v]] (assoc r k (apply f v args))) {} m))
40 |
--------------------------------------------------------------------------------
/test/entitydb/core.cljs:
--------------------------------------------------------------------------------
1 | (ns entitydb.test.core
2 | (:require [cljs.test :refer-macros [deftest is]]
3 | [entitydb.core :as edb]
4 | [entitydb.util :as util]
5 | [entitydb.relations :as relations]
6 | [clojure.string :refer [upper-case]]))
7 |
8 | (def schema {:notes {:id :id
9 | :middleware {:set [(fn [item] item)]}
10 | :relations {:user [:one :users]
11 | :links [:many :links]}
12 | :schema {}}
13 | :links {:id :id}
14 | :users {:id :id}})
15 |
16 | (def dbal (edb/make-dbal schema))
17 |
18 | (defn uppercase-note-title [i]
19 | (assoc i :title (upper-case (:title i))))
20 |
21 | (deftest ensure-layout []
22 | (let [ensured-fn (util/ensure-layout (fn [schema db entity-kw] db))
23 | new-db (ensured-fn dbal {} :notes)]
24 | (is (= new-db {:notes {:store {}
25 | :c-one {}
26 | :c-many {}}}))))
27 |
28 | (deftest call-middleware-set []
29 | (let [processed-item (util/call-middleware-set
30 | {:notes {:middleware {:set [uppercase-note-title]}}}
31 | :notes
32 | {:title "note title"})]
33 | (is (= (:title processed-item) "NOTE TITLE"))))
34 |
35 | (deftest insert-item []
36 | (let [db (util/add-empty-layout {} :notes)
37 | db-with-item (edb/insert-item {:notes {:id :id}} db :notes {:id 1 :title "Note title"})]
38 | (is (= (get-in db-with-item [:notes :store 1 :title]) "Note title"))))
39 |
40 | (deftest insert-item-with-custom-id-fn []
41 | (let [db (util/add-empty-layout {} :notes)
42 | id-fn (fn [item] (str (:id item) "-note"))
43 | schema {:notes {:id id-fn}}
44 | db-with-item (edb/insert-item schema db :notes {:id 1 :title "Note title"})]
45 | (is (= (get-in db-with-item [:notes :store "1-note" :title]) "Note title"))))
46 |
47 | (deftest remove-item []
48 | (let [db {:notes {:store {1 {:id 1 :title "Note title"}}
49 | :c-one {}
50 | :c-many {}}
51 | :users {:store {1 {:id 1 :username "Retro"}}
52 | :c-one {[:notes 1 :user] 1}
53 | :c-many {}}
54 | :links {:store {1 {:id 1 :url "www.google.com"}}
55 | :c-one {}
56 | :c-many {[:notes 1 :links] [1]}}}
57 | db-after-delete {:notes {:store {}
58 | :c-one {}
59 | :c-many {}}
60 | :users {:store {1 {:id 1 :username "Retro"}}
61 | :c-one {}
62 | :c-many {}}
63 | :links {:store {1 {:id 1 :url "www.google.com"}}
64 | :c-one {}
65 | :c-many {}}}]
66 | (is (= db-after-delete (edb/remove-item schema db :notes 1)))))
67 |
68 | (deftest get-item-by-id []
69 | (let [db {:notes {:store {1 {:id 1 :title "Note title"}}}}]
70 | (is (= (:title (edb/get-item-by-id {} db :notes 1)) "Note title"))))
71 |
72 | (deftest insert-named-item []
73 | (let [schema {:notes {}}
74 | db (util/add-empty-layout {} :notes)
75 | db-with-items (edb/insert-named-item schema db :notes :current-item {:id 1 :title "Note title"})
76 | expected-layout {:notes {:store {1 {:id 1 :title "Note title"}}
77 | :c-one {:current-item 1}
78 | :c-many {}}}]
79 | (is (= db-with-items expected-layout))))
80 |
81 | (deftest insert-collection []
82 | (let [schema {:notes {}}
83 | db (util/add-empty-layout {} :notes)
84 | note-1 {:id 1 :title "Note title 1"}
85 | note-2 {:id 2 :title "Note title 2"}
86 | db-with-items (edb/insert-collection schema db :notes :latest [note-1 note-2])
87 | expected-layout {:notes {:store {1 note-1 2 note-2}
88 | :c-one {}
89 | :c-many {:latest [1 2]}}}]
90 | (is (= db-with-items expected-layout))))
91 |
92 | (deftest append-collection []
93 | (let [schema {:notes {}}
94 | db {:notes {:store {1 {:id 1} 2 {:id 2}}
95 | :c-one {}
96 | :c-many {:latest [1 2]}}}
97 | expected-db {:notes {:store {1 {:id 1}
98 | 2 {:id 2}
99 | 3 {:id 3}
100 | 4 {:id 4}}
101 | :c-one {}
102 | :c-many {:latest [1 2 3 4]}}}]
103 | (is (= expected-db (edb/append-collection
104 | schema
105 | db
106 | :notes
107 | :latest
108 | [{:id 3} {:id 4}])))))
109 |
110 | (deftest prepend-collection []
111 | (let [schema {:notes {}}
112 | db {:notes {:store {1 {:id 1} 2 {:id 2}}
113 | :c-one {}
114 | :c-many {:latest [1 2]}}}
115 | expected-db {:notes {:store {1 {:id 1}
116 | 2 {:id 2}
117 | 3 {:id 3}
118 | 4 {:id 4}}
119 | :c-one {}
120 | :c-many {:latest [3 4 1 2]}}}]
121 | (is (= expected-db (edb/prepend-collection
122 | schema
123 | db
124 | :notes
125 | :latest
126 | [{:id 3} {:id 4}])))))
127 |
128 | (deftest prepend-to-empty-collection []
129 | (let [schema {:notes {}}
130 | db {:notes {:store {}
131 | :c-one {}
132 | :c-many {}}}
133 | expected-db {:notes {:store {1 {:id 1}}
134 | :c-one {}
135 | :c-many {:latest [1]}}}]
136 | (is (= expected-db (edb/prepend-collection
137 | schema
138 | db
139 | :notes
140 | :latest
141 | [{:id 1}])))))
142 |
143 | (deftest append-to-empty-collection []
144 | (let [schema {:notes {}}
145 | db {:notes {:store {}
146 | :c-one {}
147 | :c-many {}}}
148 | expected-db {:notes {:store {1 {:id 1}}
149 | :c-one {}
150 | :c-many {:latest [1]}}}]
151 | (is (= expected-db (edb/append-collection
152 | schema
153 | db
154 | :notes
155 | :latest
156 | [{:id 1}])))))
157 |
158 |
159 | (deftest get-named-item []
160 | (let [schema {:notes {}}
161 | note-1 {:id 1 :title "Note title 1"}
162 | db {:notes {:store {1 note-1}
163 | :c-one {:current 1}
164 | :c-many {}}}]
165 | (is (= (edb/get-named-item schema db :notes :current) note-1))))
166 |
167 | (deftest get-collection []
168 | (let [schema {:notes {}}
169 | note-1 {:id 1 :title "Note title 1"}
170 | note-2 {:id 2 :title "Note title 2"}
171 | db {:notes {:store {1 note-1 2 note-2}
172 | :c-one {}
173 | :c-many {:latest [1 2]}}}]
174 | (is (= (edb/get-collection schema db :notes :latest) [note-1 note-2]))))
175 |
176 | (deftest inserting-nil-relation-one-removes-existing-relation-one []
177 | (let [schema {:users {}
178 | :notes {:relations {:user [:one :users]}}}
179 | db (-> {}
180 | (util/add-empty-layout :notes)
181 | (util/add-empty-layout :users))
182 | note {:id 1 :user {:id 1}}
183 | note-with-nil-user {:id 1 :user nil}
184 | db-with-user (edb/insert-item schema db :notes note)
185 | expected-db-with-user {:notes {:store {1 {:id 1}}
186 | :c-one {}
187 | :c-many {}}
188 | :users {:store {1 {:id 1}}
189 | :c-one {[:notes 1 :user] 1}
190 | :c-many {}}}
191 | expected-db-with-nil-user {:notes {:store {1 {:id 1}}
192 | :c-one {}
193 | :c-many {}}
194 | :users {:store {1 {:id 1}}
195 | :c-one {}
196 | :c-many {}}}]
197 | (is (= db-with-user expected-db-with-user))
198 | (is (= (edb/insert-item schema db-with-user :notes note-with-nil-user) expected-db-with-nil-user))))
199 |
200 | (deftest inserting-item-with-no-related-data-leaves-existing-relations-intact []
201 | (let [schema {:users {}
202 | :notes {:relations {:user [:one :users]}}}
203 | db (-> {}
204 | (util/add-empty-layout :notes)
205 | (util/add-empty-layout :users))
206 | note {:id 1 :user {:id 1}}
207 | note-with-no-user {:id 1 :title "Foo"}
208 | db-with-user (edb/insert-item schema db :notes note)
209 | expected-db-with-user {:notes {:store {1 {:id 1}}
210 | :c-one {}
211 | :c-many {}}
212 | :users {:store {1 {:id 1}}
213 | :c-one {[:notes 1 :user] 1}
214 | :c-many {}}}
215 | expected-db-after-note-with-no-user {:notes {:store {1 {:id 1 :title "Foo"}}
216 | :c-one {}
217 | :c-many {}}
218 | :users {:store {1 {:id 1}}
219 | :c-one {[:notes 1 :user] 1}
220 | :c-many {}}}]
221 | (is (= db-with-user expected-db-with-user))
222 | (is (= (edb/insert-item schema db-with-user :notes note-with-no-user) expected-db-after-note-with-no-user))))
223 |
224 | (deftest relation-one []
225 | (let [db (-> {}
226 | (util/add-empty-layout :notes)
227 | (util/add-empty-layout :users))
228 | note {:id 1 :title "Note title" :user {:id 1 :username "Retro"}}
229 | db-with-items (edb/insert-item schema db :notes note)
230 | expected-db {:notes {:store {1 {:id 1 :title "Note title"}}
231 | :c-one {}
232 | :c-many {}}
233 | :users {:store {1 {:id 1 :username "Retro"}}
234 | :c-one {[:notes 1 :user] 1}
235 | :c-many {}}}]
236 | (is (= db-with-items expected-db))
237 | (let [note-from-db (edb/get-item-by-id schema db-with-items :notes 1)
238 | user-from-db (edb/get-item-by-id schema db-with-items :users 1)]
239 | (is (= "Note title" (:title note-from-db)))
240 | (is (= 1 (:id note)))
241 | (is (= user-from-db ((:user note-from-db))))
242 | (is (= user-from-db {:id 1 :username "Retro"})))))
243 |
244 | (deftest relation-many []
245 | (let [db (-> {}
246 | (util/add-empty-layout :notes)
247 | (util/add-empty-layout :links))
248 | note {:id 1 :title "Note title"
249 | :links [{:id 1 :url "http://google.com"}
250 | {:id 2 :url "http://bing.com"}]}
251 | db-with-items (edb/insert-item schema db :notes note)
252 | expected-db {:notes {:store {1 {:id 1 :title "Note title"}}
253 | :c-one {}
254 | :c-many {}}
255 | :links {:store {1 {:id 1 :url "http://google.com"}
256 | 2 {:id 2 :url "http://bing.com"}}
257 | :c-one {}
258 | :c-many {[:notes 1 :links] [1 2]}}}]
259 | (is (= db-with-items expected-db))
260 | (is (= (edb/get-item-by-id schema db-with-items :links 1) {:id 1 :url "http://google.com"}))
261 | (let [note-from-db (edb/get-item-by-id schema db-with-items :notes 1)]
262 | (is (= "Note title" (:title note-from-db)))
263 | (is (= 1 (:id note-from-db)))
264 | (is (= (:links note) ((:links note-from-db)))))))
265 |
266 | (deftest circular-relations
267 | (let [db (-> {}
268 | (util/add-empty-layout :notes)
269 | (util/add-empty-layout :links))
270 | schema {:notes {:relations {:links [:many :links]
271 | :user [:one :user]}}
272 | :links {:relations {:user [:one :user]
273 | :notes [:many :notes]}}}
274 | note {:id 1
275 | :title "Note title"
276 | :links [{:id 1} {:id 2}]}
277 | link {:id 1
278 | :url "http://www.google.com"
279 | :notes [{:id 1}]}
280 | insert-item (partial edb/insert-item schema)
281 | db-with-items (-> db
282 | (insert-item :notes note)
283 | (insert-item :links link))]
284 | (let [note-from-db (edb/get-item-by-id schema db-with-items :notes 1)
285 | link-from-db (edb/get-item-by-id schema db-with-items :links 1)
286 | note-link (get ((:links note-from-db)) 0)
287 | note-link-note (get ((:notes note-link)) 0)]
288 | (= (:url note-link) "http://www.google.com")
289 | (= note-from-db note-link-note))))
290 |
291 | (deftest nested-relations []
292 | (let [db (-> {}
293 | (util/add-empty-layout :notes)
294 | (util/add-empty-layout :links)
295 | (util/add-empty-layout :users))
296 | note {:id 1
297 | :title "Note title"
298 | :links [{:id 1}]}
299 | user {:id 1 :username "Retro"}
300 | link {:id 1 :url "http://google.com" :user {:id 1}}
301 | schema (assoc schema :links {:relations {:user [:one :users]}})
302 | insert-item (partial edb/insert-item schema)
303 | db-with-items (-> db
304 | (insert-item :notes note)
305 | (insert-item :users user)
306 | (insert-item :links link))
307 | link-from-relation (get ((:links (edb/get-item-by-id schema db-with-items :notes 1))) 0)
308 | user-from-relation ((:user link-from-relation))]
309 | (is (= user-from-relation user))))
310 |
311 | (deftest tree-relations
312 | (let [schema {:notes {:relations {:notes [:many :notes]}}}
313 | db (util/add-empty-layout {} :notes)
314 | notes [{:id 1 :title "Note #1" :notes [{:id 2}]}
315 | {:id 2 :title "Note #2" :notes [{:id 3}]}
316 | {:id 3 :title "Note #3" :notes []}]
317 | insert-item (partial edb/insert-item schema)
318 | db-with-items (reduce (fn [db item]
319 | (insert-item db :notes item))
320 | db notes)
321 | note-1 (edb/get-item-by-id schema db-with-items :notes 1)]
322 | (is (= (get-in ((:notes note-1)) [0 :title]) "Note #2"))
323 | (is (= (get-in ((:notes (get((:notes note-1)) 0))) [0 :title]) "Note #3"))))
324 |
325 | (deftest remove-collection []
326 | (let [db {:notes {:store {1 {:id 1}}
327 | :c-one {:current 1
328 | :pinned 2}
329 | :c-many {:latest [1 2]
330 | :starred [2 3]}}}
331 | expected-db {:notes {:store {1 {:id 1}}
332 | :c-one {:pinned 2}
333 | :c-many {:starred [2 3]}}}]
334 | (is (= expected-db
335 | (-> db
336 | (edb/remove-named-item :notes :current)
337 | (edb/remove-collection :notes :latest))))))
338 |
339 | (deftest empty-collection []
340 | (let [db {:notes {:store {1 {:id 1} 2 {:id 2} 3 {:id 3}}
341 | :c-one {}
342 | :c-many {:latest [1 2]
343 | :starred [2 3]
344 | :test [1]}}
345 | :__meta-store__ {:c-one {}
346 | :c-many {}
347 | :store {[:notes :latest] {:foo :bar}
348 | [:notes :starred] {:bar :baz}
349 | [:notes :test] {:baz :qux}}}}
350 | expected-db {:notes
351 | {:c-one {}
352 | :store {1 {:id 1} 2 {:id 2} 3 {:id 3}}
353 | :c-many {:starred []
354 | :latest []
355 | :test []}}
356 | :__meta-store__
357 | {:c-one {}
358 | :store {[:notes :latest] {:foo :bar}
359 | [:notes :test] {:baz :qux :foo :bar}}
360 | :c-many {}}}]
361 | (is (= expected-db
362 | (-> db
363 | (edb/empty-collection :notes :test {:foo :bar})
364 | (edb/empty-collection :notes :starred nil)
365 | (edb/empty-collection :notes :latest))))))
366 |
367 | (deftest item-meta []
368 | (let [item-meta {:status :loaded}
369 | item {:id 1 :title "Note"}
370 | schema {:notes {}}
371 | db (util/add-empty-layout {} :notes)
372 | db-with-items (edb/insert-item schema db :notes item item-meta)
373 | expected-db {:notes {:store {1 item}
374 | :c-one {}
375 | :c-many {}}
376 | :__meta-store__ {:store {[:notes 1] item-meta}
377 | :c-one {}
378 | :c-many {}}}]
379 | (is (= db-with-items expected-db))
380 | (is (= item-meta
381 | (meta (edb/get-item-by-id schema db-with-items :notes 1))))
382 | (is (= item-meta (edb/get-item-meta schema db-with-items :notes 1)))))
383 |
384 | (deftest named-item-meta []
385 | (let [collection-meta {:status :loading}
386 | item-meta {:status :loaded}
387 | item {:id 1 :title "Note"}
388 | schema {:notes {}}
389 | db (util/add-empty-layout {} :notes)
390 |
391 | db-with-collection
392 | (edb/insert-named-item schema db :notes :current-item nil collection-meta)
393 |
394 | db-with-collection-and-item
395 | (edb/insert-named-item schema db-with-collection :notes :current-item item item-meta)
396 |
397 | expected-db-with-collection
398 | {:notes {:store {}
399 | :c-one {:current-item nil}
400 | :c-many {}}
401 | :__meta-store__ {:store {[:notes :current-item] collection-meta}
402 | :c-one {}
403 | :c-many {}}}
404 |
405 | expected-db-with-collection-and-item
406 | {:notes {:store {1 item}
407 | :c-one {:current-item 1}
408 | :c-many {}}
409 | :__meta-store__ {:store {[:notes 1] item-meta}
410 | :c-one {}
411 | :c-many {}}}]
412 | (is (= db-with-collection expected-db-with-collection))
413 | (is (= db-with-collection-and-item expected-db-with-collection-and-item))
414 | (is (= collection-meta
415 | (edb/get-named-item-meta schema db-with-collection :notes :current-item)))
416 | (is (= item-meta
417 | (edb/get-named-item-meta schema db-with-collection-and-item :notes :current-item)))))
418 |
419 | (deftest collection-meta []
420 | (let [collection-meta {:status :loading}
421 | items [{:id 1 :title "Note"}]
422 | schema {:notes {}}
423 | db (util/add-empty-layout {} :notes)
424 |
425 | db-with-collection
426 | (edb/insert-collection schema db :notes :latest items collection-meta)
427 |
428 | expected-db-with-collection
429 | {:notes {:store {1 (get items 0)}
430 | :c-one {}
431 | :c-many {:latest [1]}}
432 | :__meta-store__ {:store {[:notes :latest] collection-meta}
433 | :c-one {}
434 | :c-many {}}}]
435 | (is (= db-with-collection expected-db-with-collection))
436 | (is (= collection-meta
437 | (edb/get-collection-meta schema db-with-collection :notes :latest)))
438 | (is (= collection-meta
439 | (meta (edb/get-collection schema db-with-collection :notes :latest))))))
440 |
441 | (deftest removing-item-removes-its-id-from-collections []
442 | (let [schema {:notes {}}
443 | insert-named-item (partial edb/insert-named-item schema)
444 | insert-collection (partial edb/insert-collection schema)
445 | note-1 {:id 1}
446 | note-2 {:id 2}
447 | db (util/add-empty-layout {} :notes)
448 | db-with-items-and-collections (-> db
449 | (insert-named-item :notes :current note-1)
450 | (insert-collection :notes :list [note-1 note-2]))
451 | expected-db-after-delete {:notes {:store {2 {:id 2}}
452 | :c-one {}
453 | :c-many {:list [2]}}}]
454 | (is (= expected-db-after-delete
455 | (edb/remove-item schema db-with-items-and-collections :notes 1)))))
456 |
457 | (deftest removing-item-removes-meta []
458 | (let [db {:notes {:store {1 {:id 1}}
459 | :c-one {}
460 | :c-many {}}
461 | :__meta-store__ {:store {[:notes 1] {:status :loaded}}
462 | :c-one {}
463 | :c-many {}}}
464 | expected-db {:notes {:store {}
465 | :c-one {}
466 | :c-many {}}
467 | :__meta-store__ {:store {}
468 | :c-one {}
469 | :c-many {}}}]
470 | (is (= expected-db (edb/remove-item {} db :notes 1)))))
471 |
472 | (deftest removing-named-item-removes-meta []
473 | (let [db {:notes {:store {}
474 | :c-one {:current-item nil}
475 | :c-many {}}
476 | :__meta-store__ {:store {[:notes :current-item] {:status :loaded}}
477 | :c-one {}
478 | :c-many {}}}
479 | expected-db {:notes {:store {}
480 | :c-one {}
481 | :c-many {}}
482 | :__meta-store__ {:store {}
483 | :c-one {}
484 | :c-many {}}}]
485 | (is (= expected-db (edb/remove-named-item db :notes :current-item)))))
486 |
487 | (deftest removing-collection-removes-meta []
488 | (let [db {:notes {:store {}
489 | :c-one {}
490 | :c-many {:latest []}}
491 | :__meta-store__ {:store {[:notes :latest] {:status :loaded}}
492 | :c-one {}
493 | :c-many {}}}
494 | expected-db {:notes {:store {}
495 | :c-one {}
496 | :c-many {}}
497 | :__meta-store__ {:store {}
498 | :c-one {}
499 | :c-many {}}}]
500 | (is (= expected-db (edb/remove-collection db :notes :latest)))))
501 |
502 | (deftest vacuum []
503 | (let [db {:notes {:store {1 {:id 1} 2 {:id 2} 3 {:id 3} 4 {:id 4}}
504 | :c-one {:current 1}
505 | :c-many {:latest [1 3]}}
506 | :__meta-store__ {:store {[:notes 2] {:status :loaded}}
507 | :c-one {}
508 | :c-many {}}}
509 | expected-db {:notes {:store {1 {:id 1} 3 {:id 3}}
510 | :c-one {:current 1}
511 | :c-many {:latest [1 3]}}
512 | :__meta-store__ {:store {}
513 | :c-one {}
514 | :c-many {}}}]
515 | (is (= (edb/vacuum db) expected-db))))
516 |
517 | ;; DBAL
518 | (deftest dbal-insert-item []
519 | (let [insert-item-fn (:insert-item dbal)
520 | db (insert-item-fn {} :notes {:id 1 :title "Note title"})]
521 | (is (= (get-in db [:notes :store 1 :title]) "Note title"))))
522 |
523 | (deftest inserting-item-with-relations-should-strip-them
524 | (let [schema {:user {:relations {:notes [:many :note]}
525 | :id :id}
526 | :note {:id :id}}
527 | user {:id 1 :notes [{:id 1} {:id 2}]}
528 | insert-named-item (partial edb/insert-named-item schema)
529 | insert-collection (partial edb/insert-collection schema)
530 | get-named-item (partial edb/get-named-item schema)
531 | db {}
532 | db-after-insert (insert-named-item db :user :current user)
533 | user-from-db (get-named-item db-after-insert :user :current)]
534 | (is (= {:user {:c-one {:current 1}
535 | :c-many {:list [1]}
536 | :store {1 {:id 1}}}
537 | :note {:store {1 {:id 1} 2 {:id 2}}
538 | :c-many {[:user 1 :notes] [1 2]}}}
539 | (insert-collection db-after-insert :user :list [user-from-db])))
540 | (is (= {:user {:c-one {:current 1}
541 | :store {1 {:id 1}}}
542 | :note {:store {1 {:id 1} 2 {:id 2}}
543 | :c-many {[:user 1 :notes] [1 2]}}}
544 | (insert-named-item db-after-insert :user :current user-from-db)
545 | db-after-insert))))
546 |
547 | (deftest getting-nil-item-with-relations-should-return-nil
548 | (let [schema {:user {:relations {:notes [:many :note]}
549 | :id :id}
550 | :note {:id :id}}
551 | get-named-item (partial edb/get-named-item schema)
552 | db {} ]
553 | (is (nil? (get-named-item db :user :current)))))
554 |
555 |
556 | #_(def schema {:notes {:id :id
557 | :middleware {:set [(fn [item] item)]}
558 | :relations {:user [:one :users]
559 | :links [:many :links]}
560 | :schema {}}
561 | :links {:id :id}
562 | :users {:id :id}})
563 |
564 | (deftest get-relation-path
565 | (is (= (edb/get-relation-path schema :notes :user {:id 1})
566 | [:notes 1 :user]))
567 | (is (= (edb/get-relation-path schema :notes :links {:id 1})
568 | [:notes 1 :links])))
569 |
570 |
571 |
572 |
573 | (deftest manipulating-relation-many []
574 | (let [db (-> {}
575 | (util/add-empty-layout :notes)
576 | (util/add-empty-layout :links))
577 | note {:id 1 :title "Note title"
578 | :links [{:id 1 :url "http://google.com"}
579 | {:id 2 :url "http://bing.com"}]}
580 | db-with-items (edb/insert-item schema db :notes note)
581 | expected-db {:notes {:store {1 {:id 1 :title "Note title"}}
582 | :c-one {}
583 | :c-many {}}
584 | :links {:store {1 {:id 1 :url "http://google.com"}
585 | 2 {:id 2 :url "http://bing.com"}}
586 | :c-one {}
587 | :c-many {[:notes 1 :links] [1 2]}}}
588 | expected-db-after-append {:notes {:store {1 {:id 1 :title "Note title"}}
589 | :c-one {}
590 | :c-many {}}
591 | :links {:store {1 {:id 1 :url "http://google.com"}
592 | 2 {:id 2 :url "http://bing.com"}
593 | 3 {:id 3 :url "http://altavista.com"}}
594 | :c-one {}
595 | :c-many {[:notes 1 :links] [1 2 3]}}}
596 | expected-db-after-prepend {:notes {:store {1 {:id 1 :title "Note title"}}
597 | :c-one {}
598 | :c-many {}}
599 | :links {:store {1 {:id 1 :url "http://google.com"}
600 | 2 {:id 2 :url "http://bing.com"}
601 | 4 {:id 4 :url "http://lycos.com" }}
602 | :c-one {}
603 | :c-many {[:notes 1 :links] [4 1 2]}}}
604 | expected-db-after-remove {:notes {:store {1 {:id 1 :title "Note title"}}
605 | :c-one {}
606 | :c-many {}}
607 | :links {:store {1 {:id 1 :url "http://google.com"}
608 | 2 {:id 2 :url "http://bing.com"}
609 | }
610 | :c-one {}
611 | :c-many {}}}
612 | expected-db-after-manual-insert {:notes {:store {1 {:id 1 :title "Note title"}}
613 | :c-one {}
614 | :c-many {}}
615 | :links {:store {1 {:id 1 :url "http://google.com"}
616 | 2 {:id 2 :url "http://bing.com"}
617 | 4 {:id 4 :url "http://lycos.com" }}
618 | :c-one {}
619 | :c-many {[:notes 1 :links] [4]}}}]
620 | (is (= db-with-items expected-db))
621 | (is (= (edb/get-item-by-id schema db-with-items :links 1) {:id 1 :url "http://google.com"}))
622 | (is (= expected-db-after-append
623 | (edb/append-related-collection schema db-with-items :notes :links {:id 1}
624 | [{:id 3 :url "http://altavista.com"}])))
625 | (is (= expected-db-after-prepend
626 | (edb/prepend-related-collection schema db-with-items :notes :links {:id 1}
627 | [{:id 4 :url "http://lycos.com"}])))
628 | (is (= expected-db-after-remove
629 | (edb/remove-related-collection schema db-with-items :notes :links {:id 1})))
630 | (is (= expected-db-after-manual-insert
631 | (edb/insert-related-collection schema db-with-items :notes :links {:id 1} [{:id 4 :url "http://lycos.com"}])))))
632 |
633 |
634 | (deftest inserting-empty-collection-will-remove-existing-data
635 | (let [db (-> {}
636 | (util/add-empty-layout :notes))
637 | db-with-items (edb/insert-collection schema db :notes :list [{:id 1} {:id 2}])
638 | expected-db {:notes {:store {1 {:id 1} 2 {:id 2}}
639 | :c-many {:list [1 2]}
640 | :c-one {}}}
641 | expected-db-after-inserting-empty {:notes {:store {1 {:id 1} 2 {:id 2}}
642 | :c-many {}
643 | :c-one {}}}]
644 | (is (= db-with-items expected-db))
645 | (is (= (edb/insert-collection schema db-with-items :notes :list [])
646 | expected-db-after-inserting-empty))))
647 |
648 | (deftest inserting-empty-collection-to-relation-will-remove-existing-data
649 | (let [db (-> {}
650 | (util/add-empty-layout :notes)
651 | (util/add-empty-layout :links))
652 | note {:id 1 :title "Note title"
653 | :links [{:id 1 :url "http://google.com"}
654 | {:id 2 :url "http://bing.com"}]}
655 | db-with-items (edb/insert-item schema db :notes note)
656 | expected-db {:notes {:store {1 {:id 1 :title "Note title"}}
657 | :c-one {}
658 | :c-many {}}
659 | :links {:store {1 {:id 1 :url "http://google.com"}
660 | 2 {:id 2 :url "http://bing.com"}}
661 | :c-one {}
662 | :c-many {[:notes 1 :links] [1 2]}}}
663 | expected-db-after-inserting-empty-relation
664 | {:notes {:store {1 {:id 1 :title "Note title"}}
665 | :c-one {}
666 | :c-many {}}
667 | :links {:store {1 {:id 1 :url "http://google.com"}
668 | 2 {:id 2 :url "http://bing.com"}}
669 | :c-one {}
670 | :c-many {}}}]
671 | (is (= db-with-items expected-db))
672 | (is (= (edb/insert-item schema db-with-items :notes {:id 1 :links []})
673 | expected-db-after-inserting-empty-relation))))
674 |
675 | (deftest resolve-relations
676 | (let [db (-> {}
677 | (util/add-empty-layout :notes)
678 | (util/add-empty-layout :links)
679 | (util/add-empty-layout :users))
680 | note {:id 1
681 | :title "Note title"
682 | :links [{:id 1}]
683 | :user {:id 1}}
684 | user {:id 1 :username "Retro"}
685 | link {:id 1 :url "http://google.com" :user {:id 1}}
686 | schema (-> schema
687 | (assoc-in [:notes :relations :user] [:one :users])
688 | (assoc :links {:relations {:user [:one :users]}}))
689 | insert-item (partial edb/insert-item schema)
690 | db-with-items (-> db
691 | (insert-item :notes note)
692 | (insert-item :users user)
693 | (insert-item :links link))]
694 | (is (= {:id 1
695 | :title "Note title"
696 | :links [{:id 1 :url "http://google.com" :user {:id 1 :username "Retro"}}]
697 | :user {:id 1 :username "Retro"}}
698 | (edb/get-item-by-id schema db-with-items :notes 1 [:user [:links :user]])))))
699 |
700 | (deftest resolve-relations-for-named-item
701 | (let [db (-> {}
702 | (util/add-empty-layout :notes)
703 | (util/add-empty-layout :links)
704 | (util/add-empty-layout :users))
705 | note {:id 1
706 | :title "Note title"
707 | :links [{:id 1}]
708 | :user {:id 1}}
709 | user {:id 1 :username "Retro"}
710 | link {:id 1 :url "http://google.com" :user {:id 1}}
711 | schema (-> schema
712 | (assoc-in [:notes :relations :user] [:one :users])
713 | (assoc :links {:relations {:user [:one :users]}}))
714 | insert-item (partial edb/insert-item schema)
715 | insert-named-item (partial edb/insert-named-item schema)
716 | db-with-items (-> db
717 | (insert-named-item :notes :current note)
718 | (insert-item :users user)
719 | (insert-item :links link))]
720 | (is (= {:id 1
721 | :title "Note title"
722 | :links [{:id 1 :url "http://google.com" :user {:id 1 :username "Retro"}}]
723 | :user {:id 1 :username "Retro"}}
724 | (edb/get-named-item schema db-with-items :notes :current false [:user [:links :user]])))))
725 |
726 |
727 | (deftest resolve-relations-for-collection
728 | (let [db (-> {}
729 | (util/add-empty-layout :notes)
730 | (util/add-empty-layout :links)
731 | (util/add-empty-layout :users))
732 | note {:id 1
733 | :title "Note title"
734 | :links [{:id 1}]
735 | :user {:id 1}}
736 | user {:id 1 :username "Retro"}
737 | link {:id 1 :url "http://google.com" :user {:id 1}}
738 | schema (-> schema
739 | (assoc-in [:notes :relations :user] [:one :users])
740 | (assoc :links {:relations {:user [:one :users]}}))
741 | insert-item (partial edb/insert-item schema)
742 | insert-collection (partial edb/insert-collection schema)
743 | db-with-items (-> db
744 | (insert-collection :notes :list [note])
745 | (insert-item :users user)
746 | (insert-item :links link))]
747 | (is (= [{:id 1
748 | :title "Note title"
749 | :links [{:id 1 :url "http://google.com" :user {:id 1 :username "Retro"}}]
750 | :user {:id 1 :username "Retro"}}]
751 | (edb/get-collection schema db-with-items :notes :list [:user [:links :user]])))))
752 |
--------------------------------------------------------------------------------
/test/entitydb/test.cljs:
--------------------------------------------------------------------------------
1 | (ns entitydb.test.test
2 | (:require [doo.runner :refer-macros [doo-tests]]
3 | [cljs.test :as test]
4 | [entitydb.test.core]))
5 |
6 | (doo-tests 'entitydb.test.core)
--------------------------------------------------------------------------------