diametric 0.1.1-java → 0.1.2-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87aa3d4e00a0b4f5ffbc7e06710d8bfaeddc318b
4
- data.tar.gz: 8a5f18c2e823279ce54471a89b336670a5a6219e
3
+ metadata.gz: 003a4465b11cc5367d54854afa10ee49174009da
4
+ data.tar.gz: e18bf5c1010b430be842eb4761ae266c9c7c320c
5
5
  SHA512:
6
- metadata.gz: 54e3e57bfad6731d7c09dbf23a6cc79e84becd30b0e9396171fe945c9a4b8e94351a4eb0795ec6e3a8e944d8ca636f4b13cf287821808975d82878427fac7c01
7
- data.tar.gz: abd9bd9e3f162db5e6dd070c496f6f6363dc09fb6c573689b4f754b799dbe643a0e9b9e5fba880af7acd90efa143307cbc85576b6cb219ea174c2beb6dc549c8
6
+ metadata.gz: 01275e6f3572c97559ca8a1592f87dea5b9494d455b64fc1131f1e57fd649542ee2b09590121d4b64cec89791009852951559712e23d5a62e61637df2dbb385d
7
+ data.tar.gz: 6af0a5a63dc06d64ee2c7c5f44330bd261def83c770d696822000cd94b19b2fe776dcc6b9a0909033c988bdb64971e7e55a03bdea91665a8b0c4b37a70f629f3
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