This repository has been archived by the owner on Aug 9, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,029 additions
and
514 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ pom.xml.asc | |
/.lein-* | ||
/.nrepl-port | ||
*.iml | ||
/doc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,236 @@ | ||
# spec-tacular | ||
# <a href="https://github.com/SparkFund/spec-tacular"><img src="https://avatars2.githubusercontent.com/u/7240335?v=3&s=200"></a>spec-tacular | ||
|
||
Write spectacular data definitions! | ||
Write spectacular data definitions! Our goal is to make the border | ||
between Clojure and Datomic a more convenient and safe place to live. | ||
[Browse the API](http://sparkfund.github.io/spec-tacular) or continue | ||
scrolling. | ||
|
||
Define your Datomic schemas using spec-tacular's spec DSL and receive | ||
the following in return: | ||
|
||
* **Representation of Datomic entities as maps** that verify (upon | ||
creation and association) that entity attributes have the correct | ||
fields, and in turn, the correct types | ||
|
||
* **Core Typed aliases for each spec** | ||
|
||
* **Specialized query language** with a map-like syntax that allows | ||
queries to be expressed with domain-specific spec keywords instead | ||
of Datomic attribute-keywords. Entities returned from queries are | ||
lazily constructed and can be used in typed code without extra | ||
casts. | ||
|
||
* **Simple transaction interface with Datomic**, using `create!` as a | ||
constructor, and `assoc!` as an update function. | ||
|
||
***WARNING:*** spec-tacular is under active development, and makes no | ||
claims of stability. It currently anticipates single-threaded, | ||
single-peer interactions with Datomic, and may act strangely if | ||
either of those invariants are broken. | ||
|
||
## Quick Start | ||
|
||
```clojure | ||
[spec-tacular "0.5.0"] ;; unstable | ||
``` | ||
|
||
```xml | ||
<dependency> | ||
<groupId>spec-tacular</groupId> | ||
<artifactId>spec-tacular</artifactId> | ||
<version>0.5.0</version> | ||
</dependency> | ||
``` | ||
|
||
## Usage | ||
|
||
FIXME | ||
### Creating Specs | ||
|
||
```clojure | ||
(require '[spark.spec-tacular :as sp :refer [defspec defunion]]) | ||
``` | ||
|
||
```clojure | ||
|
||
;; Sets up a House entity containing a mandantory color and optionally | ||
;; a Mailbox. It may also link in any number of Occupants. | ||
(defspec House | ||
(:link [occupants :is-many :Occupant]) | ||
[mailbox :is-a :Mailbox] | ||
[color :is-a :string :required]) | ||
|
||
(defspec Mailbox | ||
[has-mail? :is-a :boolean]) | ||
|
||
;; Houses can be occupied by either People or Pets. | ||
(defunion Occupant :Person :Pet) | ||
|
||
;; Each Person has a name that serves as an identifying field | ||
;; (implemented as Datomic's notion of identity), and an age. | ||
(defspec Person | ||
[name :is-a :string :identity :unique] | ||
[age :is-a :long]) | ||
|
||
(defunion Pet :Dog :Cat :Porcupine) | ||
|
||
(defspec Dog | ||
[fleas? :is-a :boolean]) | ||
|
||
;; Cats can contain links (passed by reference to the database) to all | ||
;; the occupants of the house that they hate. For their nefarious | ||
;; plots, no doubt. | ||
(defspec Cat | ||
[hates :is-many :Occupant :link]) | ||
|
||
(defspec Porcupine) ;; No fields, porcupines are boring | ||
``` | ||
|
||
### Creating Databases | ||
```clojure | ||
(require '[spark.spec-tacular.schema :as schema]) | ||
``` | ||
|
||
```clojure | ||
;; Returns a schema with entries for each spec defined in my-ns | ||
(schema/from-namespace *ns*) | ||
;; => ({:db/id ...., | ||
;; :db/ident :house/occupants, | ||
;; :db/valueType :db.type/ref, | ||
;; :db/cardinality :db.cardinality/many, | ||
;; ....} | ||
;; ....) | ||
|
||
;; Creates a database with the earlier schema installed. | ||
;; Returns a connection to that database. | ||
(schema/to-database! (schema/from-namespace *ns*)) | ||
;; => #<LocalConnection datomic.peer.LocalConnection@....> | ||
``` | ||
|
||
### Changing Databases | ||
```clojure | ||
(require '[spark.spec-tacular.datomic :as sd]) | ||
``` | ||
|
||
```clojure | ||
;; Use the House schema to create a database and connection | ||
(def conn-ctx {:conn (schema/to-database! (schema/from-namespace *ns*))}) | ||
|
||
;; Create a red house: | ||
(def h (sd/create! conn-ctx (house {:color "Red"}))) | ||
|
||
;; Some quick semantics: | ||
(:color h) ;; => "Red" | ||
(= h (house {:color "Red"})) ;; => false | ||
(sp/refless= h (house {:color "Red"})) ;; => true | ||
(assoc h :random-kw 42) ;; => error | ||
(set [h h]) ;; => #{h} | ||
(set [h (house {:color "Red"})]) ;; => #{h (house {:color "Red"})} | ||
|
||
;; Let some people move in: | ||
(def joe (sd/create! conn-ctx (person {:name "Joe" :age 32}))) | ||
(def bernard (sd/create! conn-ctx (person {:name "Bernard" :age 25}))) | ||
|
||
(def new-h (sd/assoc! conn-ctx h :occupants [joe bernard])) | ||
;; => assoc! returns a new House with the new field | ||
|
||
h ;; => is still the simple red house | ||
(sd/refresh conn-ctx h) ;; => new-h | ||
;; In most cases, you can forego the `refresh` and just use the return | ||
;; value of `assoc!` | ||
|
||
;; Bernard and Joe get a cat, who hates both of them, | ||
(def zuzu (sd/create! conn-ctx (cat {:hates (:occupants new-h)}))) | ||
(sd/assoc! conn-ctx h :occupants (conj (:occupants new-h) zuzu)) | ||
|
||
;; They build a mailbox, and try to put it up in another House: | ||
(let [mb (mailbox {:has-mail? false}) | ||
h1 (sd/assoc! conn-ctx h :mailbox mb) | ||
h2 (sd/create! conn-ctx (house {:color "Blue" :mailbox mb}))] | ||
;; But since Mailboxes are passed by value, | ||
;; the Mailbox get duplicated | ||
(= (:mailbox h1) (:mailbox h2)) ;; => false | ||
....) | ||
```` | ||
|
||
### Querying Databases | ||
```clojure | ||
(require '[spark.spec-tacular.datomic :as sd]) | ||
``` | ||
|
||
```clojure | ||
|
||
;; First let's distinguish the mailboxes -- let's say Joe and Bernard | ||
;; get some mail | ||
(def mb1 (sd/assoc! conn-ctx (:mailbox h1) :has-mail? true)) | ||
|
||
;; Get the database | ||
(def db (sd/db conn-ctx)) | ||
|
||
;; Use % to look for the only find variable | ||
(sd/q :find [:Mailbox ...] :in db :where [% {:has-mail? false}]) | ||
;; => #{(:mailbox h2)}, the mailbox from house h2 | ||
(sd/q :find [:Mailbox ...] :in db :where [% {:has-mail? true}]) | ||
;; => #{mb1}, that's Joe and Bernard's mailbox | ||
|
||
;; Find the Houses without mail | ||
(sd/q :find [:House ...] :in db :where | ||
[% {:mailbox {:has-mail false}}]) | ||
;; => #{h2} | ||
|
||
;; Find the House and it's human occupants when the mailbox has mail | ||
;; Use %1 and %2 to to look for multiple find variables | ||
(sd/q :find :House :Person :in db :where | ||
[%1 {:occupants %2 :mailbox {:has-mail true}}]) | ||
;; => #{[h1 joe] [h2 bernard]} | ||
``` | ||
|
||
This last example means we're looking for any `:occupants` that are | ||
`:Person`s. Even though we represent Datomic's cardinality "many" as | ||
a collection in Clojure, we still use a relation to search for members | ||
of that collection on the database. Those familiar with Datomic may | ||
understand that this part of the query (roughly) expands to | ||
|
||
```clojure | ||
[.... [?house :house/occupants ?person] ....] | ||
``` | ||
|
||
When we get the result of the query back in Clojure, we take that | ||
result and return it as a set. Onwards! | ||
|
||
```clojure | ||
|
||
;; If you want to get the spec name of entities on the database, you | ||
;; can use the special :spec-tacular/spec keyword. Here we restrict | ||
;; the occupants to the :Pet spec and then return all kinds of Pet's | ||
;; that live in houses: | ||
(sd/q :find [:string ...] :in db :where | ||
[:House {:occupants [:Person {:name %}]}]) | ||
;; => #{"Joe" "Bernard"} | ||
|
||
``` | ||
|
||
Although maps work as you would expect in a query, the vector form | ||
`[<spec> <map>]` is protected syntax meaning the `map` should be | ||
restricted to things of type `<spec>`. | ||
|
||
## Updating from v.0.4.x | ||
|
||
* Replace all `spark.sparkspec` namespaces with `spark.spec-tacular` | ||
* Check all calls to `=` to see if `refless=` is more appropriate | ||
* Check all `set`s if you mix local instances and instances on the | ||
database; these are nolonger `=` nor do they hash to the same number | ||
even if they are otherwise equivalent. | ||
* Rename `defenum` to `defunion` | ||
|
||
## Short Term Roadmap | ||
|
||
* Create `defenum` that mirrors Datomic's enumerations | ||
* Create `:component` spec option | ||
* Create `defattr` that can be used as a field type to allow shared | ||
Datomic namespaces between fields of different specs | ||
|
||
## License | ||
|
||
Copyright © 2014 SparkFund Community Investment | ||
Copyright © 2014-2015 [Spark Community Investment](https://www.sparkfund.co) | ||
|
||
Distributed under the Apache License Version 2.0 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,59 @@ | ||
(defproject spec-tacular "0.4.24" | ||
:description "First-class, extendable data specifications for clojure." | ||
(defproject spec-tacular "0.5.0" | ||
:description "First-class data specifications for Clojure and Datomic." | ||
:url "https://github.com/SparkFund/spec-tacular" | ||
:license {:name "Apache License, Version 2.0" | ||
:url "http://www.apache.org/licenses/LICENSE-2.0.html"} | ||
:dependencies [[org.clojure/clojure "1.6.0"] | ||
[com.datomic/datomic-free "0.9.5130" | ||
:exclusions [joda-time]] | ||
[prismatic/schema "0.2.4"] | ||
[io.pedestal/pedestal.jetty "0.3.0"] | ||
[io.pedestal/pedestal.service "0.3.0"] | ||
[io.pedestal/pedestal.service-tools "0.3.0"] | ||
[org.clojure/test.check "0.7.0"] | ||
[ring/ring-core "1.3.0"] | ||
[clj-http "0.9.1"] | ||
[clojure-csv/clojure-csv "2.0.1"] | ||
[org.clojure/tools.macro "0.1.2"] | ||
[org.clojure/core.typed "0.2.77"] | ||
[org.immutant/immutant "1.1.3"] | ||
[org.clojure/core.match "0.3.0-alpha4"]] | ||
[org.clojure/core.match "0.3.0-alpha4" | ||
:exclusions [org.ow2.asm/asm-all | ||
org.clojure/tools.analyzer | ||
org.clojure/tools.reader | ||
org.clojure/tools.analyzer.jvm]] | ||
[clj-time "0.9.0"] | ||
|
||
;; Restify | ||
[clojure-csv/clojure-csv "2.0.1"] | ||
[io.pedestal/pedestal.jetty "0.3.0" | ||
:exclusions [ch.qos.logback/logback-classic]] | ||
[io.pedestal/pedestal.service "0.3.0" | ||
:exclusions [commons-codec | ||
org.clojure/tools.reader | ||
org.slf4j/slf4j-api | ||
ch.qos.logback/logback-classic]] | ||
[io.pedestal/pedestal.service-tools "0.3.0" | ||
:exclusions [org.slf4j/log4j-over-slf4j | ||
org.slf4j/jul-to-slf4j | ||
org.slf4j/jcl-over-slf4j | ||
org.clojure/tools.reader | ||
ch.qos.logback/logback-classic | ||
commons-codec]] | ||
[org.immutant/immutant "1.1.3" | ||
:exclusions [commons-codec | ||
org.jgroups/jgroups | ||
org.jboss.logging/jboss-logging]] | ||
[clj-http "0.9.1" | ||
:exclusions [commons-codec | ||
potemkin]] | ||
[ring/ring-core "1.3.0" | ||
:exclusions [commons-codec]]] | ||
:plugins [[lein-typed "0.3.5"]] | ||
:test-selectors {:default (complement :loud) | ||
:loud :loud | ||
:all (constantly true)} | ||
:core.typed {:check [spark.sparkspec.datomic | ||
spark.sparkspec.schema | ||
spark.sparkspec.test-specs | ||
spark.sparkspec.typecheck-test]}) | ||
:core.typed {:check [spark.spec-tacular.schema | ||
spark.spec-tacular.typecheck-test]} | ||
:codox {:defaults {:doc/format :markdown} | ||
:sources ["src" "test"] | ||
:include [spark.spec-tacular | ||
spark.spec-tacular.datomic | ||
spark.spec-tacular.schema | ||
spark.spec-tacular.generators] | ||
:src-dir-uri "https://github.com/SparkFund/spec-tacular/tree/develop/" | ||
:src-linenum-anchor-prefix "L"} | ||
:pedantic? :abort) |
Oops, something went wrong.