diametric 0.1.2-java → 0.1.3-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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  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-java.gemspec +11 -10
  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 +147 -75
@@ -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
  }
data/lib/diametric.rb CHANGED
@@ -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
  #