diametric 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +6 -14
  2. data/Gemfile +3 -3
  3. data/Jarfile +13 -6
  4. data/LICENSE.txt +2 -2
  5. data/README.md +57 -26
  6. data/Rakefile +1 -0
  7. data/bin/datomic-rest +1 -1
  8. data/bin/download-datomic +1 -1
  9. data/datomic_version.yml +2 -2
  10. data/diametric.gemspec +8 -7
  11. data/ext/diametric/DiametricCollection.java +45 -38
  12. data/ext/diametric/DiametricConnection.java +16 -15
  13. data/ext/diametric/DiametricDatabase.java +9 -8
  14. data/ext/diametric/DiametricEntity.java +104 -21
  15. data/ext/diametric/DiametricObject.java +12 -1
  16. data/ext/diametric/DiametricPeer.java +52 -31
  17. data/ext/diametric/DiametricService.java +2 -0
  18. data/ext/diametric/DiametricSet.java +11 -4
  19. data/ext/diametric/DiametricUUID.java +8 -1
  20. data/ext/diametric/DiametricUtils.java +90 -62
  21. data/lib/diametric.rb +1 -0
  22. data/lib/diametric/associations/collection.rb +103 -0
  23. data/lib/diametric/entity.rb +166 -103
  24. data/lib/diametric/persistence/common.rb +0 -44
  25. data/lib/diametric/persistence/peer.rb +53 -2
  26. data/lib/diametric/persistence/rest.rb +27 -1
  27. data/lib/diametric/query.rb +49 -31
  28. data/lib/diametric/rest_service.rb +8 -9
  29. data/lib/diametric/service_base.rb +7 -7
  30. data/lib/diametric/transactor.rb +6 -5
  31. data/lib/diametric/version.rb +1 -1
  32. data/lib/diametric_service.jar +0 -0
  33. data/spec/config/free-transactor-template.properties +6 -6
  34. data/spec/developer_query_spec.rb +17 -6
  35. data/spec/diametric/entity_spec.rb +62 -139
  36. data/spec/diametric/peer_api_spec.rb +23 -23
  37. data/spec/diametric/persistence/peer_spec.rb +73 -11
  38. data/spec/diametric/persistence/rest_spec.rb +108 -16
  39. data/spec/diametric/query_spec.rb +3 -3
  40. data/spec/diametric/rest_service_spec.rb +4 -4
  41. data/spec/diametric/schema_spec.rb +526 -0
  42. data/spec/diametric/transactor_spec.rb +5 -6
  43. data/spec/integration_spec.rb +7 -7
  44. data/spec/peer_integration_spec.rb +25 -1
  45. data/spec/peer_seattle_spec.rb +1 -2
  46. data/spec/spec_helper.rb +31 -4
  47. data/spec/support/cardinarity_many_example.rb +37 -0
  48. data/spec/support/entities.rb +127 -0
  49. data/spec/support/has_a_example.rb +31 -0
  50. data/spec/support/has_many_example.rb +79 -0
  51. data/spec/support/persistence_examples.rb +13 -5
  52. data/spec/support/various_types_examples.rb +163 -0
  53. data/spec/test_version_file.yml +2 -2
  54. metadata +66 -15
@@ -31,6 +31,13 @@ public class DiametricSet extends RubyObject {
31
31
  private Integer count = null; // unable to count the vector size that exceeds Integer
32
32
  private DiametricCommon common = null;
33
33
 
34
+ static IRubyObject getDiametricSet(ThreadContext context, Set value) {
35
+ RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::Set");
36
+ DiametricSet diametric_set = (DiametricSet)clazz.allocate();
37
+ diametric_set.init(value);
38
+ return diametric_set;
39
+ }
40
+
34
41
  public DiametricSet(Ruby runtime, RubyClass klazz) {
35
42
  super(runtime, klazz);
36
43
  }
@@ -242,9 +249,9 @@ public class DiametricSet extends RubyObject {
242
249
  Var var = DiametricService.getFn("clojure.set", "union");
243
250
  if (set instanceof HashSet) {
244
251
  PersistentHashSet value = convertHashSetToPersistentHashSet(set);
245
- return DiametricUtils.getDiametricSet(context, (Set)var.invoke(value, other));
252
+ return DiametricSet.getDiametricSet(context, (Set)var.invoke(value, other));
246
253
  } else {
247
- return DiametricUtils.getDiametricSet(context, (Set)var.invoke(set, other));
254
+ return DiametricSet.getDiametricSet(context, (Set)var.invoke(set, other));
248
255
  }
249
256
  } catch (Throwable t) {
250
257
  throw context.getRuntime().newRuntimeError(t.getMessage());
@@ -259,9 +266,9 @@ public class DiametricSet extends RubyObject {
259
266
  Var var = DiametricService.getFn("clojure.set", "difference");
260
267
  if (set instanceof HashSet) {
261
268
  PersistentHashSet value = convertHashSetToPersistentHashSet(set);
262
- return DiametricUtils.getDiametricSet(context, (Set)var.invoke(value, other));
269
+ return DiametricSet.getDiametricSet(context, (Set)var.invoke(value, other));
263
270
  } else {
264
- return DiametricUtils.getDiametricSet(context, (Set)var.invoke(set, other));
271
+ return DiametricSet.getDiametricSet(context, (Set)var.invoke(set, other));
265
272
  }
266
273
  } catch (Throwable t) {
267
274
  throw context.getRuntime().newRuntimeError(t.getMessage());
@@ -17,6 +17,13 @@ public class DiametricUUID extends RubyObject {
17
17
  private static final long serialVersionUID = 2083281771243513904L;
18
18
  private java.util.UUID java_uuid = null;
19
19
 
20
+ static IRubyObject getDiametricUUID(ThreadContext context, java.util.UUID value) {
21
+ RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::UUID");
22
+ DiametricUUID diametric_uuid = (DiametricUUID)clazz.allocate();
23
+ diametric_uuid.init((java.util.UUID)value);
24
+ return diametric_uuid;
25
+ }
26
+
20
27
  public DiametricUUID(Ruby runtime, RubyClass klazz) {
21
28
  super(runtime, klazz);
22
29
  }
@@ -34,7 +41,7 @@ public class DiametricUUID extends RubyObject {
34
41
  RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::UUID");
35
42
  DiametricUUID diametric_uuid = (DiametricUUID)clazz.allocate();
36
43
  try {
37
- java.util.UUID java_uuid = (UUID) clojure.lang.RT.var("datomic.api", "squuid").invoke();
44
+ java.util.UUID java_uuid = (UUID) DiametricService.getFn("datomic.api", "squuid").invoke();
38
45
  diametric_uuid.init(java_uuid);
39
46
  return diametric_uuid;
40
47
  } catch (Throwable t) {
@@ -6,10 +6,7 @@ import java.io.Reader;
6
6
  import java.util.ArrayList;
7
7
  import java.util.Collections;
8
8
  import java.util.Date;
9
- import java.util.HashMap;
10
- import java.util.HashSet;
11
9
  import java.util.List;
12
- import java.util.Map;
13
10
  import java.util.Set;
14
11
  import java.util.UUID;
15
12
 
@@ -23,6 +20,7 @@ import org.jruby.RubyFloat;
23
20
  import org.jruby.RubyHash;
24
21
  import org.jruby.RubyNil;
25
22
  import org.jruby.RubyString;
23
+ import org.jruby.RubySymbol;
26
24
  import org.jruby.RubyTime;
27
25
  import org.jruby.anno.JRubyMethod;
28
26
  import org.jruby.anno.JRubyModule;
@@ -31,11 +29,14 @@ import org.jruby.javasupport.util.RuntimeHelpers;
31
29
  import org.jruby.runtime.ThreadContext;
32
30
  import org.jruby.runtime.builtin.IRubyObject;
33
31
 
32
+ import clojure.lang.APersistentMap;
34
33
  import clojure.lang.APersistentVector;
35
34
  import clojure.lang.IPersistentSet;
35
+ import clojure.lang.Keyword;
36
36
  import clojure.lang.LazySeq;
37
37
  import clojure.lang.PersistentHashSet;
38
38
  import clojure.lang.PersistentVector;
39
+ import clojure.lang.Var;
39
40
  import datomic.Util;
40
41
 
41
42
  @JRubyModule(name="Diametric::Persistence::Utils")
@@ -67,6 +68,24 @@ public class DiametricUtils {
67
68
  }
68
69
  }
69
70
 
71
+ @JRubyMethod(meta=true)
72
+ public static IRubyObject read_string(ThreadContext context, IRubyObject klazz, IRubyObject arg) {
73
+ if (!(arg instanceof RubyString)) {
74
+ throw context.getRuntime().newArgumentError("Argument should be string");
75
+ }
76
+ RubyString ruby_string = (RubyString)arg;
77
+ try {
78
+ Var reader = DiametricService.getFn("clojure.core", "read-string");
79
+ Object value = reader.invoke((String)ruby_string.asJavaString());
80
+ RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::Object");
81
+ DiametricObject diametric_object = (DiametricObject)clazz.allocate();
82
+ diametric_object.update(value);
83
+ return diametric_object;
84
+ } catch (Exception e) {
85
+ throw context.getRuntime().newRuntimeError(e.getMessage());
86
+ }
87
+ }
88
+
70
89
  static String rubyStringToJava(IRubyObject arg) {
71
90
  if (arg instanceof RubyString) {
72
91
  // TODO probably, we need to specify encoding.
@@ -80,11 +99,7 @@ public class DiametricUtils {
80
99
  if (value instanceof RubyNil) return null;
81
100
  if (value instanceof RubyString) {
82
101
  String str = (String)((RubyString)value).toJava(String.class);
83
- try {
84
- return (Object)UUID.fromString(str);
85
- } catch (IllegalArgumentException e) {
86
- return (Object)str;
87
- }
102
+ return getStringOrUUID(str);
88
103
  }
89
104
  if (value instanceof RubyBoolean) return (Object)((RubyBoolean)value).toJava(Boolean.class);
90
105
  if (value instanceof RubyFixnum) return (Object)((RubyFixnum)value).toJava(Long.class);
@@ -99,6 +114,16 @@ public class DiametricUtils {
99
114
  RubyTime tmvalue = (RubyTime)value;
100
115
  return (Object)tmvalue.getJavaDate();
101
116
  }
117
+ if (value instanceof RubySymbol) {
118
+ // schema or data keyword
119
+ RubyString edn_string = (RubyString)RuntimeHelpers.invoke(context, value, "to_s");
120
+ return (Object)Keyword.intern((String)edn_string.asJavaString());
121
+ }
122
+ if (value.respondsTo("to_edn") && value.respondsTo("symbol")) {
123
+ // EDN::Type::Symbol (query)
124
+ RubyString edn_string = (RubyString)RuntimeHelpers.invoke(context, value, "to_edn");
125
+ return (Object)clojure.lang.Symbol.intern((String)edn_string.asJavaString());
126
+ }
102
127
  if (value.respondsTo("to_time")) {
103
128
  // DateTime or Date
104
129
  RubyTime tmvalue = (RubyTime)RuntimeHelpers.invoke(context, value, "to_time");
@@ -110,15 +135,23 @@ public class DiametricUtils {
110
135
  }
111
136
  //System.out.println("NOT YET CONVERTED");
112
137
  //System.out.println("RESPONDSTO? TO_A:" + value.respondsTo("to_a"));
113
- if (value.respondsTo("to_a")) { // might be Set for cardinality many type
114
- return getList(context, value);
115
- }
138
+ //if (value.respondsTo("to_a")) { // might be Set for cardinality many type
139
+ // return getList(context, value);
140
+ //}
116
141
  if (value instanceof DiametricObject) {
117
142
  return ((DiametricObject)value).toJava();
118
143
  }
119
144
  return (Object)value.toJava(Object.class);
120
145
  }
121
146
 
147
+ static Object getStringOrUUID(String str) {
148
+ try {
149
+ return (Object)UUID.fromString(str);
150
+ } catch (IllegalArgumentException e) {
151
+ return (Object)str;
152
+ }
153
+ }
154
+
122
155
  static IPersistentSet getPersistentSet(ThreadContext context, IRubyObject value) {
123
156
  return PersistentHashSet.create((List)value.callMethod(context, "to_a"));
124
157
  }
@@ -147,79 +180,74 @@ public class DiametricUtils {
147
180
  if (value instanceof Double) return RubyFloat.newFloat(runtime, (Double)value);
148
181
  if (value instanceof Date) return RubyTime.newTime(runtime, ((Date)value).getTime());
149
182
  if (value instanceof Set) {
150
- return getDiametricSet(context, (Set)value);
183
+ return DiametricSet.getDiametricSet(context, (Set)value);
151
184
  }
152
185
  if ((value instanceof APersistentVector) ||
153
186
  (value instanceof LazySeq) ||
154
187
  (value instanceof PersistentVector.ChunkedSeq)){
155
- return getDiametricCollection(context, (List)value);
188
+ return DiametricCollection.getDiametricCollection(context, (List)value);
189
+ }
190
+ if (value instanceof datomic.Entity) {
191
+ return DiametricEntity.getDiametricEntity(context, value);
156
192
  }
157
193
  if (value instanceof java.util.UUID) {
158
- RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::UUID");
159
- DiametricUUID diametric_uuid = (DiametricUUID)clazz.allocate();
160
- diametric_uuid.init((java.util.UUID)value);
161
- return diametric_uuid;
194
+ return DiametricUUID.getDiametricUUID(context, (java.util.UUID)value);
162
195
  }
163
196
  return JavaUtil.convertJavaToUsableRubyObject(runtime, value);
164
197
  }
165
198
 
166
- static IRubyObject getDiametricSet(ThreadContext context, Set value) {
167
- RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::Set");
168
- DiametricSet diametric_set = (DiametricSet)clazz.allocate();
169
- diametric_set.init(value);
170
- return diametric_set;
171
- }
172
-
173
- static IRubyObject getDiametricCollection(ThreadContext context, List value) {
174
- RubyClass clazz = (RubyClass)context.getRuntime().getClassFromPath("Diametric::Persistence::Collection");
175
- DiametricCollection diametric_collection = (DiametricCollection)clazz.allocate();
176
- diametric_collection.init((List)value);
177
- return diametric_collection;
178
- }
179
-
180
- static List<Object> convertRubyTxDataToJava(ThreadContext context, IRubyObject arg) {
181
- List<Object> tx_data = null;
199
+ static PersistentVector convertRubyTxDataToJava(ThreadContext context, IRubyObject arg) {
182
200
  if (arg instanceof RubyArray) {
183
- tx_data = fromRubyArray(context, arg);
201
+ return fromRubyArray(context, (RubyArray)arg);
184
202
  } else {
185
203
  Object obj = arg.toJava(Object.class);
186
204
  if (obj instanceof clojure.lang.PersistentVector) {
187
- tx_data = (clojure.lang.PersistentVector)obj;
205
+ return (clojure.lang.PersistentVector)obj;
188
206
  }
189
207
  }
190
- return tx_data;
208
+ return null;
191
209
  }
192
210
 
193
- private static List<Object> fromRubyArray(ThreadContext context, IRubyObject arg) {
194
- RubyArray ruby_tx_data = (RubyArray)arg;
195
- List<Object> java_tx_data = new ArrayList<Object>();
196
- for (int i=0; i<ruby_tx_data.getLength(); i++) {
197
- IRubyObject element = (IRubyObject) ruby_tx_data.get(i);
211
+ static PersistentVector fromRubyArray(ThreadContext context, RubyArray ruby_array) {
212
+ Var var = DiametricService.getFn("clojure.core", "vector");
213
+ PersistentVector clj_tx_data = (PersistentVector)var.invoke();
214
+ Var adder = DiametricService.getFn("clojure.core", "conj");
215
+ for (int i=0; i<ruby_array.getLength(); i++) {
216
+ Object element = ruby_array.get(i);
198
217
  if (element instanceof RubyHash) {
199
- RubyHash ruby_hash = (RubyHash) element;
200
- Map<Object, Object> keyvals = new HashMap<Object, Object>();
201
- while (true) {
202
- IRubyObject pair = ruby_hash.shift(context);
203
- if (pair instanceof RubyNil) break;
204
- Object key = DiametricUtils.convertRubyToJava(context, ((RubyArray) pair).shift(context));
205
- Object value = DiametricUtils.convertRubyToJava(context, ((RubyArray) pair).shift(context));
206
- keyvals.put(key, value);
207
- }
208
- java_tx_data.add(Collections.unmodifiableMap(keyvals));
218
+ APersistentMap map = fromRubyHash(context, (RubyHash)element);
219
+ clj_tx_data = (PersistentVector)adder.invoke(clj_tx_data, map);
209
220
  } else if (element instanceof RubyArray) {
210
- RubyArray ruby_array = (RubyArray) element;
211
- List<Object> keyvals = new ArrayList<Object>();
212
- while (true) {
213
- IRubyObject ruby_element = ruby_array.shift(context);
214
- if (ruby_element instanceof RubyNil) break;
215
- Object key_or_value = DiametricUtils.convertRubyToJava(context, ruby_element);
216
- keyvals.add(key_or_value);
217
- }
218
- java_tx_data.add(Collections.unmodifiableList(keyvals));
221
+ PersistentVector vector = fromRubyArray(context, (RubyArray)element);
222
+ clj_tx_data = (PersistentVector)adder.invoke(clj_tx_data, vector);
223
+ } else if (element instanceof IRubyObject) {
224
+ clj_tx_data =
225
+ (PersistentVector)adder.invoke(clj_tx_data, DiametricUtils.convertRubyToJava(context, (IRubyObject)element));
226
+ } else if (element instanceof String) {
227
+ clj_tx_data = (PersistentVector)adder.invoke(clj_tx_data, getStringOrUUID((String)element));
219
228
  } else {
220
- continue;
229
+ clj_tx_data = (PersistentVector)adder.invoke(clj_tx_data, element);
230
+ }
231
+ }
232
+ return clj_tx_data;
233
+ }
234
+
235
+ private static APersistentMap fromRubyHash(ThreadContext context, RubyHash ruby_hash) {
236
+ Var var = DiametricService.getFn("clojure.core", "hash-map");
237
+ APersistentMap map = (APersistentMap)var.invoke();
238
+ Var associator = DiametricService.getFn("clojure.core", "assoc");
239
+ while (true) {
240
+ IRubyObject pair = ruby_hash.shift(context);
241
+ if (pair instanceof RubyNil) break;
242
+ Object key = DiametricUtils.convertRubyToJava(context, ((RubyArray) pair).shift(context));
243
+ Object value = DiametricUtils.convertRubyToJava(context, ((RubyArray) pair).shift(context));
244
+ if (value instanceof RubyHash) {
245
+ value = fromRubyHash(context, (RubyHash)value);
246
+ } else if (value instanceof RubyArray) {
247
+ value = fromRubyArray(context, (RubyArray)value);
221
248
  }
249
+ map = (APersistentMap)associator.invoke(map, key, value);
222
250
  }
223
- return java_tx_data;
251
+ return map;
224
252
  }
225
253
  }
@@ -1,5 +1,6 @@
1
1
  require "diametric/version"
2
2
  require "diametric/entity"
3
+ require "diametric/associations/collection"
3
4
  require "diametric/query"
4
5
  require "diametric/persistence"
5
6
  require "diametric/errors"
@@ -0,0 +1,103 @@
1
+ module Diametric
2
+ module Associations
3
+ class Collection
4
+ def initialize(base, name, enum=nil, &block)
5
+ @base = base
6
+ @attribute_name = name
7
+ @data = Set.new(enum, &block)
8
+ end
9
+
10
+ def add(*entities)
11
+ old_data = @data.dup
12
+ entities.each do |entity|
13
+ if entity.dbid.nil? || entity.dbid.to_i < 0
14
+ entity.save
15
+ end
16
+ @data << entity.dbid
17
+ end
18
+ if old_data != @data
19
+ @base.send("#{@attribute_name}=", self)
20
+ end
21
+ end
22
+ alias :<< :add
23
+
24
+ def add_reified_entities(*entities)
25
+ entities.each do |entity|
26
+ @data << entity
27
+ end
28
+ @base.send("clean_#{@attribute_name}=", self)
29
+ end
30
+
31
+ def delete(*entities)
32
+ entities.each do |entity|
33
+ return unless self.include?(entity)
34
+ if entity.is_a?(Fixnum) || entity.respond_to?(:to_i)
35
+ # dbid or dbid object
36
+ @data.delete_if {|e| e.to_i == entity.to_i}
37
+ elsif entity.respond_to?(:dbid)
38
+ # reified or created entity
39
+ @data.delete_if {|e| e.dbid == entity.dbid}
40
+ end
41
+ end
42
+ end
43
+
44
+ def destroy(*entities)
45
+ entities.each do |entity|
46
+ return unless self.include?(entity)
47
+ if entity.respond_to? :destroy
48
+ entity.destroy
49
+ end
50
+ self.delete(entity)
51
+ end
52
+ end
53
+
54
+ def include?(o)
55
+ if o.is_a?(Fixnum) || o.respond_to?(:to_i)
56
+ @data.find_all {|e| e.respond_to?(:to_i)}.collect(&:to_i).include?(o.to_i)
57
+ elsif o.respond_to?(:dbid)
58
+ @data.find_all {|e| e.respond_to?(:dbid)}.collect(&:dbid).include?(o.dbid)
59
+ else
60
+ false
61
+ end
62
+ end
63
+ alias :member? :include?
64
+
65
+ def &(enum)
66
+ # this method is used to test the given class is Set or not.
67
+ # don't delete this method.
68
+ n = self.class.new
69
+ @data.each { |o| n.add(o) if include?(o) }
70
+ n
71
+ end
72
+ alias intersection &
73
+
74
+ def inspect
75
+ return sprintf('#<%s: {%s}>', self.class, @data.to_a.inspect[1..-2])
76
+ end
77
+
78
+ def replace(entities)
79
+ @data = Set.new
80
+ entities.each do |entity|
81
+ if entity.dbid.nil? || entity.dbid.to_i < 0
82
+ @data << entity.dbid if entity.save
83
+ else
84
+ @data << entity
85
+ end
86
+ end
87
+ self
88
+ end
89
+
90
+ def method_missing(method, *args, &block)
91
+ if !args.empty? && block
92
+ @data.send(method, args, &block)
93
+ elsif !args.empty?
94
+ @data.send(method, args)
95
+ elsif block
96
+ @data.send(method, &block)
97
+ else
98
+ @data.send(method)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -51,7 +51,7 @@ module Diametric
51
51
  :cardinality => :one
52
52
  }
53
53
 
54
- @temp_ref = -1000
54
+ @temp_ref = -1000000
55
55
 
56
56
  def self.included(base)
57
57
  base.send(:extend, ClassMethods)
@@ -210,13 +210,7 @@ module Diametric
210
210
  # @return [Array] A Datomic schema, as Ruby data that can be
211
211
  # converted to EDN.
212
212
  def schema
213
- return peer_schema if self.instance_variable_get("@peer")
214
- rest_schema
215
- end
216
-
217
- def rest_schema
218
213
  defaults = {
219
- :"db/id" => tempid(:"db.part/db"),
220
214
  :"db/cardinality" => :"db.cardinality/one",
221
215
  :"db.install/_attribute" => :"db.part/db"
222
216
  }
@@ -238,13 +232,13 @@ module Diametric
238
232
  schema << defaults.merge({
239
233
  :"db/ident" => namespace(prefix, attribute),
240
234
  :"db/valueType" => value_type(value_type),
235
+ :"db/id" => tempid(:"db.part/db")
241
236
  }).merge(opts)
242
237
  end
243
-
244
238
  enum_schema = [
245
239
  :"db/add", tempid(:"db.part/user"), :"db/ident"
246
240
  ]
247
- prefix = self.name.downcase
241
+ prefix = self.to_s.underscore
248
242
  @enums.each do |key, values|
249
243
  values.each do |value|
250
244
  ident_value = :"#{prefix}.#{key.downcase}/#{value.to_s.sub(/_/, "-").downcase}"
@@ -255,48 +249,6 @@ module Diametric
255
249
  schema_array
256
250
  end
257
251
 
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
252
  # Given a set of Ruby data returned from a Datomic query, this
301
253
  # can re-hydrate that data into a model instance.
302
254
  #
@@ -316,13 +268,20 @@ module Diametric
316
268
  parent.class.attribute_names.each do |e|
317
269
  if parent.class.attributes[e][:value_type] == "ref"
318
270
  ref = parent.instance_variable_get("@#{e.to_s}")
319
- if ref.is_a?(Fixnum) || ref.is_a?(Java::DatomicQuery::EntityMap)
271
+ if ref.is_a?(Fixnum) ||
272
+ (self.instance_variable_get("@peer") && ref.is_a?(Diametric::Persistence::Entity))
320
273
  child = reify(ref, connection)
321
274
  child = resolve_ref_dbid(child, connection)
322
275
  parent.instance_variable_set("@#{e.to_s}", child)
276
+ elsif ref.is_a?(Diametric::Associations::Collection)
277
+ children = Diametric::Associations::Collection.new(parent, e.to_s)
278
+ ref.each do |entity|
279
+ children.add_reified_entities(reify(entity, connection))
280
+ end
281
+ parent.instance_variable_set("@#{e.to_s}", children)
323
282
  elsif ref.is_a?(Set)
324
283
  children = ref.inject(Set.new) do |memo, entity|
325
- child = reify(entity, connection)
284
+ child = reify(entity, connection)
326
285
  memo.add(child)
327
286
  memo
328
287
  end
@@ -334,6 +293,38 @@ module Diametric
334
293
  end
335
294
 
336
295
  def reify(thing, conn_or_db=nil, resolve=false)
296
+ return peer_reify(thing, conn_or_db, resolve) if self.instance_variable_get("@peer")
297
+ rest_reify(thing, resolve)
298
+ end
299
+
300
+ def rest_reify(dbid, resolve)
301
+ query = [
302
+ :find, ~"?ident", ~"?v",
303
+ :in, ~"\$", [~"?e"],
304
+ :where, [~"?e", ~"?a", ~"?v"], [~"?a", :"db/ident", ~"?ident"]
305
+ ]
306
+ entities = self.q(query, [[dbid]])
307
+ class_name = to_classname(entities.first.first)
308
+ instance = eval("#{class_name}.new")
309
+ entities.each do |k, v|
310
+ matched_data = /([a-zA-Z0-9_\.]+)\/([a-zA-Z0-9_]+)/.match(k.to_s)
311
+ attribute = instance.send(matched_data[2])
312
+ if attribute && attribute.is_a?(Diametric::Associations::Collection)
313
+ attribute.add_reified_entities(v)
314
+ else
315
+ instance.send("clean_#{matched_data[2]}=", v)
316
+ end
317
+ end
318
+ instance.send("dbid=", dbid)
319
+
320
+ if resolve
321
+ instance = resolve_ref_dbid(instance, nil)
322
+ end
323
+
324
+ instance
325
+ end
326
+
327
+ def peer_reify(thing, conn_or_db=nil, resolve=false)
337
328
  conn_or_db ||= Diametric::Persistence::Peer.connect.db
338
329
 
339
330
  if conn_or_db.respond_to?(:db)
@@ -359,13 +350,11 @@ module Diametric
359
350
  return thing
360
351
  end
361
352
  first_key = entity.keys.first
362
- match_data = /:([a-zA-Z0-9_]+)\/([a-zA-Z0-9_]+)/.match(first_key)
363
- entity_name = match_data[1].capitalize.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}"}
364
- instance = eval("#{entity_name}.new")
365
- instance.send("#{match_data[2]}=", entity[first_key])
366
- entity.keys[1..-1].each do |key|
367
- match_data = /:([a-zA-Z0-9_]+)\/([a-zA-Z0-9_]+)/.match(key)
368
- instance.send("#{match_data[2]}=", entity[key])
353
+ class_name = to_classname(first_key)
354
+ instance = eval("#{class_name}.new")
355
+ entity.keys.each do |key|
356
+ matched_data = /:([a-zA-Z0-9_\.]+)\/([a-zA-Z0-9_]+)/.match(key)
357
+ instance.send("clean_#{matched_data[2]}=", entity[key])
369
358
  end
370
359
  instance.send("dbid=", Diametric::Persistence::Object.new(entity.get("db/id")))
371
360
 
@@ -376,6 +365,34 @@ module Diametric
376
365
  instance
377
366
  end
378
367
 
368
+ def to_classname(key)
369
+ names = []
370
+ # drops the first character ":"
371
+ key = key.to_s
372
+ if key[0] == ":"
373
+ key = key[1..-1]
374
+ end
375
+ key.chars.inject("") do |memo, c|
376
+ if c == "/"
377
+ # means the end of class name
378
+ names << memo
379
+ break
380
+ elsif c == "."
381
+ # namespace delimiter
382
+ names << memo
383
+ memo = ""
384
+ elsif c == "-"
385
+ # Clojure uses - for name, but Ruby can't
386
+ # converts dash to underscore
387
+ memo << "_"
388
+ else
389
+ memo << c
390
+ end
391
+ memo
392
+ end
393
+ names.collect(&:camelize).join("::")
394
+ end
395
+
379
396
  def find(id)
380
397
  if self.instance_variable_get("@peer")
381
398
  connection ||= Diametric::Persistence::Peer.connect
@@ -383,19 +400,6 @@ module Diametric
383
400
  reify(id, connection)
384
401
  end
385
402
 
386
- # Returns the prefix for this model used in Datomic. Can be
387
- # overriden by declaring {#namespace_prefix}
388
- #
389
- # @example
390
- # Mouse.prefix #=> "mouse"
391
- # FireHouse.prefix #=> "fire_house"
392
- # Person::User.prefix #=> "person.user"
393
- #
394
- # @return [String]
395
- def prefix
396
- @namespace_prefix || self.to_s.underscore.sub('/', '.')
397
- end
398
-
399
403
  # Create a temporary id placeholder.
400
404
  #
401
405
  # @param e [*#to_edn] Elements to put in the placeholder. Should
@@ -405,16 +409,25 @@ module Diametric
405
409
  # @return [EDN::Type::Unknown] Temporary id placeholder.
406
410
  def tempid(*e)
407
411
  if self.instance_variable_get("@peer")
408
- if e[0].to_s.include?("user")
409
- return Diametric::Persistence::Peer.tempid(":db.part/user")
410
- else
411
- return Diametric::Persistence::Peer.tempid(":db.part/db")
412
- end
412
+ Diametric::Persistence::Peer.tempid(*e)
413
413
  else
414
- EDN.tagged_element('db/id', e)
414
+ Diametric::Persistence::REST.tempid(*e)
415
415
  end
416
416
  end
417
417
 
418
+ # Returns the prefix for this model used in Datomic. Can be
419
+ # overriden by declaring {#namespace_prefix}
420
+ #
421
+ # @example
422
+ # Mouse.prefix #=> "mouse"
423
+ # FireHouse.prefix #=> "fire_house"
424
+ # Person::User.prefix #=> "person.user"
425
+ #
426
+ # @return [String]
427
+ def prefix
428
+ @namespace_prefix || self.to_s.underscore.gsub('/', '.')
429
+ end
430
+
418
431
  # Namespace a attribute for Datomic.
419
432
  #
420
433
  # @param ns [#to_s] Namespace.
@@ -422,11 +435,7 @@ module Diametric
422
435
  #
423
436
  # @return [Symbol] Namespaced attribute.
424
437
  def namespace(ns, attribute)
425
- if self.instance_variable_get("@peer")
426
- ":" + [ns.to_s, attribute.to_s].join("/")
427
- else
428
- [ns.to_s, attribute.to_s].join("/").to_sym
429
- end
438
+ [ns.to_s, attribute.to_s].join("/").to_sym
430
439
  end
431
440
 
432
441
  # Raise an error if validation failed.
@@ -457,13 +466,72 @@ module Diametric
457
466
  define_attribute_method name
458
467
 
459
468
  define_method(name) do
460
- instance_variable_get("@#{name}")
469
+ ivar = instance_variable_get("@#{name}")
470
+ if ivar.nil? &&
471
+ self.class.attributes[name][:value_type] == Ref &&
472
+ self.class.attributes[name][:cardinality] == :many
473
+ ivar = Diametric::Associations::Collection.new(self, name)
474
+ end
475
+ ivar
461
476
  end
462
477
 
463
478
  define_method("#{name}=") do |value|
464
479
  send("#{name}_will_change!") unless value == instance_variable_get("@#{name}")
465
- if cardinality == :many
466
- value = Set.new(value)
480
+ case self.class.attributes[name][:value_type]
481
+ when Ref
482
+ case cardinality
483
+ when :many
484
+ if value.is_a?(Enumerable) && value.first.respond_to?(:save)
485
+ # entity type
486
+ ivar = send("#{name}")
487
+ instance_variable_set("@#{name}", ivar.replace(value))
488
+ elsif value.is_a?(Diametric::Associations::Collection)
489
+ instance_variable_set("@#{name}", value)
490
+ elsif value.is_a?(Enumerable)
491
+ # enum type
492
+ # however, falls here when empty array is given for entity type
493
+ instance_variable_set("@#{name}", Set.new(value))
494
+ end
495
+ when :one
496
+ if value.respond_to?(:save)
497
+ # entity type
498
+ if value.save
499
+ instance_variable_set("@#{name}", value.dbid)
500
+ end
501
+ else
502
+ # enum type
503
+ instance_variable_set("@#{name}", value)
504
+ end
505
+ end
506
+ else # not Ref
507
+ case cardinality
508
+ when :many
509
+ instance_variable_set("@#{name}", Set.new(value))
510
+ when :one
511
+ instance_variable_set("@#{name}", value)
512
+ end
513
+ end
514
+ end
515
+
516
+ define_method("clean_#{name}=") do |value|
517
+ if self.class.attributes[name][:value_type] != Ref && cardinality == :many
518
+ if value.is_a? Enumerable
519
+ value = Set.new(value)
520
+ else
521
+ # used from rest reify
522
+ ivar = instance_variable_get("@#{name}")
523
+ ivar ||= Set.new
524
+ value = ivar.add(value)
525
+ end
526
+ end
527
+ if self.class.attributes[name][:value_type] == Ref &&
528
+ cardinality == :many &&
529
+ !(value.is_a? Diametric::Associations::Collection)
530
+ if value.is_a? Enumerable
531
+ value = Diametric::Associations::Collection.new(self, name, value)
532
+ else
533
+ value = Diametric::Associations::Collection.new(self, name, [value])
534
+ end
467
535
  end
468
536
  instance_variable_set("@#{name}", value)
469
537
  end
@@ -512,7 +580,6 @@ module Diametric
512
580
  attribute_names.each do |attribute_name|
513
581
  cardinality = self.class.attributes[attribute_name.to_sym][:cardinality]
514
582
 
515
- #if cardinality == :many && self.class.instance_variable_get("@peer").nil?
516
583
  if cardinality == :many
517
584
  txes += cardinality_many_tx_data(attribute_name)
518
585
  else
@@ -521,42 +588,38 @@ module Diametric
521
588
  end
522
589
 
523
590
  if entity_tx.present?
524
- if self.class.instance_variable_get("@peer")
525
- @dbid ||= tempid
526
- txes << entity_tx.merge({":db/id" => dbid})
527
- else
528
- txes << entity_tx.merge({:"db/id" => dbid || tempid})
529
- end
591
+ @dbid ||= tempid
592
+ txes << entity_tx.merge({:"db/id" => dbid})
530
593
  end
531
594
  txes
532
595
  end
533
596
 
534
597
  def cardinality_many_tx_data(attribute_name)
535
- prev = Array(self.changed_attributes[attribute_name]).to_set
598
+ changed = self.changed_attributes[attribute_name]
599
+ prev =
600
+ changed.is_a?(Diametric::Associations::Collection) ? changed.to_set : Array(changed).to_set
536
601
  curr = self.send(attribute_name)
602
+ curr = curr.is_a?(Diametric::Associations::Collection) ? curr.to_set : curr
537
603
 
538
604
  protractions = curr - prev
539
605
  retractions = prev - curr
540
606
 
541
607
  namespaced_attribute = self.class.namespace(self.class.prefix, attribute_name)
542
608
  txes = []
543
- if self.class.instance_variable_get("@peer")
544
- @dbid ||= tempid
545
- txes_data(txes, ":db/retract", namespaced_attribute, retractions) unless retractions.empty?
546
- txes_data(txes, ":db/add", namespaced_attribute, protractions) unless protractions.empty?
547
- else
548
- txes << [:"db/retract", (dbid || tempid), namespaced_attribute, retractions.to_a] unless retractions.empty?
549
- txes << [:"db/add", (dbid || tempid) , namespaced_attribute, protractions.to_a] unless protractions.empty?
550
- end
609
+ @dbid ||= tempid
610
+ txes << [:"db/retract", dbid, namespaced_attribute, retractions.to_a] unless retractions.empty?
611
+ txes << [:"db/add", dbid , namespaced_attribute, protractions.to_a] unless protractions.empty?
551
612
  txes
552
613
  end
553
614
 
615
+ =begin
554
616
  def txes_data(txes, op, namespaced_attribute, set)
555
617
  set.to_a.each do |s|
556
618
  value = s.respond_to?(:dbid) ? s.dbid : s
557
619
  txes << [op, @dbid, namespaced_attribute, value]
558
620
  end
559
621
  end
622
+ =end
560
623
 
561
624
  # Returns hash of all attributes for this object
562
625
  #