diametric 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- N2Y1ODQ4NGMzNjA0ZjY3ODIzNDk2NjIwMGUxNDA4YjViYjM2ZmE5Mg==
4
+ N2Q2MmI3ZGUwNDFjNzMyMTkxYmViN2ZhZDBkNzEyMTcwNDc4YjJlMw==
5
5
  data.tar.gz: !binary |-
6
- NTA1YWRhN2YwM2M0Y2RhOGJlN2NmMGNlMTZmY2E4NWQwZjE2OTc2Nw==
6
+ YmEyYzllZTcwMTJjNzA5OWFhMmZlYWE5ZGRjNjI2NDQ1MmEzZjdlNw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YjA4N2I5ZWNkMWZiZTE4NDlkODRmMTYwMjM2ZGNlYjEzNzVlZTY5ZWMzYWM0
10
- MjcyZWZjMGYyNWNjMzZjMTNiYTRmODI0ZGJiMjlhMDIyMmEyMWM0M2RiYjhl
11
- MjA1NzdiYTdjMDExYmZhYmZlNzlkMWI4YjQ1NWEwODIxYzQzZDc=
9
+ YmY3NWU5MmI2ZGJlZWFmMTIyZjU4MjAwYmI0OGI2ZTMyNmIzZmM3YmE5YzY1
10
+ ZjMwYjgwNmJmYmJjMzQ3OTQyMWU3NDI2YThlMzE0Zjk1MWU1NzE4OTNhZTUz
11
+ YjRiYjczMTA3NTViMDE2OTkwMmMwMWIwNmViYWI5ZWYzNDY4OWE=
12
12
  data.tar.gz: !binary |-
13
- N2U0NzQ0NjM2YWRkMjU2NTM4ODM2Njc3NWY0NzVlOTVlMDMzYWVmNmI4MTI5
14
- ZGUzNDFmZjQ3ZGFhNWZmMzIzMWJiNjNlMWI1MmUxNzJhZjgxOTAwODFkNTQ1
15
- YzBiZTJlYTcxOTU2MmY5MTVkZWU1N2YzYWFmMTY1MDU3OWQwN2U=
13
+ NDdkN2FiZjU5OWYzNzk0Mjk5YmFmY2Q4YjMwMmRkNmYxZDQzZTRhMzdiNjg0
14
+ MDE5ZDRiMDU5MWVkZTU2OGIyZDRiNjM0OGQ5ZmIxYjRiODg5YzhmMDZhM2M3
15
+ OTBmZDMzYWViMTljY2RkNWExZGYyYmM4N2ZiNTZmMWU0NTE0ZWM=
data/README.md CHANGED
@@ -2,241 +2,689 @@
2
2
 
3
3
  # Diametric
4
4
 
5
- Diametric is a library for building schemas, queries, and transactions
6
- for [Datomic][] from Ruby objects. It is also used to map Ruby objects
5
+ Diametric is ActiveRecord style wrapper for [Datomic](http://www.datomic.com/),
6
+ and a library for building schemas, queries, and transactions
7
+ for Datomic from Ruby objects. It is also used to map Ruby objects
7
8
  as entities into a Datomic database.
8
9
 
9
- ## Thanks
10
10
 
11
- Development of Diametric was sponsored by [Relevance][]. They are the
12
- best Clojure shop around and one of the best Ruby shops. I highly
13
- recommend them for help with your corporate projects.
11
+ Diametric supports both CRuby and JRuby.
12
+ When you are on CRuby, you can use Datomic's REST service.
13
+ While you are creating data and making queries, Diametric is poking around Datomic's REST API.
14
+ When you are on JRuby, you can use Datomic's REST and Peer services.
15
+ If you are using Peer service, Diametric is poking Datomic's clojure API and some clojure functions.
16
+
17
+
18
+ For Rubyists who are familiar with object oriented programming, Diametric converts:
19
+ - Ruby model definition to Datomic schema
20
+ - Creating, saving and updating model data to Datomic transaction data
21
+ - Ruby query to Datomic's datalog query
22
+
23
+
24
+ The biggest difference between REST and Peer services is available database types.
25
+ REST service can use memory database only, which means you can't save data in any file.
26
+ While Peer service can use memory and free version of transactor,
27
+ which means you can save data in a file through Datomic's transactor.
28
+ Other than that, Peer service has excellent API to use Datomic's various features.
14
29
 
15
- Special thanks to Mongoid for writing some solid ORM code that was liberally borrowed from to add Rails support to Diametric.
16
30
 
17
31
  ## Documents
18
32
 
19
33
  Other than highlights below, there are documents on Wiki.
20
34
 
21
- - [Getting Started](https://github.com/relevance/diametric/wiki/Getting-Started)
35
+ - [Entity API](https://github.com/relevance/diametric/wiki/Entity-API)
36
+ - [Query API](https://github.com/relevance/diametric/wiki/Query-API)
37
+ - [Persistence API](https://github.com/relevance/diametric/wiki/Persistence-API)
22
38
  - [Rails Integration](https://github.com/relevance/diametric/wiki/Rails-Integration-%28Experimental%29)
23
39
  - [Seattle Example](https://github.com/relevance/diametric/wiki/Seattle-Example)
24
40
 
25
41
 
26
- ## Entity API
42
+ This document shows how to use Diametric with simple examples.
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ gem install diametric
48
+ ```
49
+
50
+ Diametric is a multi-platoform gem. On CRuby, above installs CRuby version of gem.
51
+ On JRuby, it is JRuby version of gem with Java extension.
52
+
53
+
54
+ [note]
55
+ When you install diametric on JRuby, you'll see a message, "Building native extensions. This could take a while...."
56
+ Although Diametric doesn't rely on C library, it depends on a several Java libraries.
57
+ While installing gem, diametric downloads thoes jar archives including dependencies on your local maven repo.
58
+ The message shows up because of this.
59
+
60
+
61
+ ## Preparation for CRuby
62
+
63
+ On CRuby, you need to start Datomic's REST server.
64
+
65
+ Diametric has a command to start REST server.
66
+ Type `datomic-rest -p port -a db_alias -u uri`, for example
67
+
68
+ ```
69
+ datomc-rest -p 9000 -a free -u datomic:mem://
70
+ ```
71
+
72
+ When you run the command at the very first time, it takes long to start running.
73
+ This is because Diametric downloads datomic if it doesn't exist in Diametric's directory tree.
74
+ If it is the second time or later, the REST server starts quickly.
75
+
76
+ To learn more about the options in the above command, please go to [http://docs.datomic.com/rest.html](http://docs.datomic.com/rest.html).
77
+
78
+ Once the REST server starts running, go to [localhost:9000](localhost:9000) on your browser.
79
+ You can see Datomic's REST service is running.
80
+
81
+ Alternatively, you can download Datomic archive from [http://downloads.datomic.com/free.htm](http://downloads.datomic.com/free.html),
82
+ and start REST server using Datomic command, `script/datomic-rest -p 9000 free datomic:mem://`
83
+
84
+
85
+ ## Preparation for JRuby
86
+
87
+ You have 2 choices on JRuby, Peer and REST service.
88
+ Peer service works on the same JVM as JRuby and is just a Java library for JRuby.
89
+
27
90
 
28
- The `Entity` module is interesting, in that it is primarily made of
29
- pure functions that take their receiver (an instance of the class they
30
- are included in) and return data that you can use in Datomic. This
31
- makes it not an ORM-like thing at all, but instead a Ruby-ish data
32
- builder for Datomic. And yet, a `Diametric::Entity` is fully
33
- `ActiveModel` compliant! You can use them anywhere you would use an
34
- `ActiveRecord` model or another `ActiveModel`-compliant instance.
91
+ When you choose REST service, follow *Preparation for CRuby* section.
92
+
93
+
94
+ When you choose Peer service, you don't need to prepare anything.
95
+ You even don't need to start Datomic. Diametric does everything for you.
96
+ What you need to do is just coding in Ruby using Diametric API.
97
+
98
+
99
+ Although Diametric API makes coding very easy, you still have a freedom to hit Datomic's Java API directly.
100
+
101
+
102
+ ## Typical coding steps
103
+
104
+ Typical Diametric coding takes steps below:
105
+
106
+ 1. Connect to Datomic
107
+ 2. Define entities
108
+ 3. Create schema on Datomic from entity definitions
109
+ 4. Create entities and save it on Datomic
110
+ 5. Make a query to Datomic
111
+
112
+
113
+ ## Connect to the Datomic REST Service
114
+
115
+ To establish connection to Datomic REST Service, you can do as in below:
116
+
117
+ ```ruby
118
+ require 'diametric'
119
+ Diametric::Persistence.establish_base_connection({:uri => 'http://localhost:9000', :storage => 'free', :database => 'sample'})
120
+ ```
121
+
122
+ Optionally, you can connect using datomic-clinet gem:
123
+ ```ruby
124
+ require 'datomic/client'
125
+
126
+ @datomic_uri = 'http://localhost:9000'
127
+ @storage = 'free'
128
+ @dbname = "sample"
129
+ @client = Datomic::Client.new @datomic_uri, @storage
130
+ @client.create_database(@dbname)
131
+ ```
132
+
133
+ Each parameter should be consistent to the ones used to start REST service.
134
+
135
+
136
+ ## Connect to the Datomic Peer service (JRuby only)
137
+
138
+ ```ruby
139
+ require 'diametric'
140
+ Diametric::Persistence.establish_base_connection({:uri=>'datomic:mem://sample'})
141
+ ```
142
+
143
+ Or in a peer service specific way,
144
+ ```ruby
145
+ datomic_uri = "datomic:mem://sample-#{SecureRandom.uuid}"
146
+ @conn = Diametric::Persistence::Peer.connect(datomic_uri)
147
+ ```
148
+
149
+ ## Define an entity
150
+
151
+ While a relational databse inserts a record, Diametric saves `Entity`.
152
+ To save data by Diametric, you need to define `Entity` first.
153
+ Defining entities loosely corresponds to defining database tables.
154
+
155
+ Below is an example of `Entity` definition for Peer service.
156
+ Be aware, you should include `Diametric::Entity` and
157
+ either one of `Diametric::Persistence::REST` or `Diametric::Persistence::Peer`in your Entity definition.
35
158
 
36
- They do not include all `ActiveModel` modules by default, only the
37
- ones needed to establish compliance. You may want to include others
38
- yourself, such as `Validations` or `Callbacks`.
39
159
 
40
160
  ```ruby
41
161
  require 'diametric'
42
162
 
43
163
  class Person
44
164
  include Diametric::Entity
165
+ include Diametric::Persistence::Peer
45
166
 
46
- attribute :name, String, :index => true
47
- attribute :email, String, :cardinality => :many
167
+ attribute :name, String, index: true
48
168
  attribute :birthday, DateTime
49
- attribute :iq, Integer
169
+ attribute :awesomeness, Boolean, doc: "Is this person awesome?"
50
170
  end
171
+ ```
172
+
173
+ The `attribute` definition is consists of:
174
+ - attribute name (required)
175
+ - attribute type (required)
176
+ - attribute options (optional)
177
+
178
+ This is a Diametric way of converting [Datomic schema](http://docs.datomic.com/schema.html)
179
+ to Ruby model definition.
180
+
181
+ The attribute name goes to `:db/ident` and the value will be `:<class name>/<attribute name>`.
182
+ This is interpreted as `:<namespace>/<name>` on Datomic.
183
+
184
+ Currently, Diametric supports following data types:
185
+
186
+ | Diametric | Datomic |
187
+ | ---------- |:------------------:|
188
+ | String | `:db.type/string` |
189
+ | Symbol | `:db.type/keyword` |
190
+ | Boolean | `:db.type/boolean` |
191
+ | Integer | `:db.type/long` |
192
+ | Float | `:db.type/double` |
193
+ | Double | `:db.type/double` |
194
+ | BigDecimal | `:db.type/bigdec` |
195
+ | Ref | `:db.type/ref` |
196
+ | DateTime | `:db.type/instant` |
197
+ | UUID | `:db.type/uuid` |
198
+
199
+ In addition, Diametric supports `Enum` type.
200
+
201
+ Avaialbe options are:
202
+
203
+ | Diametric | Datomic |
204
+ | -------------------------------- |:----------------------:|
205
+ | `:cardinality => :one` (default) | `:db.cardinality/one` |
206
+ | `:cardinality => :many` | `:db.cardinality/many` |
207
+ | `:doc => "some document here"` | `:db/doc` |
208
+ | `:unique => :identity` | `:db.unique/identity` |
209
+ | `:unique => :value` | `:db.unique/value` |
210
+ | `:index => true` | `:db/index` |
211
+ | `:fulltext => true` | `:db/fulltext` |
212
+
51
213
 
52
- # To see what schema data will be produced:
53
- Person.schema
54
-
55
- # The schema will be mapped to Datomic transaction data below:
56
- # [{:db/id #db/id[:db.part/db]
57
- # :db/ident :person/name
58
- # :db/valueType :db.type/string
59
- # :db/cardinality :db.cardinality/one
60
- # :db/index true
61
- # :db.install/_attribute :db.part/db}
62
- # {:db/id #db/id[:db.part/db]
63
- # :db/ident :person/email
64
- # :db/valueType :db.type/string
65
- # :db/cardinality :db.cardinality/many
66
- # :db.install/_attribute :db.part/db}
67
- # {:db/id #db/id[:db.part/db]
68
- # :db/ident :person/birthday
69
- # :db/valueType :db.type/instant
70
- # :db/cardinality :db.cardinality/one
71
- # :db.install/_attribute :db.part/db}
72
- # {:db/id #db/id[:db.part/db]
73
- # :db/ident :person/iq
74
- # :db/valueType :db.type/long
75
- # :db/cardinality :db.cardinality/one
76
- # :db.install/_attribute :db.part/db}]
77
-
78
- # To check what attributes are in Person model:
79
- Person.attributes.keys
80
- # [:dbid, :name, :email, :birthday, :iq]
81
-
82
- # To create an instance from a query result
83
- person = Person.new(Hash[*(Person.attributes.zip(results_from_query).flatten)])
84
- # or
85
- person = Person.from_query(results_from_query)
86
-
87
- # To update/set values of a model:
88
- person.iq = 180
89
-
90
- # Below will help to understand what transaction data will be produced:
91
- person.tx_data(:iq)
92
-
93
- # It will be mapped to the Datomic transaction data:
94
- # [{:db/id person.dbid
95
- # :person/iq 180}]
96
-
97
- # To create new model instance:
98
- person = Person.new(:name => "Peanut")
99
- person.tx_data
100
- # Datomic transaction data will be:
101
- # [{:db/id #db/id[:db.part/user]
102
- # :person/name "Peanut"}]
103
- ```
104
-
105
- In addition to `attribute`, Diametric supports `enum` type. This is often used on Datomic.
106
- For details, see [Seattle Example](https://github.com/relevance/diametric/wiki/Seattle-Example)
107
-
108
- ## Query API
109
-
110
- The query API is used for generating Datomic queries, whether to send via an external client or via the persistence API. The two methods used to generate a query are `.where` and `.filter`, both of which are chainable.
111
-
112
- To get query data and args for a query, call `.data` on a `Query`.
113
-
114
- If you are using a persistence API, you can ask `Query` to get the results of a Datomic query. `Diametric::Query` is an `Enumerable`. To get the results of a query, use `Enumerable` methods such as `.each` or `.first`. `Query` also provides a `.all` method to run the query and get the results.
115
-
116
- ```ruby
117
- query = Datomic::Query.new(Person).where(:name => "Clinton Dreisbach")
118
- query.data
119
- # Datomic query:
120
- # [:find ?e ?name ?email ?birthday ?iq ?website
121
- # :from $ ?name
122
- # :where [?e :person/name ?name]
123
- # [?e :person/email ?email]
124
- # [?e :person/birthday ?birthday]
125
- # [?e :person/iq ?iq]
126
- # [?e :person/website ?website]]
127
- # Args:
128
- # ["Clinton Dreisbach"]
129
- #
130
- # Returns as an array, [query, args].
131
-
132
- query = Datomic::Query.new(Person).where(:name => "Clinton Dreisbach").filter(:>, :iq, 150)
133
- query.data
134
- # Datomic query:
135
- # [:find ?e ?name ?email ?birthday ?iq ?website
136
- # :from $ ?name
137
- # :where [?e :person/name ?name]
138
- # [?e :person/email ?email]
139
- # [?e :person/birthday ?birthday]
140
- # [?e :person/iq ?iq]
141
- # [?e :person/website ?website]
142
- # [> ?iq 150]
143
- # Args:
144
- # ["Clinton Dreisbach"]
145
- #
146
- # Returns as an array, [query, args].
147
- ```
148
-
149
- ## Persistence API
150
-
151
- The persistence API comes in two flavors: REST- and Peer-based.
152
- Although REST-bases API supports CRUD operations like a legacy RDBMS,
153
- it is a subset of Peer-based API.
154
-
155
- The suitable persistent module will be selected based on URI to connect datomic.
156
- You don't need to care which module should be included.
157
- However, if you like to inlcude REST or Peer module explicitely, you can write it.
158
-
159
-
160
- ### Peer
161
-
162
- With Peer connection, you can create objects that know how to store themselves to Datomic through a Datomic Peer.
163
-
164
- Peer connection as well as "require `diametric/persistence/peer`" are only available on JRuby.
165
- When you install the `diametric` gem with JRuby, all `.jar` files needed to run Datomic will be downloaded.
214
+ ## Create a schema
215
+
216
+ Creating schema on Datomic is similar to a migration on Ruby on Rails.
217
+ However, schema creation can be done as a part of a program.
218
+ For Datomic, the difference between schema and entity is quite small.
219
+ Like saving entities, schemas are saved in Datomic.
220
+
221
+ Creating schema is done by calling `create_schema` method of the entity.
166
222
 
167
223
  ```ruby
168
- require 'diametric'
169
- require 'diametric/persistence'
224
+ Person.create_schema.get
225
+ ```
226
+
227
+ `Person.create_schema` (not followed by get) also creates a schema,
228
+ but it is a sort of piling up on the queue and not performed immediately.
229
+ If `get` is not specified, schema creation is done by at some point implicitly.
230
+ Adding `get` requests Datomic to create schema *now*.
231
+ This is good to know whether the entity definition is correct or not.
232
+ If some attribute definition has a problem, error will be raised.
233
+
234
+
235
+ ## Create, save and update an entity
236
+
237
+ Once the entity is defined, and schema is created on Datomic,
238
+ you can create an entity instance like you create a new object in Ruby.
239
+ Below is an example:
240
+
241
+
242
+ ```ruby
243
+ person = Person.new
244
+ person.name = "Sky"
245
+ person.birthday = DateTime.parse('2005-01-01')
246
+ person.awesomeness = true
247
+
248
+ puts "dbid (before save): #{person.dbid}"
249
+
250
+ person.save
251
+
252
+ puts "dbid (after save): #{person.dbid}"
253
+ ```
254
+
255
+ Above prints:
170
256
 
171
- # database URI
172
- # will create database if it does not already exist
173
- Diametric::Persistence.establish_base_connection({:uri=>'datomic:mem://sample'})
257
+ ```
258
+ dbid (before save):
259
+ dbid (after save): 17592186045418
174
260
  ```
175
261
 
176
- ### REST
262
+ The *dbid* is equivalent to *id* of ordinary ActiveModel objects.
263
+ Before `save`, person object doesn't have `dbid` value.
264
+ But after `save`, it will have an auto-generated value as dbid.
177
265
 
178
- With REST connection, you can create objects that know how to store themselves to Datomic through the Datomic REST API.
179
- REST connection is available both on CRuby and JRuby.
180
- You need to download Datomic by yourself and start the server before you run the code.
266
+ Alternatively, you can create an entity in one line:
267
+ ```ruby
268
+ Person.new(name: "Sky", birthday: DateTime.parse('2005-01-01'), awesomeness: true).save
269
+ ```
181
270
 
271
+ To update attribue values of the instance, use update_attributes:
182
272
  ```ruby
183
- require 'diametric'
184
- require 'diametric/persistence'
273
+ person.update_attributes(name: "Sky Jr.", awesomeness: false)
274
+ ```
185
275
 
186
- # database url, database alias, database name
187
- # will create database if it does not already exist
188
- Diametric::Persistence.establish_base_connection({:uri => 'http://localhost:9000', :storage => 'free', :database => 'sample'})
276
+
277
+ ## Make queries
278
+
279
+ Queries are done by Diametric::Query instance. A basic usage is:
280
+
281
+
282
+ 1. Create Diametric::Query instance with a required argument.
283
+ 2. Call methods of Query instance. This triggers a query to Datomic.
284
+ 3. Iterate the query result and do something to get data.
285
+
286
+ Diametric::Query has three arguments in constructor:
287
+ ```ruby
288
+ Diametric::Query.new(entity_class, db_or_conn=nil, resolve=false)
289
+ ```
290
+ The second and the third options work only on Peer service.
291
+
292
+
293
+ On REST service, the query results are array of entity objects.
294
+ On Peer service, the result data type depends on the constructor arguments.
295
+ In default setting, the query result is a Set of Arrays.
296
+ Each array has entity id (future Daimetric version will have a couple of attributes depends on the query).
297
+ This data structure is exactly the same as what Datomic returns.
298
+ Also, to avoid overhead comes from converting to Ruby's Set and Arrays,
299
+ Diametric wraps in Diametric::Persistence::Set or Diametric::Persistence::Collection.
300
+ (Diametric's wrappers are not a perfect Ruby Array nor Ruby Set in this version.
301
+ Those will be improved in future versions.)
302
+
303
+ The reason Diametric doesn't create entity instances by defualt is to save memory.
304
+ When dealing with millions or billions of data, saving memory is key to run faster,
305
+ and, more importantly, to avoid OutOfMemoryError.
306
+ In terms of saving memory, Diametric returns minimum by default.
307
+
308
+ If you don't have a lot of data and want to get instances directly from query results,
309
+ set true to the third option, `resolve`.
310
+
311
+ When resolve option is set to true, the query result will be an Array of entity instances.
312
+ You should be careful to set this option to true. When it is true, Diametric tries to
313
+ create instances recursively following associations.
314
+ If friends or friends of friends includs self, you'll likely to get StackOverflowError.
315
+
316
+ To resolve entity from a dbid, use Entity's reify method.
317
+ The reify method creates an instance from the dbid.
318
+
319
+ The second option, `db_or_conn` is to specify the database state at some time in the past.
320
+ If it is not specified, Diametric uses the default database state, which means the latest state.
321
+
322
+ ### find all
323
+ Query example1:
324
+ ```ruby
325
+ # create test data
326
+ Person.new(name: "Sun", birthday: DateTime.parse('2005-01-01'), awesomeness: true).save
327
+ Person.new(name: "Cloud", birthday: DateTime.parse('1980-02-12'), awesomeness: false).save
328
+ Person.new(name: "Breeze", birthday: DateTime.parse('1911-03-23'), awesomeness: true).save
329
+ Person.new(name: "Sleet", birthday: DateTime.parse('2010-04-30'), awesomeness: false).save
330
+ Person.new(name: "Rain", birthday: DateTime.parse('2005-05-05'), awesomeness: true).save
331
+
332
+ # make a query
333
+ query = Diametric::Query.new(Person)
334
+ query.each do |ary|
335
+ person = Person.reify(ary.first)
336
+ puts "name: #{person.name}, dbid: #{person.dbid}, birthday: #{person.birthday}, awesomeness: #{person.awesomeness}"
337
+ end
338
+ ```
339
+
340
+ Above prints:
341
+ ```ruby
342
+ name: Rain, dbid: 17592186045429, birthday: 2005-05-04 20:00:00 -0400, awesomeness: true
343
+ name: Breeze, dbid: 17592186045425, birthday: 1911-03-22 19:00:00 -0500, awesomeness: true
344
+ name: Sleet, dbid: 17592186045427, birthday: 2010-04-29 20:00:00 -0400, awesomeness: false
345
+ name: Sun, dbid: 17592186045421, birthday: 2004-12-31 19:00:00 -0500, awesomeness: true
346
+ name: Cloud, dbid: 17592186045423, birthday: 1980-02-11 19:00:00 -0500, awesomeness: false
347
+ ```
348
+
349
+ Query example2 (with resolve to true):
350
+ ```ruby
351
+ query = Diametric::Query.new(Person, nil, true)
352
+ query.each do |entity|
353
+ puts "name: #{entity.name}, dbid: #{entity.dbid}, birthday: #{entity.birthday}, awesomeness: #{entity.awesomeness}"
354
+ end
355
+ ```
356
+
357
+ The example2 prints exactly the same as the example1.
358
+
359
+
360
+ ### where
361
+
362
+ To narrow down the query results, you can use `where` and `filter` methods.
363
+
364
+ Query example3:
365
+ ```ruby
366
+ query = Diametric::Query.new(Person, nil, true).where(awesomeness: true)
367
+ query.each do |entity|
368
+ puts "name: #{entity.name}, dbid: #{entity.dbid}, birthday: #{entity.birthday}, awesomeness: #{entity.awesomeness}"
369
+ end
370
+ ```
371
+
372
+ Above prints:
373
+ ```ruby
374
+ name: Sun, dbid: 17592186045421, birthday: 2004-12-31 19:00:00 -0500, awesomeness: true
375
+ name: Rain, dbid: 17592186045429, birthday: 2005-05-04 20:00:00 -0400, awesomeness: true
376
+ name: Breeze, dbid: 17592186045425, birthday: 1911-03-22 19:00:00 -0500, awesomeness: true
189
377
  ```
190
378
 
191
- ### Using persisted models
379
+ ### filter
192
380
 
381
+ Query example4:
382
+ ```ruby
383
+ query = Diametric::Query.new(Person, nil, true).filter(:>, :birthday, DateTime.parse('2004-12-31'))
384
+ query.each do |entity|
385
+ puts "name: #{entity.name}, dbid: #{entity.dbid}, birthday: #{entity.birthday}, awesomeness: #{entity.awesomeness}"
386
+ end
387
+ ```
388
+ The results are:
193
389
  ```ruby
194
- class Goat
390
+ name: Sun, dbid: 17592186045418, birthday: 2004-12-31 19:00:00 -0500, awesomeness: true
391
+ name: Sleet, dbid: 17592186045424, birthday: 2010-04-29 20:00:00 -0400, awesomeness: false
392
+ name: Rain, dbid: 17592186045426, birthday: 2005-05-04 20:00:00 -0400, awesomeness: true
393
+ ```
394
+
395
+ Currently, filter supports simple Clojure comparisons.
396
+ For example:
397
+ ```clojure
398
+ (< attribute value)
399
+ (> attribute value)
400
+ (= attribute value)
401
+ ...
402
+ ```
403
+ The first argument of filter is mapped to the predicate of the clojure function.
404
+ The second argument of filter is always an attrbute name,
405
+ which will be replaced by attribute values of entities.
406
+ The third argument of the filter is the value to be compared.
407
+
408
+
409
+ ### chaining
410
+
411
+ Ruby's method chaining can be used for `where` and `filter` methods.
412
+
413
+ Query example5:
414
+ ```ruby
415
+ query = Diametric::Query.new(Person, nil, true).where(awesomeness: true).filter(:>, :birthday, DateTime.parse('2004-12-31'))
416
+ query.all.each do |p|
417
+ puts "name: #{p.name}, dbid: #{p.dbid}, birthday: #{p.birthday}, awesomeness: #{p.awesomeness}"
418
+ end
419
+ ```
420
+ The query finds entities whose awesomenesses are true and whose birthdays are after December 31, 2004.
421
+ As expected, it prints:
422
+ ```ruby
423
+ name: Sun, dbid: 17592186045418, birthday: 2004-12-31 19:00:00 -0500, awesomeness: true
424
+ name: Rain, dbid: 17592186045426, birthday: 2005-05-04 20:00:00 -0400, awesomeness: true
425
+ ```
426
+
427
+ ### short cut methods
428
+
429
+ So far, the query examples use `Diametric::Query.new(...)`.
430
+ Other than that, Diametric supports consice short cut query methods.
431
+ The examples above can be rewritten below.
432
+ In this case, resolve option is set to true by default.
433
+
434
+ ```ruby
435
+ #query = Diametric::Query.new(Person)
436
+ query = Person.all
437
+
438
+ #query = Diametric::Query.new(Person, nil, true).where(awesomeness: true)
439
+ query = Person.where(awesomeness: true)
440
+
441
+ #query = Diametric::Query.new(Person, nil, true).filter(:>, :birthday, DateTime.parse('2004-12-31'))
442
+ query = Person.filter(:>, :birthday, DateTime.parse('2004-12-31'))
443
+ ```
444
+
445
+ ### make query to the past (Peer only)
446
+
447
+ Datomic has the idea of time.
448
+ It allows database state to role back at some point in the past.
449
+ The example below makes the query to the current and secounds (milliseconds?) before of database.
450
+
451
+ Query example6:
452
+ ```ruby
453
+ # prints current awesomeness values
454
+ Diametric::Query.new(Person, nil, true).all.collect(&:awesomeness)
455
+ => [true, true, false, true, false]
456
+
457
+ # saves the time. this will be the past
458
+ past = Time.now
459
+ => 2013-11-09 20:17:59 -0500
460
+
461
+ # updates awesomeness false to true
462
+ Diametric::Query.new(Person, nil, true).where(awesomeness: false).each do |person|
463
+ person.update_attributes(awesomeness: true)
464
+ end
465
+ => [[17592186045420], [17592186045424]]
466
+
467
+ # prints updated awesomeness values
468
+ Diametric::Query.new(Person, nil, true).all.collect(&:awesomeness)
469
+ => [true, true, true, true, true]
470
+
471
+ # pulls out the past database
472
+ past_db = @conn.db.as_of(past)
473
+ => #<Diametric::Persistence::Database:0x49fe0bcd>
474
+
475
+ # makes a query to past database
476
+ Diametric::Query.new(Person, past_db, true).all.collect(&:awesomeness)
477
+ => [true, true, false, true, false]
478
+
479
+ # query results shows the database before "past"
480
+ ```
481
+
482
+ ### using datomic's query string (Peer only)
483
+
484
+ Currently, Diametric's query can do only a part of Datomic can do.
485
+ When you want a more complicated query or customized query, you can use
486
+ `Diametric::Persistence::Peer.q` method. For example:
487
+
488
+ ```ruby
489
+ result = Diametric::Persistence::Peer.q("[:find ?name :where [?person :person/name ?name]]", @conn.db)
490
+ ```
491
+
492
+ You'll get the results below:
493
+ ```
494
+ [["Rain"], ["Sleet"], ["Sun"], ["Cloud"], ["Breeze"]]
495
+ ```
496
+ This result data type is a Set of Arrays, which is the same as the default setting.
497
+
498
+ Below is another example with a value argument. This is what Diametric's filter query does.
499
+ ```ruby
500
+ result = Diametric::Persistence::Peer.q("[:find ?birthday :in $ [?value] :where [?e :person/birthday ?birthday] [(< ?birthday ?value)]]", @conn.db, DateTime.parse('2002-01-01'))
501
+ ```
502
+ The result is:
503
+ ```ruby
504
+ [[#inst "1980-02-12T00:00:00.000-00:00"], [#inst "1911-03-23T00:00:00.000-00:00"]]
505
+ ```
506
+
507
+ This is a Set of Arrays whose elements are Datomic's Date expression.
508
+ However, Diametric wrapper converts to Ruby object when it is used.
509
+ ```ruby
510
+ result.each do |ary|
511
+ puts ary.first.strftime("%m/%d/%Y")
512
+ end
513
+ ```
514
+ returns:
515
+ ```ruby
516
+ 02/11/1980
517
+ 03/22/1911
518
+ ```
519
+
520
+
521
+ ### Delete entities
522
+
523
+ To delete an entity, use destroy method:
524
+
525
+ ```ruby
526
+ query = Person.where(:name => "Sleet")
527
+ query.all.each {|p| p.destroy }
528
+ ```
529
+
530
+ ## Validation
531
+
532
+ ActiveModel's validation are included by default. All you need to do is start using them!
533
+
534
+ ```ruby
535
+ require 'diametric'
536
+
537
+ class Person
195
538
  include Diametric::Entity
196
539
  include Diametric::Persistence
197
-
540
+
198
541
  attribute :name, String, :index => true
199
- attribute :age, Integer
542
+
543
+ validates :name, :length => {:minimum => 5}
200
544
  end
545
+ Person.create_schema
546
+ ```
201
547
 
202
- goat = Goat.new(:name => 'Beans', :age => 2)
203
- goat.dbid # => nil
204
- goat.name # => "Beans"
205
- goat.persisted? # => false
206
- goat.new? # => true
548
+ This model validates the length of the name. If the name has less than 5 letters, the input is invalid. For example:
207
549
 
208
- goat.save
209
- goat.dbid # => new id autogenerated
210
- goat.name # => "Beans"
211
- goat.persisted? # => true
212
- goat.new? # => false
550
+ ```ruby
551
+ jeff = Person.new(:name => "Geoffrey")
552
+ jeff.valid?
553
+ # => true
213
554
 
214
- goats = Goat.where(:name => "Beans")
215
- #=> [Goat(id: 1, age: 2, name: "Beans")]
555
+ jeff.name = "Jeff"
556
+ jeff.valid?
557
+ # => false
558
+ ```
216
559
 
217
- goat = Goat.first(:name => "Beans")
218
- #=> Goat(id: 1, age: 2, name: "Beans")
560
+ Similarly to ActiveRecord, models cannot save until they are valid:
219
561
 
220
- goats = Goat.filter(:<, :age, 3)
221
- #=> [Goat(id: 1, age: 2, name: "Beans")]
562
+ ```ruby
563
+ jeff.name = "Jeff"
564
+ jeff.save
565
+ # => false
222
566
 
223
- goats = Goat.filter(:>, :age, 3)
224
- #=> []
567
+ jeff.name = "Goeffrey"
568
+ jeff.save
569
+ # => #<... transaction results object ...>
225
570
  ```
226
571
 
227
- ## Installation
572
+ ## Cardinality
573
+
574
+ When you want to define one-to-many attributes, you should add `:cardinality => :many` to an attribute definition.
575
+
576
+ ```ruby
577
+ class Profile
578
+ include Diametric::Entity
579
+ include Diametric::Persistence::Peer
580
+
581
+ attribute :name, String, :index => true
582
+ attribute :likes, String, :cardinality => :many
583
+ end
584
+ Profile.create_schema.get
585
+ ```
586
+
587
+ The attribute with `:cardinality => :many` accepts either Array or Set. Usage will be:
588
+
589
+ ```ruby
590
+ Profile.new(name: "Breeze", likes: ["chocolate", "biking", "afternoon"]).save
591
+ Profile.new(name: "Sun", likes: ["banana", "running", "morning"]).save
592
+ Profile.new(name: "Rain", likes: (Set.new ["pumpkin pie", "video game", "night"])).save
593
+ ```
594
+
595
+ Datomic saves values in a Set object when the attribute has `:cardinality => :many` definition. For example:
596
+ ```ruby
597
+ Profile.where(:name => "Breeze").each {|e| puts e.likes}
598
+ ```
599
+ returns `#<Set:0x007fbb66294928>`. So, if the following gets run:
600
+
601
+ ```ruby
602
+ Diametric::Query.new(Profile).each do |ary|
603
+ profile = Profile.reify(ary.first)
604
+ puts "#{profile.name} likes #{profile.likes.inspect}"
605
+ end
606
+ ```
607
+ Above prints:
608
+ ```ruby
609
+ Rain likes #<Set: {"pumpkin pie", "video game", "night"}>
610
+ Sun likes #<Set: {"morning", "banana", "running"}>
611
+ Breeze likes #<Set: {"chocolate", "afternoon", "biking"}>
612
+ ```
613
+ The order is not guaranteed.
614
+
615
+ ## Association (Peer only)
228
616
 
229
- Add this line to your application's Gemfile:
617
+ On Datomic, association is fairly easy even though it is one to many or many to many.
618
+ Association is defined by `Ref` type.
619
+ Just assigning a saved entity instance to `Ref` type attribute makes association.
230
620
 
231
- gem 'diametric'
621
+ The entity definition looks like below:
622
+ ```ruby
623
+ class Somebody
624
+ include Diametric::Entity
625
+ include Diametric::Persistence::Peer
232
626
 
233
- And then execute:
627
+ attribute :name, String
628
+ attribute :mom, Ref, :cardinality => :one
629
+ attribute :dad, Ref, :cardinality => :one
630
+ attribute :kids, Ref, :cardinality => :many
631
+ end
632
+ ```
234
633
 
235
- $ bundle
634
+ This entity has one to one and one to many associations.
635
+ Below is an example of creating an entity instance and making queries.
236
636
 
237
- Or install it yourself as:
637
+ ```ruby
638
+ # creates mom and dad
639
+ mom = Somebody.new(name: "Snow White")
640
+ => #<Somebody:0x3292eff7 @changed_attributes={"name"=>nil}, @name="Snow White">
641
+ mom.save
642
+ => {:db-before datomic.db.Db@f89fe443, :db-after datomic.db.Db@efb79a88, :tx-data [#Datum{:e 13194139534333 :a 50 :v #inst "2013-11-10T03:35:06.889-00:00" :tx 13194139534333 :added true} #Datum{:e 17592186045438 :a 67 :v "Snow White" :tx 13194139534333 :added true}], :tempids {-9223350046623220305 17592186045438}}
643
+ dad = Somebody.new(name: "Prince Brave")
644
+ => #<Somebody:0x1425e531 @changed_attributes={"name"=>nil}, @name="Prince Brave">
645
+ dad.save
646
+ => {:db-before datomic.db.Db@efb79a88, :db-after datomic.db.Db@3bddfe3, :tx-data [#Datum{:e 13194139534335 :a 50 :v #inst "2013-11-10T03:35:09.405-00:00" :tx 13194139534335 :added true} #Datum{:e 17592186045440 :a 67 :v "Prince Brave" :tx 13194139534335 :added true}], :tempids {-9223350046623220306 17592186045440}}
647
+
648
+ # creates sombody who has mom and dad
649
+ Somebody.new(name: "Alice Wonderland", mom: mom, dad: dad).save
650
+ => {:db-before datomic.db.Db@3bddfe3, :db-after datomic.db.Db@df977d06, :tx-data [#Datum{:e 13194139534337 :a 50 :v #inst "2013-11-10T03:35:39.562-00:00" :tx 13194139534337 :added true} #Datum{:e 17592186045442 :a 68 :v 17592186045438 :tx 13194139534337 :added true} #Datum{:e 17592186045442 :a 69 :v 17592186045440 :tx 13194139534337 :added true} #Datum{:e 17592186045442 :a 67 :v "Alice Wonderland" :tx 13194139534337 :added true}], :tempids {-9223350046623220307 17592186045442}}
651
+
652
+ # makes a query whose name is "Alice Wonderland"
653
+ me = Diametric::Query.new(Somebody, @conn, true).where(name: "Alice Wonderland").first
654
+ => #<Somebody:0x5f369fc6 @changed_attributes={"dad"=>nil, "mom"=>nil, "name"=>nil}, @dbid=17592186045442, @name="Alice Wonderland", @dad=#<Somebody:0x75de7009 @changed_attributes={"name"=>nil}, @dbid=17592186045440, @name="Prince Brave">, @mom=#<Somebody:0x7c840fe3 @changed_attributes={"name"=>nil}, @dbid=17592186045438, @name="Snow White">>
655
+
656
+ # creates another two others who have mom as me
657
+ mario = Somebody.new(name: "Mario", mom: me)
658
+ => #<Somebody:0x6ad3fbe4 @changed_attributes={"name"=>nil, "mom"=>nil}, @name="Mario", @mom=#<Somebody:0x5f369fc6 @changed_attributes={"dad"=>nil, "mom"=>nil, "name"=>nil}, @dbid=17592186045442, @name="Alice Wonderland", @dad=#<Somebody:0x75de7009 @changed_attributes={"name"=>nil}, @dbid=17592186045440, @name="Prince Brave">, @mom=#<Somebody:0x7c840fe3 @changed_attributes={"name"=>nil}, @dbid=17592186045438, @name="Snow White">>>
659
+ mario.save
660
+ => {:db-before datomic.db.Db@df977d06, :db-after datomic.db.Db@79b5b85f, :tx-data [#Datum{:e 13194139534339 :a 50 :v #inst "2013-11-10T03:36:37.567-00:00" :tx 13194139534339 :added true} #Datum{:e 17592186045444 :a 68 :v 17592186045445 :tx 13194139534339 :added true} #Datum{:e 17592186045444 :a 67 :v "Mario" :tx 13194139534339 :added true} #Datum{:e 17592186045445 :a 68 :v 17592186045447 :tx 13194139534339 :added true} #Datum{:e 17592186045445 :a 69 :v 17592186045446 :tx 13194139534339 :added true} #Datum{:e 17592186045445 :a 67 :v "Alice Wonderland" :tx 13194139534339 :added true} #Datum{:e 17592186045446 :a 67 :v "Prince Brave" :tx 13194139534339 :added true} #Datum{:e 17592186045447 :a 67 :v "Snow White" :tx 13194139534339 :added true}], :tempids {-9223350046623220311 17592186045447, -9223350046623220310 17592186045446, -9223350046623220309 17592186045445, -9223350046623220308 17592186045444}}
661
+ luigi = Somebody.new(name: "Luigi", mom: me)
662
+ => #<Somebody:0x4ebed2b3 @changed_attributes={"name"=>nil, "mom"=>nil}, @name="Luigi", @mom=#<Somebody:0x5f369fc6 @temp_ref=-1013, @changed_attributes={}, @dbid=17592186045445, @name="Alice Wonderland", @dad=#<Somebody:0x75de7009 @temp_ref=-1014, @changed_attributes={}, @dbid=17592186045446, @name="Prince Brave", @previously_changed={"name"=>[nil, "Prince Brave"]}>, @previously_changed={"dad"=>[nil, #<Somebody:0x75de7009 @temp_ref=-1014, @changed_attributes={}, @dbid=17592186045446, @name="Prince Brave", @previously_changed={"name"=>[nil, "Prince Brave"]}>], "mom"=>[nil, #<Somebody:0x7c840fe3 @temp_ref=-1015, @changed_attributes={}, @dbid=17592186045447, @name="Snow White", @previously_changed={"name"=>[nil, "Snow White"]}>], "name"=>[nil, "Alice Wonderland"]}, @mom=#<Somebody:0x7c840fe3 @temp_ref=-1015, @changed_attributes={}, @dbid=17592186045447, @name="Snow White", @previously_changed={"name"=>[nil, "Snow White"]}>>>
663
+ luigi.save
664
+ => {:db-before datomic.db.Db@79b5b85f, :db-after datomic.db.Db@9a8e7dab, :tx-data [#Datum{:e 13194139534344 :a 50 :v #inst "2013-11-10T03:36:37.649-00:00" :tx 13194139534344 :added true} #Datum{:e 17592186045449 :a 68 :v 17592186045445 :tx 13194139534344 :added true} #Datum{:e 17592186045449 :a 67 :v "Luigi" :tx 13194139534344 :added true}], :tempids {-9223350046623220312 17592186045449}}
665
+ me.update_attributes(kids: [mario, luigi])
666
+ => {:db-before datomic.db.Db@9a8e7dab, :db-after datomic.db.Db@7507454d, :tx-data [#Datum{:e 13194139534346 :a 50 :v #inst "2013-11-10T03:36:38.821-00:00" :tx 13194139534346 :added true} #Datum{:e 17592186045445 :a 70 :v 17592186045444 :tx 13194139534346 :added true} #Datum{:e 17592186045445 :a 70 :v 17592186045449 :tx 13194139534346 :added true}], :tempids {}}
667
+
668
+ # again, makes a query whose name is "Alice Wonderland"
669
+ me = Diametric::Query.new(Somebody, @conn, true).where(name: "Alice Wonderland").first
670
+ => #<Somebody:0x1eb6037d @mom=#<Somebody:0x335b3d6 @dbid=17592186045427, @changed_attributes={"name"=>nil}, @name="Snow White">, @dad=#<Somebody:0x38848217 @dbid=17592186045426, @changed_attributes={"name"=>nil}, @name="Prince Brave">, @dbid=17592186045425, @changed_attributes={"kids"=>nil, "dad"=>nil, "mom"=>nil, "name"=>nil}, @kids=#<Set: {#<Somebody:0x3c743d40 @mom=#<Java::DatomicQuery::EntityMap:0x77a9ac36>, @dbid=17592186045424, @changed_attributes={"mom"=>nil, "name"=>nil}, @name="Mario">, #<Somebody:0x2444c3df @mom=#<Java::DatomicQuery::EntityMap:0x5aac6f9f>, @dbid=17592186045429, @changed_attributes={"mom"=>nil, "name"=>nil}, @name="Luigi">}>, @name="Alice Wonderland">
671
+
672
+ # looks kids data
673
+ me.kids.collect(&:name)
674
+ => ["Mario", "Luigi"]
675
+
676
+ # retieve mom's instance from kids
677
+ Somebody.reify(me.kids.first.mom).name
678
+ => "Alice Wonderland"
679
+ ```
238
680
 
239
- $ gem install diametric
681
+ ## Thanks
682
+
683
+ Development of Diametric was sponsored by [Relevance][]. They are the
684
+ best Clojure shop around and one of the best Ruby shops. I highly
685
+ recommend them for help with your corporate projects.
686
+
687
+ Special thanks to Mongoid for writing some solid ORM code that was liberally borrowed from to add Rails support to Diametric.
240
688
 
241
689
  ## Contributing
242
690