diametric 0.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +28 -0
  3. data/Jarfile +20 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +264 -0
  6. data/Rakefile +49 -0
  7. data/bin/datomic-rest +33 -0
  8. data/bin/download-datomic +13 -0
  9. data/datomic_version.yml +4 -0
  10. data/diametric-java.gemspec +39 -0
  11. data/ext/diametric/DiametricCollection.java +147 -0
  12. data/ext/diametric/DiametricConnection.java +113 -0
  13. data/ext/diametric/DiametricDatabase.java +107 -0
  14. data/ext/diametric/DiametricEntity.java +90 -0
  15. data/ext/diametric/DiametricListenableFuture.java +47 -0
  16. data/ext/diametric/DiametricObject.java +66 -0
  17. data/ext/diametric/DiametricPeer.java +414 -0
  18. data/ext/diametric/DiametricService.java +102 -0
  19. data/ext/diametric/DiametricUUID.java +61 -0
  20. data/ext/diametric/DiametricUtils.java +183 -0
  21. data/lib/boolean_type.rb +3 -0
  22. data/lib/diametric.rb +42 -0
  23. data/lib/diametric/config.rb +54 -0
  24. data/lib/diametric/config/environment.rb +42 -0
  25. data/lib/diametric/entity.rb +659 -0
  26. data/lib/diametric/errors.rb +13 -0
  27. data/lib/diametric/generators/active_model.rb +42 -0
  28. data/lib/diametric/persistence.rb +48 -0
  29. data/lib/diametric/persistence/common.rb +82 -0
  30. data/lib/diametric/persistence/peer.rb +154 -0
  31. data/lib/diametric/persistence/rest.rb +107 -0
  32. data/lib/diametric/query.rb +259 -0
  33. data/lib/diametric/railtie.rb +52 -0
  34. data/lib/diametric/rest_service.rb +74 -0
  35. data/lib/diametric/service_base.rb +77 -0
  36. data/lib/diametric/transactor.rb +86 -0
  37. data/lib/diametric/version.rb +3 -0
  38. data/lib/diametric_service.jar +0 -0
  39. data/lib/tasks/create_schema.rb +27 -0
  40. data/lib/tasks/diametric_config.rb +45 -0
  41. data/lib/value_enums.rb +8 -0
  42. data/spec/conf_helper.rb +55 -0
  43. data/spec/config/free-transactor-template.properties +73 -0
  44. data/spec/config/logback.xml +59 -0
  45. data/spec/data/seattle-data0.dtm +452 -0
  46. data/spec/data/seattle-data1.dtm +326 -0
  47. data/spec/developer_create_sample.rb +39 -0
  48. data/spec/developer_query_spec.rb +120 -0
  49. data/spec/diametric/config_spec.rb +60 -0
  50. data/spec/diametric/entity_spec.rb +476 -0
  51. data/spec/diametric/peer_api_spec.rb +147 -0
  52. data/spec/diametric/persistence/peer_many2many_spec.rb +76 -0
  53. data/spec/diametric/persistence/peer_spec.rb +27 -0
  54. data/spec/diametric/persistence/rest_spec.rb +30 -0
  55. data/spec/diametric/persistence_spec.rb +59 -0
  56. data/spec/diametric/query_spec.rb +118 -0
  57. data/spec/diametric/rest_service_spec.rb +56 -0
  58. data/spec/diametric/transactor_spec.rb +68 -0
  59. data/spec/integration_spec.rb +107 -0
  60. data/spec/parent_child_sample.rb +42 -0
  61. data/spec/peer_integration_spec.rb +121 -0
  62. data/spec/peer_seattle_spec.rb +200 -0
  63. data/spec/rc2013_seattle_big.rb +82 -0
  64. data/spec/rc2013_seattle_small.rb +60 -0
  65. data/spec/rc2013_simple_sample.rb +72 -0
  66. data/spec/seattle_integration_spec.rb +106 -0
  67. data/spec/simple_validation_sample.rb +31 -0
  68. data/spec/spec_helper.rb +63 -0
  69. data/spec/support/entities.rb +157 -0
  70. data/spec/support/gen_entity_class.rb +9 -0
  71. data/spec/support/persistence_examples.rb +104 -0
  72. data/spec/test_version_file.yml +4 -0
  73. metadata +290 -0
@@ -0,0 +1,42 @@
1
+ module Diametric
2
+ module Config
3
+
4
+ # Encapsulates logic for getting environment information.
5
+ #
6
+ # This is nearly a word-for-word copy of {http://github.com/mongoid/mongoid Mongoid}'s {https://github.com/mongoid/mongoid/blob/master/lib/mongoid/config/environment.rb lib/mongoid/config/environment.rb} file.
7
+ # Thanks!
8
+ module Environment
9
+ extend self
10
+
11
+ # Get the name of the environment that we are running under. This first
12
+ # looks for Rails, then Sinatra, then a RACK_ENV environment variable,
13
+ # and if none of those are found raises an error.
14
+ #
15
+ # @example Get the env name.
16
+ # Environment.env_name
17
+ #
18
+ # @raise [ "No environment specified" ] If no environment was set.
19
+ #
20
+ # @return [ String ] The name of the current environment.
21
+ def env_name
22
+ return Rails.env if defined?(Rails)
23
+ return Sinatra::Base.environment.to_s if defined?(Sinatra)
24
+ ENV["RACK_ENV"] || ENV["DIAMETRIC_ENV"] || raise("No environment specified")
25
+ end
26
+
27
+ # Load the yaml from the provided path and return the settings for the
28
+ # current environment.
29
+ #
30
+ # @example Load the yaml.
31
+ # Environment.load_yaml("/work/diametric.yml")
32
+ #
33
+ # @param [ String ] path The location of the file.
34
+ #
35
+ # @return [ Hash ] The settings.
36
+ def load_yaml(path, environment = nil)
37
+ env = environment ? environment.to_s : env_name
38
+ YAML.load(ERB.new(File.new(path).read).result)[env]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,659 @@
1
+ require "bigdecimal"
2
+ require "edn"
3
+ require 'active_support/core_ext'
4
+ require 'active_support/inflector'
5
+ require 'active_model'
6
+ require 'set'
7
+ require 'value_enums'
8
+ require 'boolean_type'
9
+
10
+ module Diametric
11
+
12
+ # +Diametric::Entity+ is a module that, when included in a class,
13
+ # gives it the ability to generate Datomic schemas, queries, and
14
+ # transactions, and makes it +ActiveModel+ compliant.
15
+ #
16
+ # While this allows you to use this anywhere you would use an
17
+ # +ActiveRecord::Base+ model or another +ActiveModel+-compliant
18
+ # instance, it _does not_ include persistence. The +Entity+ module
19
+ # is primarily made of pure functions that take their receiver (an
20
+ # instance of the class they are included in) and return data that
21
+ # you can use in Datomic. +Entity+ can be best thought of as a data
22
+ # builder for Datomic.
23
+ #
24
+ # Of course, you can combine +Entity+ with one of the two available
25
+ # Diametric persistence modules for a fully persistent model.
26
+ #
27
+ # When +Entity+ is included in a class, that class is extended with
28
+ # {ClassMethods}.
29
+ #
30
+ # @!attribute dbid
31
+ # The database id assigned to the entity by Datomic.
32
+ # @return [Integer]
33
+ module Entity
34
+ Ref = "ref"
35
+ UUID = "uuid"
36
+ Double = "double"
37
+ # Conversions from Ruby types to Datomic types.
38
+ VALUE_TYPES = {
39
+ Symbol => "keyword",
40
+ String => "string",
41
+ Integer => "long",
42
+ Float => "double",
43
+ BigDecimal => "bigdec",
44
+ DateTime => "instant",
45
+ Boolean => "boolean",
46
+ URI => "uri",
47
+ UUID => "uuid"
48
+ }
49
+
50
+ DEFAULT_OPTIONS = {
51
+ :cardinality => :one
52
+ }
53
+
54
+ @temp_ref = -1000
55
+
56
+ def self.included(base)
57
+ base.send(:extend, ClassMethods)
58
+ base.send(:extend, ActiveModel::Naming)
59
+ base.send(:include, ActiveModel::Conversion)
60
+ base.send(:include, ActiveModel::Dirty)
61
+ base.send(:include, ActiveModel::Validations)
62
+
63
+ base.class_eval do
64
+ @attributes = {}
65
+ @defaults = {}
66
+ @enums = {}
67
+ @namespace_prefix = nil
68
+ @partition = :"db.part/user"
69
+ end
70
+ end
71
+
72
+ def self.next_temp_ref
73
+ @temp_ref -= 1
74
+ end
75
+
76
+ # @!attribute [rw] partition
77
+ # The Datomic partition this entity's data will be stored in.
78
+ # Defaults to +:db.part/user+.
79
+ # @return [String]
80
+ #
81
+ # @!attribute [r] attributes
82
+ # @return [Array] Definitions of each of the entity's attributes (name, type, options).
83
+ #
84
+ # @!attribute [r] defaults
85
+ # @return [Array] Default values for any entitites defined with a +:default+ key and value.
86
+ module ClassMethods
87
+ def partition
88
+ @partition
89
+ end
90
+
91
+ def partition=(partition)
92
+ self.partition = partition.to_sym
93
+ end
94
+
95
+ def attributes
96
+ @attributes
97
+ end
98
+
99
+ def defaults
100
+ @defaults
101
+ end
102
+
103
+ # Set the namespace prefix used for attribute names
104
+ #
105
+ # @param prefix [#to_s] The prefix to be used for namespacing entity attributes
106
+ #
107
+ # @example Override the default namespace prefix
108
+ # class Mouse
109
+ # include Diametric::Entity
110
+ #
111
+ # namespace_prefix :mice
112
+ # end
113
+ #
114
+ # Mouse.new.prefix # => :mice
115
+ #
116
+ # @return void
117
+ def namespace_prefix(prefix)
118
+ @namespace_prefix = prefix
119
+ end
120
+
121
+ # Add an attribute to a {Diametric::Entity}.
122
+ #
123
+ # Valid options are:
124
+ #
125
+ # * +:index+: The only valid value is +true+. This causes the
126
+ # attribute to be indexed for easier lookup.
127
+ # * +:unique+: Valid values are +:value+ or +:identity.+
128
+ # * +:value+ causes the attribute value to be unique to the
129
+ # entity and attempts to insert a duplicate value will fail.
130
+ # * +:identity+ causes the attribute value to be unique to
131
+ # the entity. Attempts to insert a duplicate value with a
132
+ # temporary entity id will result in an "upsert," causing the
133
+ # temporary entity's attributes to be merged with those for
134
+ # the current entity in Datomic.
135
+ # * +:cardinality+: Specifies whether an attribute associates
136
+ # a single value or a set of values with an entity. The
137
+ # values allowed are:
138
+ # * +:one+ - the attribute is single valued, it associates a
139
+ # single value with an entity.
140
+ # * +:many+ - the attribute is mutli valued, it associates a
141
+ # set of values with an entity.
142
+ # +:one+ is the default.
143
+ # * +:doc+: A string used in Datomic to document the attribute.
144
+ # * +:fulltext+: The only valid value is +true+. Indicates that a
145
+ # fulltext search index should be generated for the attribute.
146
+ # * +:default+: The value the attribute will default to when the
147
+ # Entity is initialized. Defaults for attributes with +:cardinality+ of +:many+
148
+ # will be transformed into a Set by passing the default to +Set.new+.
149
+ #
150
+ # @example Add an indexed name attribute.
151
+ # attribute :name, String, :index => true
152
+ #
153
+ # @param name [String] The attribute's name.
154
+ # @param value_type [Class] The attribute's type.
155
+ # Must exist in {Diametric::Entity::VALUE_TYPES}.
156
+ # @param opts [Hash] Options to pass to Datomic.
157
+ #
158
+ # @return void
159
+ def attribute(name, value_type, opts = {})
160
+ opts = DEFAULT_OPTIONS.merge(opts)
161
+
162
+ establish_defaults(name, value_type, opts)
163
+
164
+ @attributes[name] = {:value_type => value_type}.merge(opts)
165
+
166
+ setup_attribute_methods(name, opts[:cardinality])
167
+ end
168
+
169
+ # @return [Array<Symbol>] Names of the entity's attributes.
170
+ def attribute_names
171
+ @attributes.keys
172
+ end
173
+
174
+ # @return [Array<Symbol>] Names of the entity's enums.
175
+ def enum_names
176
+ @enums.keys
177
+ end
178
+
179
+ # Add an enum to a {Diametric::Entity}.
180
+ #
181
+ # enum is used when attribute type is Ref and refers
182
+ # a set of values.
183
+ # name should be the same as corresponding attribute name.
184
+ #
185
+ # @example Add an enum of colors
186
+ # class Palette
187
+ # attribute :color, Ref
188
+ # enum :color, [:blue, :green, :yellow, :orange]
189
+ # end
190
+ # p = Pallet.new
191
+ # p.color = Pallet::Color::Green
192
+ #
193
+ # @param name [Symbol] The enum's name.
194
+ # @param values [Array] The enum values.
195
+ #
196
+ # @return void
197
+ def enum(name, values)
198
+ enum_values = nil
199
+ enum_values = values.to_set if values.is_a?(Array)
200
+ enum_values = values if values.is_a?(Set)
201
+ raise RuntimeError "values should be Array or Set" if enum_values.nil?
202
+ enum_name = name.to_s.capitalize
203
+ syms = values.collect(&:to_s).collect(&:upcase).collect(&:to_sym)
204
+ class_eval("module #{enum_name};enum #{syms};end")
205
+ @enums[name] = syms
206
+ end
207
+
208
+ # Generates a Datomic schema for a model's attributes.
209
+ #
210
+ # @return [Array] A Datomic schema, as Ruby data that can be
211
+ # converted to EDN.
212
+ def schema
213
+ return peer_schema if self.instance_variable_get("@peer")
214
+ rest_schema
215
+ end
216
+
217
+ def rest_schema
218
+ defaults = {
219
+ :"db/id" => tempid(:"db.part/db"),
220
+ :"db/cardinality" => :"db.cardinality/one",
221
+ :"db.install/_attribute" => :"db.part/db"
222
+ }
223
+
224
+ schema_array = @attributes.reduce([]) do |schema, (attribute, opts)|
225
+ opts = opts.dup
226
+ value_type = opts.delete(:value_type)
227
+
228
+ unless opts.empty?
229
+ opts[:cardinality] = namespace("db.cardinality", opts[:cardinality])
230
+ opts[:unique] = namespace("db.unique", opts[:unique]) if opts[:unique]
231
+ opts = opts.map { |k, v|
232
+ k = namespace("db", k)
233
+ [k, v]
234
+ }
235
+ opts = Hash[*opts.flatten]
236
+ end
237
+
238
+ schema << defaults.merge({
239
+ :"db/ident" => namespace(prefix, attribute),
240
+ :"db/valueType" => value_type(value_type),
241
+ }).merge(opts)
242
+ end
243
+
244
+ enum_schema = [
245
+ :"db/add", tempid(:"db.part/user"), :"db/ident"
246
+ ]
247
+ prefix = self.name.downcase
248
+ @enums.each do |key, values|
249
+ values.each do |value|
250
+ ident_value = :"#{prefix}.#{key.downcase}/#{value.to_s.sub(/_/, "-").downcase}"
251
+ es = [:"db/add", tempid(:"db.part/user"), :"db/ident", ident_value]
252
+ schema_array << es
253
+ end
254
+ end
255
+ schema_array
256
+ end
257
+
258
+ # Generates a Datomic schema for a model's attributes.
259
+ #
260
+ # @return [Array] A Datomic schema, as Ruby data that can be
261
+ # converted to EDN.
262
+ def peer_schema
263
+ defaults = {
264
+ ":db/cardinality" => ":db.cardinality/one",
265
+ ":db.install/_attribute" => ":db.part/db"
266
+ }
267
+
268
+ schema_array = @attributes.reduce([]) do |schema, (attribute, opts)|
269
+ opts = opts.dup
270
+ value_type = opts.delete(:value_type)
271
+
272
+ unless opts.empty?
273
+ opts[:cardinality] = namespace("db.cardinality", opts[:cardinality])
274
+ opts[:unique] = namespace("db.unique", opts[:unique]) if opts[:unique]
275
+ opts = opts.map { |k, v|
276
+ k = namespace("db", k)
277
+ [k, v]
278
+ }
279
+ opts = Hash[*opts.flatten]
280
+ end
281
+
282
+ schema << defaults.merge({
283
+ ":db/id" => Diametric::Persistence::Peer.tempid(":db.part/db"),
284
+ ":db/ident" => namespace(prefix, attribute),
285
+ ":db/valueType" => value_type(value_type),
286
+ }).merge(opts)
287
+ end
288
+
289
+ prefix = self.name.downcase
290
+ @enums.each do |key, values|
291
+ values.each do |value|
292
+ ident_value = ":#{prefix}.#{key.downcase}/#{value.to_s.sub(/_/, "-").downcase}"
293
+ es = [":db/add", Diametric::Persistence::Peer.tempid(":db.part/user"), ":db/ident", ident_value]
294
+ schema_array << es
295
+ end
296
+ end
297
+ schema_array
298
+ end
299
+
300
+ # Given a set of Ruby data returned from a Datomic query, this
301
+ # can re-hydrate that data into a model instance.
302
+ #
303
+ # @return [Entity]
304
+ def from_query(query_results, connection=nil, resolve=false)
305
+ dbid = query_results.shift
306
+ widget = self.new(Hash[attribute_names.zip query_results])
307
+ widget.dbid = dbid
308
+
309
+ if resolve
310
+ widget = resolve_ref_dbid(widget, connection)
311
+ end
312
+ widget
313
+ end
314
+
315
+ def resolve_ref_dbid(parent, connection)
316
+ parent.class.attribute_names.each do |e|
317
+ if parent.class.attributes[e][:value_type] == "ref"
318
+ ref = parent.instance_variable_get("@#{e.to_s}")
319
+ if ref.is_a?(Fixnum) || ref.is_a?(Java::DatomicQuery::EntityMap)
320
+ child = from_dbid_or_entity(ref, connection)
321
+ child = resolve_ref_dbid(child, connection)
322
+ parent.instance_variable_set("@#{e.to_s}", child)
323
+ elsif ref.is_a?(Set)
324
+ children = ref.inject(Set.new) do |memo, entity|
325
+ child = from_dbid_or_entity(entity, connection)
326
+ memo.add(child)
327
+ memo
328
+ end
329
+ parent.instance_variable_set("@#{e.to_s}", children)
330
+ end
331
+ end
332
+ end
333
+ parent
334
+ end
335
+
336
+ def from_dbid_or_entity(thing, conn_or_db=nil, resolve=false)
337
+ conn_or_db ||= Diametric::Persistence::Peer.connect.db
338
+
339
+ if conn_or_db.respond_to?(:db)
340
+ conn_or_db = conn_or_db.db
341
+ end
342
+
343
+ if thing.is_a? Fixnum
344
+ dbid = thing
345
+ entity = conn_or_db.entity(dbid)
346
+ elsif thing.respond_to?(:eid)
347
+ dbid = thing.eid
348
+ if entity.respond_to?(:keys)
349
+ entity = thing
350
+ else
351
+ entity = conn_or_db.entity(dbid)
352
+ end
353
+ elsif thing.respond_to?(:to_java)
354
+ dbid = thing.to_java
355
+ entity = conn_or_db.entity(dbid)
356
+ else
357
+ return thing
358
+ end
359
+ first_key = entity.keys.first
360
+ match_data = /:([a-zA-Z0-9_]+)\/([a-zA-Z0-9_]+)/.match(first_key)
361
+ entity_name = match_data[1].capitalize.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}"}
362
+ instance = eval("#{entity_name}.new")
363
+ instance.send("#{match_data[2]}=", entity[first_key])
364
+ entity.keys[1..-1].each do |key|
365
+ match_data = /:([a-zA-Z0-9_]+)\/([a-zA-Z0-9_]+)/.match(key)
366
+ instance.send("#{match_data[2]}=", entity[key])
367
+ end
368
+ instance.send("dbid=", Diametric::Persistence::Object.new(dbid))
369
+
370
+ if resolve
371
+ instance = resolve_ref_dbid(instance, conn_or_db)
372
+ end
373
+
374
+ instance
375
+ end
376
+
377
+ def find(id)
378
+ if self.instance_variable_get("@peer")
379
+ connection ||= Diametric::Persistence::Peer.connect
380
+ end
381
+ from_dbid_or_entity(id, connection)
382
+ end
383
+
384
+ # Returns the prefix for this model used in Datomic. Can be
385
+ # overriden by declaring {#namespace_prefix}
386
+ #
387
+ # @example
388
+ # Mouse.prefix #=> "mouse"
389
+ # FireHouse.prefix #=> "fire_house"
390
+ # Person::User.prefix #=> "person.user"
391
+ #
392
+ # @return [String]
393
+ def prefix
394
+ @namespace_prefix || self.to_s.underscore.sub('/', '.')
395
+ end
396
+
397
+ # Create a temporary id placeholder.
398
+ #
399
+ # @param e [*#to_edn] Elements to put in the placeholder. Should
400
+ # be either partition or partition and a negative number to be
401
+ # used as a reference.
402
+ #
403
+ # @return [EDN::Type::Unknown] Temporary id placeholder.
404
+ def tempid(*e)
405
+ if self.instance_variable_get("@peer")
406
+ if e[0].to_s.include?("user")
407
+ return Diametric::Persistence::Peer.tempid(":db.part/user")
408
+ else
409
+ return Diametric::Persistence::Peer.tempid(":db.part/db")
410
+ end
411
+ else
412
+ EDN.tagged_element('db/id', e)
413
+ end
414
+ end
415
+
416
+ # Namespace a attribute for Datomic.
417
+ #
418
+ # @param ns [#to_s] Namespace.
419
+ # @param attribute [#to_s] Attribute.
420
+ #
421
+ # @return [Symbol] Namespaced attribute.
422
+ def namespace(ns, attribute)
423
+ if self.instance_variable_get("@peer")
424
+ ":" + [ns.to_s, attribute.to_s].join("/")
425
+ else
426
+ [ns.to_s, attribute.to_s].join("/").to_sym
427
+ end
428
+ end
429
+
430
+ # Raise an error if validation failed.
431
+ #
432
+ # @example Raise the validation error.
433
+ # Person.fail_validate!(person)
434
+ #
435
+ # @param [ Entity ] entity The entity to fail.
436
+ def fail_validate!(entity)
437
+ raise Errors::ValidationError.new(entity)
438
+ end
439
+
440
+ private
441
+
442
+ def value_type(vt)
443
+ if vt.is_a?(Class) || vt.is_a?(Module)
444
+ vt = VALUE_TYPES[vt]
445
+ end
446
+ namespace("db.type", vt)
447
+ end
448
+
449
+ def establish_defaults(name, value_type, opts = {})
450
+ default = opts.delete(:default)
451
+ @defaults[name] = default if default
452
+ end
453
+
454
+ def setup_attribute_methods(name, cardinality)
455
+ define_attribute_method name
456
+
457
+ define_method(name) do
458
+ instance_variable_get("@#{name}")
459
+ end
460
+
461
+ define_method("#{name}=") do |value|
462
+ send("#{name}_will_change!") unless value == instance_variable_get("@#{name}")
463
+ if cardinality == :many
464
+ value = Set.new(value)
465
+ end
466
+ instance_variable_set("@#{name}", value)
467
+ end
468
+ end
469
+ end
470
+
471
+ def dbid
472
+ @dbid
473
+ end
474
+
475
+ def dbid=(dbid)
476
+ @dbid = dbid
477
+ end
478
+
479
+ alias :id :dbid
480
+ alias :"id=" :"dbid="
481
+
482
+ # Create a new {Diametric::Entity}.
483
+ #
484
+ # @param params [Hash] A hash of attributes and values to
485
+ # initialize the entity with.
486
+ def initialize(params = {})
487
+ self.class.defaults.merge(params).each do |k, v|
488
+ self.send("#{k}=", v)
489
+ end
490
+ end
491
+
492
+ # @return [Integer] A reference unique to this entity instance
493
+ # used in constructing temporary ids for transactions.
494
+ def temp_ref
495
+ @temp_ref ||= Diametric::Entity.next_temp_ref
496
+ end
497
+
498
+ # Creates data for a Datomic transaction.
499
+ #
500
+ # @param attribute_names [*Symbol] Attribute names to save in the
501
+ # transaction. If no names are given, any changed
502
+ # attributes will be saved.
503
+ #
504
+ # @return [Array] Datomic transaction data.
505
+ def tx_data(*attribute_names)
506
+ attribute_names = self.changed_attributes.keys if attribute_names.empty?
507
+
508
+ entity_tx = {}
509
+ txes = []
510
+ attribute_names.each do |attribute_name|
511
+ cardinality = self.class.attributes[attribute_name.to_sym][:cardinality]
512
+
513
+ #if cardinality == :many && self.class.instance_variable_get("@peer").nil?
514
+ if cardinality == :many
515
+ txes += cardinality_many_tx_data(attribute_name)
516
+ else
517
+ entity_tx[self.class.namespace(self.class.prefix, attribute_name)] = self.send(attribute_name)
518
+ end
519
+ end
520
+
521
+ if entity_tx.present?
522
+ if self.class.instance_variable_get("@peer")
523
+ @dbid ||= tempid
524
+ txes << entity_tx.merge({":db/id" => dbid})
525
+ else
526
+ txes << entity_tx.merge({:"db/id" => dbid || tempid})
527
+ end
528
+ end
529
+ txes
530
+ end
531
+
532
+ def cardinality_many_tx_data(attribute_name)
533
+ prev = Array(self.changed_attributes[attribute_name]).to_set
534
+ curr = self.send(attribute_name)
535
+
536
+ protractions = curr - prev
537
+ retractions = prev - curr
538
+
539
+ namespaced_attribute = self.class.namespace(self.class.prefix, attribute_name)
540
+ txes = []
541
+ if self.class.instance_variable_get("@peer")
542
+ @dbid ||= tempid
543
+ txes_data(txes, ":db/retract", namespaced_attribute, retractions) unless retractions.empty?
544
+ txes_data(txes, ":db/add", namespaced_attribute, protractions) unless protractions.empty?
545
+ else
546
+ txes << [:"db/retract", (dbid || tempid), namespaced_attribute, retractions.to_a] unless retractions.empty?
547
+ txes << [:"db/add", (dbid || tempid) , namespaced_attribute, protractions.to_a] unless protractions.empty?
548
+ end
549
+ txes
550
+ end
551
+
552
+ def txes_data(txes, op, namespaced_attribute, set)
553
+ set.to_a.each do |s|
554
+ value = s.respond_to?(:dbid) ? s.dbid : s
555
+ txes << [op, @dbid, namespaced_attribute, value]
556
+ end
557
+ end
558
+
559
+ # Returns hash of all attributes for this object
560
+ #
561
+ # @return [Hash<Symbol, object>] Hash of atrributes
562
+ def attributes
563
+ Hash[self.class.attribute_names.map {|attribute_name| [attribute_name, self.send(attribute_name)] }]
564
+ end
565
+
566
+ # @return [Array<Symbol>] Names of the entity's attributes.
567
+ def attribute_names
568
+ self.class.attribute_names
569
+ end
570
+
571
+ # @return [EDN::Type::Unknown] A temporary id placeholder for
572
+ # use in transactions.
573
+ def tempid
574
+ self.class.tempid(self.class.partition, temp_ref)
575
+ end
576
+
577
+ # Checks to see if the two objects are the same entity in Datomic.
578
+ #
579
+ # @return [Boolean]
580
+ def ==(other)
581
+ return false if self.dbid.nil?
582
+ return false unless other.respond_to?(:dbid)
583
+ return false unless self.dbid == other.dbid
584
+ true
585
+ end
586
+
587
+ # Checks to see if the two objects are of the same type, are the same
588
+ # entity, and have the same attribute values.
589
+ #
590
+ # @return [Boolean]
591
+ def eql?(other)
592
+ return false unless self == other
593
+ return false unless self.class == other.class
594
+
595
+ attribute_names.each do |attr|
596
+ return false unless self.send(attr) == other.send(attr)
597
+ end
598
+
599
+ true
600
+ end
601
+
602
+ # Methods for ActiveModel compliance.
603
+
604
+ # For ActiveModel compliance.
605
+ # @return [self]
606
+ def to_model
607
+ self
608
+ end
609
+
610
+ # Key for use in REST URL's, etc.
611
+ # @return [Integer, nil]
612
+ def to_key
613
+ persisted? ? [dbid] : nil
614
+ end
615
+
616
+ # @return [Boolean] Is the entity persisted?
617
+ def persisted?
618
+ !dbid.nil?
619
+ end
620
+
621
+ # @return [Boolean] Is this a new entity (that is, not persisted)?
622
+ def new_record?
623
+ !persisted?
624
+ end
625
+
626
+ # @return [false] Is this entity destroyed? (Always false.) For
627
+ # ActiveModel compliance.
628
+ def destroyed?
629
+ false
630
+ end
631
+
632
+ # @return [Boolean] Is this entity valid? By default, this is
633
+ # always true.
634
+ def valid?
635
+ errors.empty?
636
+ end
637
+
638
+ # @return [ActiveModel::Errors] Errors on this entity. By default,
639
+ # this is empty.
640
+ def errors
641
+ @errors ||= ActiveModel::Errors.new(self)
642
+ end
643
+
644
+ # Update method updates attribute values and saves new values in datomic.
645
+ # The method receives hash as an argument.
646
+ def update(attrs)
647
+ attrs.each do |k, v|
648
+ self.send(k.to_s+"=", v)
649
+ self.changed_attributes[k]=v
650
+ end
651
+ self.save if self.respond_to? :save
652
+ true
653
+ end
654
+
655
+ def destroy
656
+ self.retract_entity(dbid)
657
+ end
658
+ end
659
+ end