diametric 0.1.1-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 +7 -0
- data/Gemfile +28 -0
- data/Jarfile +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +264 -0
- data/Rakefile +49 -0
- data/bin/datomic-rest +33 -0
- data/bin/download-datomic +13 -0
- data/datomic_version.yml +4 -0
- data/diametric-java.gemspec +39 -0
- data/ext/diametric/DiametricCollection.java +147 -0
- data/ext/diametric/DiametricConnection.java +113 -0
- data/ext/diametric/DiametricDatabase.java +107 -0
- data/ext/diametric/DiametricEntity.java +90 -0
- data/ext/diametric/DiametricListenableFuture.java +47 -0
- data/ext/diametric/DiametricObject.java +66 -0
- data/ext/diametric/DiametricPeer.java +414 -0
- data/ext/diametric/DiametricService.java +102 -0
- data/ext/diametric/DiametricUUID.java +61 -0
- data/ext/diametric/DiametricUtils.java +183 -0
- data/lib/boolean_type.rb +3 -0
- data/lib/diametric.rb +42 -0
- data/lib/diametric/config.rb +54 -0
- data/lib/diametric/config/environment.rb +42 -0
- data/lib/diametric/entity.rb +659 -0
- data/lib/diametric/errors.rb +13 -0
- data/lib/diametric/generators/active_model.rb +42 -0
- data/lib/diametric/persistence.rb +48 -0
- data/lib/diametric/persistence/common.rb +82 -0
- data/lib/diametric/persistence/peer.rb +154 -0
- data/lib/diametric/persistence/rest.rb +107 -0
- data/lib/diametric/query.rb +259 -0
- data/lib/diametric/railtie.rb +52 -0
- data/lib/diametric/rest_service.rb +74 -0
- data/lib/diametric/service_base.rb +77 -0
- data/lib/diametric/transactor.rb +86 -0
- data/lib/diametric/version.rb +3 -0
- data/lib/diametric_service.jar +0 -0
- data/lib/tasks/create_schema.rb +27 -0
- data/lib/tasks/diametric_config.rb +45 -0
- data/lib/value_enums.rb +8 -0
- data/spec/conf_helper.rb +55 -0
- data/spec/config/free-transactor-template.properties +73 -0
- data/spec/config/logback.xml +59 -0
- data/spec/data/seattle-data0.dtm +452 -0
- data/spec/data/seattle-data1.dtm +326 -0
- data/spec/developer_create_sample.rb +39 -0
- data/spec/developer_query_spec.rb +120 -0
- data/spec/diametric/config_spec.rb +60 -0
- data/spec/diametric/entity_spec.rb +476 -0
- data/spec/diametric/peer_api_spec.rb +147 -0
- data/spec/diametric/persistence/peer_many2many_spec.rb +76 -0
- data/spec/diametric/persistence/peer_spec.rb +27 -0
- data/spec/diametric/persistence/rest_spec.rb +30 -0
- data/spec/diametric/persistence_spec.rb +59 -0
- data/spec/diametric/query_spec.rb +118 -0
- data/spec/diametric/rest_service_spec.rb +56 -0
- data/spec/diametric/transactor_spec.rb +68 -0
- data/spec/integration_spec.rb +107 -0
- data/spec/parent_child_sample.rb +42 -0
- data/spec/peer_integration_spec.rb +121 -0
- data/spec/peer_seattle_spec.rb +200 -0
- data/spec/rc2013_seattle_big.rb +82 -0
- data/spec/rc2013_seattle_small.rb +60 -0
- data/spec/rc2013_simple_sample.rb +72 -0
- data/spec/seattle_integration_spec.rb +106 -0
- data/spec/simple_validation_sample.rb +31 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/support/entities.rb +157 -0
- data/spec/support/gen_entity_class.rb +9 -0
- data/spec/support/persistence_examples.rb +104 -0
- data/spec/test_version_file.yml +4 -0
- 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
|