active-orient 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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