active-orient 0.2

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.
@@ -0,0 +1,147 @@
1
+ require 'active_model'
2
+ require 'active_support/concern'
3
+ require 'active_support/hash_with_indifferent_access'
4
+
5
+ module ActiveOrient
6
+
7
+ # Module adds prop Macro and
8
+ module BaseProperties
9
+ extend ActiveSupport::Concern
10
+
11
+ ### Instance methods
12
+
13
+ # Default presentation
14
+ def to_human
15
+ "<#{self.class.to_s.demodulize}: " + content_attributes.map do |attr, value|
16
+ "#{attr}: #{value}" unless value.nil?
17
+ end.compact.sort.join(' ') + ">"
18
+ end
19
+
20
+ # Comparison support
21
+ def content_attributes
22
+ HashWithIndifferentAccess[attributes.reject do |(attr, _)|
23
+ attr.to_s =~ /(_count)\z/ ||
24
+ [:created_at, :updated_at, :type,
25
+ :id, :order_id, :contract_id].include?(attr.to_sym)
26
+ end]
27
+ end
28
+
29
+ # Update nil attributes from given Hash or model
30
+ def update_missing attrs
31
+ attrs = attrs.content_attributes unless attrs.kind_of?(Hash)
32
+
33
+ attrs.each { |attr, val| send "#{attr}=", val if send(attr).blank? }
34
+ self # for chaining
35
+ end
36
+
37
+ # Default Model comparison
38
+ def == other
39
+ case other
40
+ when String # Probably a link or a rid
41
+ link == other || rid == other
42
+ else
43
+ content_attributes.keys.inject(true) { |res, key|
44
+ res && other.respond_to?(key) && (send(key) == other.send(key)) }
45
+ end
46
+ end
47
+
48
+ ### Default attributes support
49
+
50
+ def default_attributes
51
+ {:created_at => Time.now,
52
+ :updated_at => Time.now,
53
+ }
54
+ end
55
+
56
+ def set_attribute_defaults
57
+ default_attributes.each do |key, val|
58
+ self.send("#{key}=", val) if self.send(key).nil?
59
+ # self.send("#{key}=", val) if self[key].nil? # Problems with association defaults
60
+ end
61
+ end
62
+
63
+ included do
64
+
65
+ after_initialize :set_attribute_defaults
66
+
67
+ ### Class macros
68
+
69
+ def self.prop *properties
70
+ prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
71
+
72
+ properties.each { |names| define_property names, nil }
73
+ prop_hash.each { |names, type| define_property names, type }
74
+ end
75
+
76
+ def self.define_property names, body
77
+ aliases = [names].flatten
78
+ name = aliases.shift
79
+ instance_eval do
80
+
81
+ define_property_methods name, body
82
+
83
+ aliases.each do |ali|
84
+ alias_method "#{ali}", name
85
+ alias_method "#{ali}=", "#{name}="
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.define_property_methods name, body={}
91
+ #p name, body
92
+ case body
93
+ when '' # default getter and setter
94
+ define_property_methods name
95
+
96
+ when Array # [setter, getter, validators]
97
+ define_property_methods name,
98
+ :get => body[0],
99
+ :set => body[1],
100
+ :validate => body[2]
101
+
102
+ when Hash # recursion base case
103
+ getter = case # Define getter
104
+ when body[:get].respond_to?(:call)
105
+ body[:get]
106
+ when body[:get]
107
+ proc { self[name].send "to_#{body[:get]}" }
108
+ else
109
+ proc { self[name] }
110
+ end
111
+ define_method name, &getter if getter
112
+
113
+ setter = case # Define setter
114
+ when body[:set].respond_to?(:call)
115
+ body[:set]
116
+ when body[:set]
117
+ proc { |value| self[name] = value.send "to_#{body[:set]}" }
118
+ else
119
+ proc { |value| self[name] = value } # p name, value;
120
+ end
121
+ define_method "#{name}=", &setter if setter
122
+
123
+ # Define validator(s)
124
+ [body[:validate]].flatten.compact.each do |validator|
125
+ case validator
126
+ when Proc
127
+ validates_each name, &validator
128
+ when Hash
129
+ validates name, validator.dup
130
+ end
131
+ end
132
+
133
+ # TODO define self[:name] accessors for :virtual and :flag properties
134
+
135
+ else # setter given
136
+ define_property_methods name, :set => body, :get => body
137
+ end
138
+ end
139
+
140
+ # Timestamps in lightweight models
141
+ unless defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
142
+ prop :created_at, :updated_at
143
+ end
144
+
145
+ end # included
146
+ end # module BaseProperties
147
+ end
@@ -0,0 +1,441 @@
1
+ class String
2
+ def to_or
3
+ "'#{self}'"
4
+ end
5
+ end
6
+
7
+ class Numeric
8
+ def to_or
9
+ "#{self.to_s}"
10
+ end
11
+ end
12
+ module ActiveOrient
13
+
14
+ #require 'base'
15
+ #require 'base_properties'
16
+
17
+ class Model < ActiveOrient::Base
18
+ include BaseProperties
19
+
20
+ mattr_accessor :orientdb
21
+ mattr_accessor :logger
22
+ =begin
23
+ orientdb_class is used to instantiate a ActiveOrient:Model:{class} by providing its name
24
+ todo: implement object-inherence
25
+ =end
26
+ def self.orientdb_class name:
27
+ klass = Class.new( self )
28
+ name = name.to_s.camelize
29
+ if self.send :const_defined?, name
30
+ retrieved_class = self.send :const_get, name
31
+ else
32
+ new_class = self.send :const_set , name , klass
33
+ new_class.orientdb = orientdb
34
+ new_class # return_value
35
+ end
36
+ end
37
+
38
+ =begin
39
+ ActiveOrient::Model.autoload_object "#00:00"
40
+ either retrieves the object from the rid_store or loads it from the DB
41
+
42
+ the rid_store is updated!
43
+
44
+ to_do: fetch for version in the db and load the object if a change is detected
45
+ =end
46
+ def self.autoload_object link
47
+ # puts "autoload_object #{link}"
48
+ link_cluster_and_record = link[1,link.size].split(':').map &:to_i
49
+ @@rid_store[link_cluster_and_record].presence || orientdb.get_document( link )
50
+ end
51
+
52
+
53
+ def self.superClass
54
+ orientdb.get_classes( 'name', 'superClass').detect{|x| x["name"].downcase == new.class.to_s.downcase.split(':')[-1].to_s
55
+ }['superClass']
56
+ end
57
+
58
+ def to_orient
59
+ link
60
+ end
61
+ def from_orient
62
+ self
63
+ end
64
+ =begin
65
+ Returns just the name of the Class
66
+ =end
67
+ def classname
68
+ self.class.to_s.split(':')[-1]
69
+ end
70
+
71
+ =begin
72
+ If a Rest::Model-Object is included in a HashWidhtIndifferentAccess-Object, only the link is stored
73
+ =end
74
+ def nested_under_indifferent_access # :nodoc:
75
+ link
76
+ end
77
+
78
+ # hard-coded orientdb-columns
79
+ # prop :cluster, :version, :record, :fieldtypes
80
+
81
+ # def default_attributes
82
+ # super.merge cluster: 0
83
+ # super.merge version: 0
84
+ # super.merge record: 0
85
+ # end
86
+ def riid # :nodoc:
87
+ [ @metadata[ :cluster ] , @metadata[ :record ] ]
88
+ end
89
+ =begin
90
+ rid is used in the where-part of sql-queries
91
+ =end
92
+ def rid
93
+ if @metadata.has_key?( 'cluster')
94
+ "#{@metadata[ :cluster ]}:#{@metadata[ :record ]}"
95
+ else
96
+ "0:0"
97
+ end
98
+ end
99
+ =begin
100
+ link is used in any sql-commands
101
+ eg . update #link set ...
102
+ =end
103
+ def link
104
+ "##{rid}"
105
+ end
106
+
107
+
108
+ def method_missing method, *args, &b
109
+ property= orientdb.get_class_properties( classname )['properties'].detect{|x| x.has_value? method.to_s }
110
+ puts "method_missing::property"
111
+ puts property.inspect
112
+ if property.present?
113
+ if property['type'] == 'LINKSET'
114
+ attributes[method] = OrientSupport::Array.new( self )
115
+ else
116
+ attributes[method] = ''
117
+ end
118
+ else
119
+ raise NoMethodError
120
+ end
121
+ end
122
+ =begin
123
+ Queries the database and fetches the count of datasets
124
+
125
+ Any parameter that qualifies a database-query is suppoerted
126
+ (see method get_documents)
127
+ =end
128
+ def self.count **args
129
+ orientdb.count_documents from: self , **args
130
+ end
131
+ =begin
132
+ Creates a new Instance of the Class with the applied attributes
133
+ and returns the freshly instantiated Object
134
+ =end
135
+
136
+ def self.create properties = {}
137
+ orientdb.create_or_update_document self, set: properties
138
+ end
139
+
140
+ def self.update_or_create set: {}, where:{} ,**args, &b
141
+ orientdb.update_or_create_documents self , set: set, where: where , **args , &b
142
+
143
+ end
144
+
145
+ # historic method
146
+ def self.new_document attributes: {} # :nodoc:
147
+ orientdb.create_or_update_document self, set: attributes
148
+ end
149
+ =begin
150
+ Create a Property in the Schema of the Class
151
+ :call-seq:
152
+ self.create_property field (required) , type: 'string', linked_class: nil
153
+ =end
154
+
155
+ def self.create_property field, **keyword_arguments
156
+ orientdb.create_property self, field, **keyword_arguments
157
+ end
158
+
159
+ def self.create_link name, class_name
160
+ orientdb.create_property self, name, type: 'link', linked_class: class_name
161
+ end
162
+ def self.create_linkset name, class_name
163
+ orientdb.create_property self, name, type: 'linkset', linked_class: class_name
164
+ end
165
+ =begin
166
+ Only if the Class inherents from »E«
167
+ Instantiate a new Edge betwen two Vertices
168
+
169
+ Parameter: unique: (true) In case of an existing Edge just update its Properties.
170
+ :call-seq:
171
+ self.create_edge from: , to: , unique: false, attributes:{}
172
+ =end
173
+ def self.create_edge **keyword_arguments
174
+ o=orientdb.nexus_edge self, **keyword_arguments
175
+ [:from,:to].each{|y| keyword_arguments[y].reload! }
176
+ o # return_value
177
+ end
178
+
179
+ def query q
180
+ a= ActiveOrient::Query.new
181
+ a.queries << q
182
+ a.execute_queries
183
+ end
184
+ =begin
185
+ Parameter projection:
186
+
187
+ »select« is a method of enumeration, we use »projection:« to specify anything between »select« and »from«
188
+ in the query-string.
189
+
190
+ projection: a_string --> inserts the sting as it appears
191
+ an OrientSupport::OrientQuery-Object --> performs a sub-query and uses the result for further querying though the given parameters.
192
+
193
+ [ a, b, c ] --> "a , b , c " ( inserts a comma-separated list )
194
+
195
+ { a: b, "sum(x) "=> f } --> "a as b, sum(x) as f" (renames properties and uses functions )
196
+ Parameter distinct:
197
+
198
+ Performs a Query like » select distinct( property ) [ as property ] from ...«
199
+
200
+ distinct: :property --> the result is mapped to the property »distinct«.
201
+
202
+ [ :property ] --> the result replaces the property
203
+
204
+ { property: :some_name} --> the result is mapped to ModelInstance.some_name
205
+
206
+ Parameter Order
207
+
208
+ Sorts the result-set.
209
+
210
+ If new properties are introduced via select:, distinct: etc
211
+
212
+ Sorting takes place on these properties
213
+
214
+ order: :property
215
+ { property: asc, property: desc }
216
+ [property, property, .. ] ( orderdirection is 'asc' )
217
+
218
+
219
+ Further supported Parameter:
220
+ group_by
221
+ skip
222
+ limit
223
+ unwind
224
+
225
+ see orientdb- documentation (https://orientdb.com/docs/last/SQL-Query.html)
226
+
227
+ Parameter query:
228
+ Instead of providing the parameter, the OrientSupport::OrientQuery can build and
229
+ tested before the method-call. The OrientQuery-Object can be provided with the query-parameter
230
+ i.e.
231
+ q= OrientSupport::OrientQuery.new
232
+ TestModel = r.open_class 'test_model'
233
+ q.from TestModel
234
+ q.where { name: 'Thomas' }
235
+
236
+ count= TestModel.count query:q
237
+ q.limit 10
238
+ 0.step(count,10) do |x|
239
+ q.skip = x
240
+ puts TestModel.get_documents( query: q ).map{|x| x.adress }.join('\t')
241
+ end
242
+ prints a Table with 10 columns.
243
+ =end
244
+
245
+ def self.get_documents **args , &b
246
+ orientdb.get_documents from: classname, **args, &b
247
+
248
+ end
249
+ =begin
250
+ Performs a query on the Class and returns an Array of ActiveOrient:Model-Records.
251
+
252
+ Example:
253
+ Log = r.open_class 'Log'
254
+ Log.where priority: 'high'
255
+ --> submited database-request: query/hc_database/sql/select from Log where priority = 'high'/-1
256
+ => [ #<ActiveOrient::Model::Log:0x0000000480f7d8 @metadata={ ... }, ... ]
257
+
258
+
259
+ =end
260
+ def self.where attributes = {}
261
+ orientdb.get_documents from: self, where: attributes
262
+ end
263
+ =begin
264
+
265
+ removes the Model-Instance from the database
266
+
267
+ returns true (successfully deleted) or false ( obj not deleted)
268
+ =end
269
+ def delete
270
+
271
+ r= if is_edge?
272
+ # returns the count of deleted edges
273
+ orientdb.delete_edge rid
274
+ else
275
+ orientdb.delete_document rid
276
+ end
277
+ ActiveOrient::Base.remove_riid self if r # removes the obj from the rid_store
278
+ r # true or false
279
+ end
280
+ =begin
281
+ An Edge is defined
282
+ * when inherented from the superclass »E» (formal definition)
283
+ * if it has an in- and an out property
284
+
285
+ Actually we just check the second term as we trust the constuctor to work properly
286
+ =end
287
+ def is_edge?
288
+ attributes.keys.include?( 'in') && attributes.keys.include?('out')
289
+ end
290
+ # get enables loading of datasets if a link is followed
291
+ # model_class.all.first.link.get
292
+ def self.get rid
293
+ orientdb.get_document rid
294
+ end
295
+ def self.all
296
+ orientdb.get_documents from: self
297
+ end
298
+ def self.first where: {}
299
+ orientdb.get_documents( from: self, where: where, limit: 1).pop
300
+ end
301
+
302
+ def self.last where: {}
303
+ # debug:: orientdb.get_documents( self, order: { "@rid" => 'desc' }, limit: 1 ){ |x| puts x }.pop
304
+ orientdb.get_documents( from: self, where: where, order: { "@rid" => 'desc' }, limit: 1 ).pop
305
+ end
306
+ =begin
307
+ Convient update of the dataset by calling sql-patch
308
+ The attributes are saved to the database.
309
+ With the optional :set argument ad-hoc attributes can be defined
310
+ obj = ActiveOrient::Model::Contracts.first
311
+ obj.name = 'new_name'
312
+ obj.update set: { yesterdays_event: 35 }
313
+ =end
314
+ def update set: {}
315
+ attributes.merge!( set ) if set.present?
316
+ result= orientdb.patch_document(rid) do
317
+ attributes.merge( { '@version' => @metadata[ :version ], '@class' => @metadata[ :class ] } )
318
+ end
319
+ # returns a new instance of ActiveOrient::Model
320
+ reload! ActiveOrient::Model.orientdb_class(name: classname).new( JSON.parse( result ))
321
+ # instantiate object, update rid_store and reassign to self
322
+
323
+ end
324
+ =begin
325
+ Overwrite the attributes with Database-Contents (or attributes provided by the updated_dataset.model-instance)
326
+ =end
327
+ def reload! updated_dataset=nil
328
+ updated_dataset = orientdb.get_document( link) if updated_dataset.nil?
329
+ @metadata[:version]= updated_dataset.version
330
+ attributes = updated_dataset.attributes
331
+ self # return_value (otherwise only the attributes would be returned)
332
+ end
333
+
334
+ def remove_item_from_property array, item=nil
335
+ logger.progname = 'ActiveOrient::Model#RemoveItemFromProperty'
336
+ execute_array = Array.new
337
+ return unless attributes.has_key? array
338
+ remove_execute_array = -> (it) do
339
+ case it
340
+ when ActiveOrient::Model
341
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} remove #{array} = #{it.link}"}
342
+ when String
343
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} remove #{array} = '#{it}'"}
344
+ when Numeric
345
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} remove #{array} = #{it}"}
346
+ else
347
+ logger.error { "Only Basic Formats supported . Cannot Serialize #{it.class} this way" }
348
+ logger.error { "Try to load the array from the DB, modify it and update the hole record" }
349
+ end
350
+ end
351
+
352
+ if block_given?
353
+ items = yield
354
+ items.each{|x| remove_execute_array[x]; self.attributes[array].delete( x ) }
355
+ elsif item.present?
356
+ remove_execute_array[item]
357
+ a= attributes; a.delete item
358
+ self.attributes[array].delete( item )
359
+ end
360
+ orientdb.execute do
361
+ execute_array
362
+ end
363
+ reload!
364
+
365
+ rescue RestClient::InternalServerError => e
366
+ logger.error " Could not remove item in #{array} "
367
+ logger.error e.inspect
368
+ end
369
+
370
+ =begin
371
+ Convient method for populating embedded- or linkset-properties
372
+
373
+ In both cases an array/ a collection is stored in the database.
374
+
375
+ its called via
376
+ model.add_item_to_property( linkset- or embedded property, Object_to_be_linked_to )
377
+ or
378
+ mode.add_items_to_property( linkset- or embedded property ) do
379
+ Array_of_Objects_to_be_linked_to
380
+ (actually, the objects must inherent from ActiveOrient::Model, Numeric, String)
381
+ end
382
+
383
+ to_do: use "<<" to add the item to the property
384
+ =end
385
+ def add_item_to_property array, item=nil
386
+ logger.progname = 'ActiveOrient::Model#AddItem2Property'
387
+ execute_array = Array.new
388
+ self.attributes[array] = Array.new unless attributes[array].present?
389
+ add_2_execute_array = -> (it) do
390
+ case it
391
+ when ActiveOrient::Model
392
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} add #{array} = #{it.link}"}
393
+ when String
394
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} add #{array} = '#{it}'"}
395
+ when Numeric
396
+ execute_array << {type: "cmd", language: "sql", command: "update #{link} add #{array} = #{it}"}
397
+ else
398
+ logger.error { "Only Basic Formats supported . Cannot Serialize #{it.class} this way" }
399
+ logger.error { "Try to load the array from the DB, modify it and update the hole record" }
400
+ end
401
+ end
402
+
403
+ if block_given?
404
+ items = yield
405
+ items.each{|x| add_2_execute_array[x]; self.attributes[array] << x }
406
+ elsif item.present?
407
+ add_2_execute_array[item]
408
+ self.attributes[array] << item
409
+ end
410
+ orientdb.execute do
411
+ execute_array
412
+ end
413
+ reload!
414
+
415
+ rescue RestClient::InternalServerError => e
416
+ logger.error " Duplicate found in #{array} "
417
+ logger.error e.inspect
418
+ end
419
+
420
+ alias add_items_to_property add_item_to_property
421
+ ## historical aliases
422
+ alias update_linkset add_item_to_property
423
+ alias update_embedded add_item_to_property
424
+ =begin
425
+ Convient method for updating a linkset-property
426
+ its called via
427
+ model.update_linkset( linkset-property, Object_to_be_linked_to )
428
+ or
429
+ mode.update_linkset( linkset-property ) do
430
+ Array_of_Objects_to_be_linked_to
431
+ end
432
+ =end
433
+
434
+ #private
435
+ def version
436
+ @metadata[ :version ]
437
+ end
438
+
439
+ end # class
440
+
441
+ end # module