active-orient 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +8 -3
  4. data/Guardfile +12 -4
  5. data/README.md +221 -201
  6. data/VERSION +1 -1
  7. data/active-orient.gemspec +3 -2
  8. data/bin/active-orient-console +35 -0
  9. data/config/boot.rb +84 -16
  10. data/config/config.yml +10 -0
  11. data/config/connect.yml +6 -2
  12. data/create_project +19 -0
  13. data/examples/books.rb +86 -39
  14. data/examples/createTime.rb +91 -0
  15. data/examples/streets.rb +85 -84
  16. data/examples/test_commands.rb +92 -0
  17. data/examples/test_commands_2.rb +54 -0
  18. data/examples/test_commands_3.rb +48 -0
  19. data/examples/test_commands_4.rb +28 -0
  20. data/examples/time_graph/Gemfile +21 -0
  21. data/examples/time_graph/Guardfile +26 -0
  22. data/examples/time_graph/README.md +129 -0
  23. data/examples/time_graph/bin/active-orient-console +35 -0
  24. data/examples/time_graph/config/boot.rb +119 -0
  25. data/examples/time_graph/config/config.yml +8 -0
  26. data/examples/time_graph/config/connect.yml +17 -0
  27. data/examples/time_graph/config/init_db.rb +59 -0
  28. data/examples/time_graph/createTime.rb +51 -0
  29. data/examples/time_graph/lib/createTime.rb +82 -0
  30. data/examples/time_graph/model/day_of.rb +3 -0
  31. data/examples/time_graph/model/e.rb +6 -0
  32. data/examples/time_graph/model/edge.rb +53 -0
  33. data/examples/time_graph/model/monat.rb +19 -0
  34. data/examples/time_graph/model/stunde.rb +16 -0
  35. data/examples/time_graph/model/tag.rb +29 -0
  36. data/examples/time_graph/model/time_base.rb +6 -0
  37. data/examples/time_graph/model/time_of.rb +4 -0
  38. data/examples/time_graph/model/v.rb +3 -0
  39. data/examples/time_graph/model/vertex.rb +32 -0
  40. data/examples/time_graph/spec/lib/create_time_spec.rb +50 -0
  41. data/examples/time_graph/spec/rest_helper.rb +37 -0
  42. data/examples/time_graph/spec/spec_helper.rb +46 -0
  43. data/lib/active-orient.rb +56 -6
  44. data/lib/base.rb +149 -147
  45. data/lib/base_properties.rb +40 -41
  46. data/lib/class_utils.rb +301 -0
  47. data/lib/database_utils.rb +97 -0
  48. data/lib/init.rb +35 -0
  49. data/lib/java-api.rb +437 -0
  50. data/lib/jdbc.rb +211 -0
  51. data/lib/model/edge.rb +53 -0
  52. data/lib/model/model.rb +77 -0
  53. data/lib/model/the_class.rb +480 -0
  54. data/lib/model/the_record.rb +310 -0
  55. data/lib/model/vertex.rb +32 -0
  56. data/lib/orient.rb +113 -50
  57. data/lib/orientdb_private.rb +48 -0
  58. data/lib/other.rb +280 -0
  59. data/lib/query.rb +71 -73
  60. data/lib/rest/change.rb +124 -0
  61. data/lib/rest/create.rb +474 -0
  62. data/lib/rest/delete.rb +133 -0
  63. data/lib/rest/operations.rb +150 -0
  64. data/lib/rest/read.rb +150 -0
  65. data/lib/rest/rest.rb +111 -0
  66. data/lib/rest_disabled.rb +24 -0
  67. data/lib/support.rb +387 -296
  68. data/old_lib_functions/two_general_class.rb +139 -0
  69. data/usecase.md +49 -36
  70. data/usecase_oo.md +59 -0
  71. metadata +73 -9
  72. data/lib/model.rb +0 -461
  73. data/lib/rest.rb +0 -1036
  74. data/test.rb +0 -4
@@ -1,1036 +0,0 @@
1
- module ActiveOrient
2
- require 'cgi'
3
- require 'rest-client'
4
- require 'active_support/core_ext/string' # provides blank?, present?, presence etc
5
- #require 'model'
6
- =begin
7
- OrientDB performs queries to a OrientDB-Database
8
-
9
- The communication is based on the ActiveOrient-API.
10
-
11
- The OrientDB-Server is specified in config/connect.yml
12
-
13
- A Sample:
14
- :orientdb:
15
- :server: localhost
16
- :port: 2480
17
- :database: working-database
18
- :admin:
19
- :user: admin-user
20
- :pass: admin-password
21
-
22
-
23
- =end
24
-
25
- class OrientDB
26
- mattr_accessor :logger ## borrowed from active_support
27
- mattr_accessor :default_server ##
28
- ## expected
29
- # ActiveOrient::OrientDB.default_server = { server: 'localhost', port: 2480, user: '**', password: '**', database: 'temp'
30
- include OrientSupport::Support
31
-
32
- =begin
33
- Contructor: OrientDB is conventionally initialized.
34
- Thus several instances pointing to the same or different databases can coexist
35
-
36
- A simple
37
- xyz = ActiveOrient::OrientDB.new
38
- uses the database specified in the yaml-file »config/connect.yml« and connects
39
- xyz = ActiveOrient::OrientDB.new database: my_fency_database
40
- accesses the database »my_fency_database«. The database is created if its not existing.
41
-
42
- *USECASE*
43
- xyz = ActiveOrient::Model.orientdb = ActiveOrient::OrientDB.new
44
- initialises the Database-Connection and publishes the Instance to any ActiveOrient::Model-Object
45
- =end
46
-
47
-
48
- def initialize database: nil, connect: true
49
- self.default_server = { :server => 'localhost', :port => 2480, :protocol => 'http',
50
- :user => 'root', :password => 'root', :database => 'temp' }.merge default_server
51
- @res = get_ressource
52
- @database = database.presence || default_server[:database]
53
- self.logger = Logger.new('/dev/stdout') unless logger.present?
54
- # @database=@database#.camelcase
55
- connect() if connect
56
- # save existing classes
57
- @classes = []
58
- ActiveOrient::Model.orientdb = self
59
- end
60
-
61
- ## included for development , should be removed in production release
62
- def ressource
63
- @res
64
- end
65
- ##
66
-
67
- # called in the beginning or after a 404-Error
68
- def get_ressource
69
- login = [ default_server[:user].to_s , default_server[:password].to_s ]
70
- server_adress = [ default_server[:protocol] ,"://" , default_server[ :server ], ":" , default_server[ :port ]].map(&:to_s).join('')
71
- RestClient::Resource.new( server_adress, *login )
72
- end
73
-
74
- def connect
75
- i = 0
76
- begin
77
- logger.progname = 'OrientDB#Connect'
78
- r= @res["/connect/#{ @database }" ].get
79
- if r.code == 204
80
- logger.info{ "Connected to database #{@database} " }
81
- true
82
- else
83
- logger.error{ "Connection to database #{@database} could NOT be established" }
84
- nil
85
- end
86
- rescue RestClient::Unauthorized => e
87
- if i.zero?
88
- logger.info{ "Database #{@database} NOT present --> creating" }
89
- i=i+1
90
- create_database
91
- retry
92
- else
93
- Kernel.exit
94
- end
95
- end
96
-
97
- end
98
-
99
- ## -----------------------------------------------------------------------------------------
100
- ##
101
- ## Database stuff
102
- ##
103
- ## get_databases
104
- ## create_database
105
- ## change_database
106
- ## delete_database
107
- ## -----------------------------------------------------------------------------------------
108
-
109
- =begin
110
- returns an Array with available Database-Names as Elements
111
- =end
112
- def get_databases
113
- JSON.parse( @res["/listDatabases"].get.body)['databases']
114
- end
115
-
116
- =begin
117
- Creates a database with the given name and switches to this database as working-database
118
- Types are either 'plocal' or 'memory'
119
-
120
- returns the name of the working-database
121
- =end
122
- def create_database type: 'plocal' , database: @database
123
- logger.progname = 'OrientDB#CreateDatabase'
124
- old_d = @database
125
- @classes = []
126
- @database = database
127
- begin
128
- response = @res[database_uri{ type }].post ""
129
- if response.code == 200
130
- logger.info{ "Database #{@database} successfully created and stored as working database"}
131
- else
132
- @database = old_d
133
- logger.error{ "Database #{name} was NOT created. Working Database is still #{@database} "}
134
- end
135
- rescue RestClient::InternalServerError => e
136
- @database = old_d
137
- logger.error{ "Database #{name} was NOT created. Working Database is still #{@database} "}
138
- end
139
- @database
140
-
141
- end
142
- =begin
143
- changes the working-database to {name}
144
- =end
145
- def change_database name
146
- @classes = []
147
- @database = name
148
-
149
- end
150
- =begin
151
- deletes the database and returns true on success
152
-
153
- after the removal of the database, the working-database might be empty
154
- =end
155
- def delete_database database:
156
- @classes = []
157
- logger.progname = 'OrientDB#DropDatabase'
158
- old_ds = @database
159
- change_database database
160
- begin
161
- response = @res[database_uri].delete
162
- if database == old_ds
163
- change_database ""
164
- logger.info{ "Working database deleted" }
165
- else
166
- change_database old_ds
167
- logger.info{ "Database #{database} deleted, working database is still #{@database} "}
168
- end
169
- rescue RestClient::InternalServerError => e
170
- logger.info{ "Database #{database} NOT deleted" }
171
- change_database old_ds
172
- end
173
- !response.nil? && response.code == 204 ? true : false
174
-
175
- end
176
-
177
- ## -----------------------------------------------------------------------------------------
178
- ##
179
- ## Inspect, Create and Delete Classes
180
- ##
181
- ## inspect_classes
182
- ## create_class
183
- ## delete_class
184
- ##
185
- ## -----------------------------------------------------------------------------------------
186
-
187
- =begin
188
- returns an Array with (unmodified) Class-attribute-hash-Elements
189
-
190
- get_classes 'name', 'superClass'
191
- returns
192
- [ {"name"=>"E", "superClass"=>""},
193
- {"name"=>"OFunction", "superClass"=>""},
194
- {"name"=>"ORole", "superClass"=>"OIdentity"}
195
- (...)
196
- ]
197
- =end
198
-
199
- def get_classes *attributes
200
- i = 0
201
- begin
202
- response = @res[database_uri].get
203
- if response.code == 200
204
- classes= JSON.parse( response.body )['classes' ]
205
- unless attributes.empty?
206
- classes.map{|y| y.select{| v,_| attributes.include?(v) } }
207
- else
208
- classes
209
- end
210
-
211
- #.map{ |y| y.name }
212
- else
213
- []
214
- end
215
- rescue JSON::ParserError
216
- if i.zero?
217
- i = i + 1
218
- retry
219
- else
220
- raise
221
- end
222
- end
223
-
224
- end
225
- =begin
226
- returns the class_hierachie
227
-
228
- to fetch all Vertices
229
- class_hiearchie( base_class: 'V').flatten
230
- to fetch all Edges
231
- class_hierachie( base_class: 'E').flatten
232
-
233
- Notice: base_class has to be noted as String! There is no implicit conversion from Symbol or Class
234
- =end
235
-
236
- def class_hierachie base_class: '', requery: false
237
- @all_classes = get_classes( 'name', 'superClass') if requery || @all_classes.blank?
238
- def fv s # :nodoc:
239
- @all_classes.find_all{ |x| x[ 'superClass' ]== s }.map{| v| v[ 'name' ]} #.camelize }
240
- end
241
-
242
- def fx v # :nodoc:
243
- fv(v).map{ |x| ar = fx( x ) ; ar.empty? ? x : [ x , ar ] }
244
- end
245
-
246
- fx base_class
247
- end
248
- =begin
249
- returns an array with all names of the classes of the database
250
- caches the result.
251
-
252
- parameter: include_system_classes: false|true, requery: false|true
253
- =end
254
- def database_classes include_system_classes: false, requery: false
255
- requery = true if @classes.empty?
256
- if requery
257
- class_hierachie requery: true
258
- system_classes = ["OFunction", "OIdentity", "ORIDs", "ORestricted", "ORole", "OSchedule", "OTriggered", "OUser", "_studio"]
259
- all_classes = get_classes( 'name' ).map( &:values).flatten
260
- @classes = include_system_classes ? all_classes : all_classes - system_classes
261
- end
262
- @classes
263
- end
264
-
265
- alias inspect_classes database_classes
266
-
267
- =begin
268
- Creates classes and class-hierachies in OrientDB and in Ruby.
269
-
270
-
271
- Takes an Array or a Hash as argument and returns an Array of
272
- successfull allocated Ruby-Classes
273
-
274
- If the argument is an array, Basic-Classes are build.
275
-
276
- Otherwise key/value pairs are assumend to follow this terminology
277
- { SuperClass => [ class, class, ...], SuperClass => [] , ... }
278
- =end
279
-
280
- def create_classes classes
281
- # rebuild cashed classes-array
282
- # first the classes-string is camelized (this is used to allocate the ruby-class)
283
- # Then the database is queried for this string or the underscored string-variant
284
- # the name-building process is independend from the method »class_name«
285
- database_classes requery: true
286
- consts = Array.new
287
- execute transaction: false do
288
- class_cmd = ->(s,n) do
289
- n = n.to_s.camelize
290
- consts << ActiveOrient::Model.orientdb_class( name: n)
291
- unless database_classes.include?(n) || database_classes.include?(n.underscore)
292
- { type: "cmd", language: 'sql', command: "create class #{n} extends #{s}" }
293
-
294
- end
295
- end ## class_cmd
296
-
297
- if classes.is_a?(Array)
298
- classes.map do | n |
299
- n = n.to_s.camelize # capitalize
300
- consts << ActiveOrient::Model.orientdb_class( name: n)
301
- unless database_classes.include?( n ) || database_classes.include?(n.underscore)
302
- { type: "cmd", language: 'sql', command: "create class #{n} " }
303
- end
304
- end
305
- elsif classes.is_a?(Hash)
306
- classes.keys.map do | superclass |
307
- items = Array.new
308
- superClass = superclass.to_s.camelize
309
- items << { type: "cmd", language: 'sql', command: "create class #{superClass} abstract" } unless database_classes.flatten.include?( superClass ) || database_classes.flatten.include?( superClass.underscore )
310
- items << if classes[superclass].is_a?( String ) || classes[superclass].is_a?( Symbol )
311
- class_cmd[superClass, classes[superclass] ]
312
- elsif classes[superclass].is_a?( Array )
313
- classes[superclass].map{|n| class_cmd[superClass, n] }
314
- end
315
- #puts items.flatten.map{|x| x[:command]}
316
- items # returnvalue
317
- end.flatten
318
- end.compact # erase nil-entries, in case the class is already allocated
319
- end
320
- # refresh cached class-informations
321
- database_classes requery: true
322
- # returns an array of allocated Constants/Classes
323
- consts
324
- end
325
-
326
- =begin
327
- creates a class and returns the a ActiveOrient::Model:{Newclass}-Class- (Constant)
328
- which is designed to take any documents stored in this class
329
-
330
- Predefined attributes: version, cluster, record
331
-
332
- Other attributes are assigned dynamically upon reading documents
333
-
334
- The classname is Camelized by default, eg: classnames are always Capitalized, underscores ('_') indicate
335
- that the following letter is capitalized, too.
336
-
337
- Other class-names can be used if a "$" is placed at the begining of the name-string. However, most of the
338
- model-based methods will not work in this case.
339
- =end
340
- def create_class newclass
341
- create_classes( [ newclass ] ).first
342
- end
343
-
344
- alias open_class create_class
345
-
346
- def create_vertex_class name , superclass: 'V'
347
- create_classes( { superclass => name } ).first
348
- end
349
-
350
- def create_edge_class name , superclass: 'E'
351
- create_classes( { superclass => name } ).first
352
- end
353
-
354
- =begin
355
- nexus_edge connects two documents/vertexes
356
- The parameter o_class can be either a class or a string
357
- =end
358
-
359
- def nexus_edge o_class , attributes: {}, from:, to:, unique: false
360
- logger.progname = "ActiveOrient::OrientDB#NexusEdge"
361
- if from.is_a? Array
362
- from.map{|f| nexus_edge o_class, attributes: attributes, from: f, to: to, unique: unique }
363
- elsif to.is_a? Array
364
- to.map{|t| nexus_edge o_class, attributes: attributes, from: from, to: t, unique: unique }
365
- else
366
-
367
- if unique
368
- wwhere = { out: from.to_orient, in: to.to_orient }.merge(attributes.to_orient)
369
- existing_edge = get_documents( from: o_class, where: wwhere )
370
- if existing_edge.first.is_a?( ActiveOrient::Model )
371
- logger.debug { "reusing edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
372
- return existing_edge.first
373
- end
374
- end
375
- logger.debug { "creating edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
376
- response= execute( o_class, transaction: false) do
377
- #[ { type: "cmd", language: 'sql', command: CGI.escapeHTML("create edge #{class_name(o_class)} from #{translate_to_rid[m]} to #{to.to_orient}; ")} ]
378
- attr_string = attributes.blank? ? "" : "set #{ generate_sql_list attributes.to_orient }"
379
- [ { type: "cmd", language: 'sql',
380
- command: "create edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} #{attr_string}"} ]
381
- end
382
- if response.is_a?(Array) && response.size == 1
383
- response.pop # RETURN_VALUE
384
- else
385
- response
386
- end
387
- end
388
- end
389
-
390
- =begin
391
- Deletes a single edge when providing a single rid-link (#00:00)
392
- Deletes multible edges when providing a list of rid-links
393
- Todo: implement delete_edges after querying the database in one statement
394
-
395
- =end
396
- def delete_edge *rid
397
- rid = rid.map do |mm|
398
- if mm.is_a?(String)
399
- if mm.rid?
400
- mm
401
- elsif mm.is_a?(Rest::Model)
402
- mm.rid
403
- else
404
- nil
405
- end
406
- end
407
- end.compact
408
- response= execute transaction: false do
409
- [ { type: "cmd", language: 'sql', command: CGI.escapeHTML("delete edge #{rid.join(',') }")} ]
410
- end
411
- if response.is_a?( Array ) && response.size == 1
412
- response.pop # RETURN_VALUE
413
- else
414
- response
415
- end
416
- end
417
- =begin
418
- deletes the specified class and returns true on success
419
-
420
-
421
- todo: remove all instances of the class
422
- =end
423
- def delete_class o_class
424
- cl= class_name(o_class)
425
- logger.progname = 'OrientDB#DeleteClass'
426
-
427
- if database_classes.include? cl
428
- begin
429
- response = @res[class_uri{ cl } ].delete
430
- if response.code == 204
431
- # return_value: sussess of the removal
432
- !database_classes( requery: true ).include?(cl)
433
- # don't delete the ruby-class
434
- # ActiveOrient::Model.send :remove_const, cl.to_sym if o_class.is_a?(Class)
435
- end
436
- rescue RestClient::InternalServerError => e
437
- if database_classes( requery: true).include?( cl )
438
- logger.error{ "Class #{cl} still present" }
439
- logger.error{ e.inspect }
440
- false
441
- else
442
- true
443
- end
444
- end
445
- else
446
- logger.info { "Class #{cl} not present. "}
447
- end
448
-
449
- end
450
- =begin
451
- create a single property on class-level.
452
-
453
- supported types: https://orientdb.com/docs/last/SQL-Create-Property.html
454
-
455
- if index is to be specified, it's defined in the optional block
456
- create_property(class, field){ :unique | :notunique } --> creates an automatic-Index on the given field
457
- create_property( class, field){ { »name« => :unique | :notunique | :full_text } } --> creates a manual index
458
-
459
- =end
460
- def create_property o_class, field, index: nil, **args
461
- logger.progname= 'OrientDB#CreateProperty'
462
- c= create_properties o_class, {field => args} # translate_property_hash( field, args )
463
- if index.nil? && block_given?
464
- index = yield
465
- end
466
- if c==1 && index.present?
467
- if index.is_a?( String ) || index.is_a?( Symbol )
468
- create_index o_class, name: field, type: index
469
- elsif index.is_a? Hash
470
- bez= index.keys.first
471
- # puts "index: #{index.inspect}"
472
- # puts "bez: #{bez} ---> #{index.keys.inspect}"
473
- create_index o_class, name: bez, type: index[bez], on: [ field ]
474
- end
475
- end
476
-
477
- end
478
-
479
-
480
- def create_index o_class , name:, on: :automatic, type: :unique
481
-
482
- execute transaction: false do
483
- c = class_name o_class
484
- command = if on == :automatic
485
- "create index #{c}.#{name} #{type.to_s.upcase}"
486
- elsif on.is_a? Array
487
- "create index #{name} on #{class_name(o_class)}( #{on.join(', ')}) #{type.to_s.upcase}"
488
- else
489
- nil
490
- end
491
- [ { type: "cmd", language: 'sql', command: command } ]
492
- end
493
-
494
- end
495
- ## -----------------------------------------------------------------------------------------
496
- ##
497
- ## Properties
498
- ##
499
- ## create_properties
500
- ## get_class_properties
501
- ## delete_properties
502
-
503
- ##
504
- ## -----------------------------------------------------------------------------------------
505
- private
506
- def translate_property_hash field, type: nil, linked_class: nil, **args
507
- type = type.presence || args[:propertyType].presence || args[:property_type]
508
- linked_class = linked_class.presence || args[:linkedClass]
509
- if type.present?
510
- if linked_class.nil?
511
- { field => { propertyType: type.to_s.upcase } }
512
- else
513
- { field => { propertyType: type.to_s.upcase, linkedClass: class_name( linked_class ) } }
514
- end
515
- end
516
-
517
- end
518
- public
519
- =begin
520
-
521
- creates properties and optional an associated index as defined in the provided block
522
-
523
- create_properties( classname or class, properties as hash ) { index }
524
-
525
- The default-case
526
-
527
- create_properties( con_id: { type: :integer },
528
- details: { type: :link, linked_class: 'Contracts' } ) do
529
- contract_idx: :notunique
530
- end
531
-
532
- A composite index
533
-
534
- create_properties( con_id: { type: :integer },
535
- symbol: { type: :string } ) do
536
- { name: 'indexname',
537
- on: [ :con_id , :details ] # default: all specified properties
538
- type: :notunique # default: :unique
539
- }
540
- end
541
-
542
- =end
543
- def create_properties o_class, all_properties, &b
544
-
545
- all_properties_in_a_hash = HashWithIndifferentAccess.new
546
- all_properties.each{| field, args | all_properties_in_a_hash.merge! translate_property_hash( field, args ) }
547
-
548
- begin
549
- count = if all_properties_in_a_hash.is_a?( Hash )
550
- response = @res[ property_uri(class_name(o_class)) ].post all_properties_in_a_hash.to_json
551
- if response.code == 201
552
- response.body.to_i
553
- else
554
- 0
555
- end
556
- end
557
- rescue RestClient::InternalServerError => e
558
- response = JSON.parse( e.response)['errors'].pop
559
- error_message = response['content'].split(':').last
560
- # if error_message ~= /Missing linked class/
561
- logger.progname= 'OrientDB#CreatePropertes'
562
- logger.error { "Properties in #{class_name(o_class)} were NOT created" }
563
- logger.error { "Error-code #{response['code']} --> #{response['content'].split(':').last }" }
564
- nil
565
- end
566
- ### index
567
- if block_given? && count == all_properties_in_a_hash.size
568
- index = yield
569
- if index.is_a?( Hash )
570
- if index.size == 1
571
- create_index o_class, name: index.keys.first, on: all_properties_in_a_hash.keys, type: index.values.first
572
- else
573
- index_hash = HashWithIndifferentAccess.new( type: :unique, on: all_properties_in_a_hash.keys ).merge index
574
- create_index o_class, index_hash # i [:name], on: index_hash[:on], type: index_hash[:type]
575
- end
576
- end
577
- end
578
- count # return_value
579
- end
580
-
581
- def delete_property o_class, field
582
- logger.progname = 'OrientDB#DeleteProperty'
583
- begin
584
- response = @res[property_uri( class_name(o_class)){ field } ].delete
585
- true if response.code == 204
586
- rescue RestClient::InternalServerError => e
587
- logger.error{ "Property #{ field } in class #{ class_name(o_class) } NOT deleted" }
588
- false
589
- end
590
-
591
- end
592
-
593
- def get_class_properties o_class # :nodoc:
594
- JSON.parse( @res[ class_uri{ class_name(o_class) } ].get )
595
- end
596
-
597
- def print_class_properties o_class
598
- puts "Detected Properties for class #{class_name(o_class)}"
599
-
600
- rp = get_class_properties o_class
601
- n= rp['name']
602
- puts rp['properties'].map{|x| [ n+'.'+x['name'], x['type'],x['linkedClass'] ].compact.join(' -> ' )}.join("\n")
603
-
604
- end
605
- #
606
- ## -----------------------------------------------------------------------------------------
607
- ##
608
- ## Documents
609
- ##
610
- ## create_document get_document delete_document
611
- ## update_or_create_document patch_document
612
- ##
613
- ##
614
- ## get_documents
615
- ## update_documents
616
- ## delete_documents
617
- ## update_or_create_documents
618
- ## -----------------------------------------------------------------------------------------
619
-
620
- =begin #nodoc#
621
- If properties are allocated on class-level, they can be preinitialized using
622
- this method.
623
- This is disabled for now, because it does not seem nessesary
624
-
625
- =end
626
- def preallocate_class_properties o_class
627
- p= get_class_properties( o_class )['properties']
628
- unless p.nil? || p.blank?
629
- predefined_attributes = p.map do | property |
630
- [ property['name'] ,
631
- case property['type']
632
- when 'LINKMAP'
633
- Array.new
634
- when 'STRING'
635
- ''
636
- else
637
- nil
638
- end ]
639
- end.to_h
640
- else
641
- {}
642
- end
643
- end
644
-
645
- =begin
646
- Creates an Object in the Database and returns this as ActuveOrient::Model-Instance
647
- =end
648
-
649
- def create_document o_class, attributes: {}
650
- attributes = yield if attributes.empty? && block_given?
651
- post_argument = { '@class' => class_name(o_class) }.merge(attributes).to_orient
652
-
653
- begin
654
- logger.progname = 'OrientDB#CreateDocument'
655
- response = @res[ document_uri ].post post_argument.to_json
656
- data= JSON.parse( response.body )
657
- ActiveOrient::Model.orientdb_class( name: data['@class']).new data
658
- rescue RestClient::InternalServerError => e
659
- response = JSON.parse( e.response)['errors'].pop
660
- logger.error { response['content'].split(':')[1..-1].join(':') }
661
- logger.error { "No Object allocated" }
662
- nil # return_value
663
- end
664
- end
665
-
666
-
667
- def delete_document record_id
668
- logger.progname = 'OrientDB#DeleteDocument'
669
- begin
670
- response = @res[document_uri{ record_id } ].delete
671
- true if response.code == 204
672
- rescue RestClient::InternalServerError => e
673
- logger.error{ "Document #{ record_id } NOT deleted" }
674
- false
675
- end
676
-
677
- end
678
- =begin
679
- retrieves documents from a query
680
-
681
- If raw is specified, the JSON-Array is returned, eg
682
- { "@type"=>"d",
683
- "@rid"=>"#15:1",
684
- "@version"=>1,
685
- "@class"=>"DocumebntKlasse10",
686
- "con_id"=>343,
687
- "symbol"=>"EWTZ"
688
- }
689
- otherwise a ActiveModel-Instance of o_class is created and returned
690
- =end
691
- def get_documents limit: -1, raw: false, query: nil, **args
692
- query = OrientSupport::OrientQuery.new( args ) if query.nil?
693
- i=0
694
- begin
695
- url = query_sql_uri << query.compose << "/#{limit}"
696
- response = @res[URI.encode(url) ].get
697
- r=JSON.parse( response.body )['result'].map do |document |
698
- if raw
699
- document
700
- else
701
- ActiveOrient::Model.orientdb_class( name: document['@class']).new document
702
- end
703
- end
704
- rescue RestClient::InternalServerError => e
705
- response = JSON.parse( e.response)['errors'].pop
706
- logger.error { response['content'].split(':').last }
707
- i=i+1
708
- if i > 1
709
- raise
710
- else
711
- query.dataset_name = query.datatset_name.underscore
712
- logger.info { "trying to query using #{o_class}" }
713
- retry
714
- end
715
- end
716
-
717
- end
718
-
719
- def count_documents **args
720
- logger.progname = 'OrientDB#count_documents'
721
-
722
- query = OrientSupport::OrientQuery.new args
723
- query.projection << 'COUNT (*)'
724
- result = get_documents raw: true, query: query
725
-
726
- result.first['COUNT']
727
-
728
- end
729
-
730
- =begin
731
- Inserts a Document with the attributes provided in the attributes-hash
732
- eg
733
- create_document class_name: @classname, attributes: { con_id: 343, symbol: 'EWTZ' }
734
-
735
- untested: for hybrid and schema-less documents the following syntax is supported
736
- create_document class_name: "Account",
737
- attributes: { date: 1350426789, amount: 100.34,
738
- "@fieldTypes" => "date=t,amount=c" }
739
-
740
- The supported special types are:
741
- n
742
- 'f' for float
743
- 'c' for decimal
744
- 'l' for long
745
- 'd' for double
746
- 'b' for byte and binary
747
- 'a' for date
748
- 't' for datetime
749
- 's' for short
750
- 'e' for Set, because arrays and List are serialized as arrays like [3,4,5]
751
- 'x' for links
752
- 'n' for linksets
753
- 'z' for linklist
754
- 'm' for linkmap
755
- 'g' for linkbag
756
-
757
- =end
758
- =begin
759
- Creating a new Database-Entry ( where is omitted )
760
-
761
- otherwise updating the Database-Entry (if present)
762
-
763
- The optional Block should provide a hash with attributes(properties). These are used if
764
- a new dataset is created.
765
- =end
766
- def create_or_update_document o_class , **args, &b
767
- logger.progname = 'Rest#CreateOrUpdateDocument'
768
- r= update_or_create_documents o_class, **args, &b
769
- if r.size > 1
770
- logger.error { "multible documents updated by #{ generate_sql_list( where )}" }
771
- end
772
- r.first # return_value
773
- end
774
- =begin
775
- Based on the query specified in :where records are updated according to :set
776
-
777
- Returns an Array of updated documents
778
-
779
- The optional Block should provide a hash with attributes(properties). These are used if
780
- a new dataset is created.
781
-
782
- ### das ist noch nicht rund.
783
- #
784
- =end
785
- def update_or_create_documents o_class , set: {}, where: {} , **args , &b
786
- logger.progname = 'Rest#UpdateOrCreateDocuments'
787
- if where.blank?
788
- [ create_document( o_class, attributes: set ) ]
789
- else
790
- set.extract!( where.keys ) # removes any keys from where in set
791
- possible_documents = get_documents from: class_name( o_class ), where: where, **args
792
- if possible_documents.empty?
793
- if block_given?
794
- more_where = yield # do Preparations prior to the creation of the dataset
795
- # if the block returns a Hash , it is merged into the insert_query.
796
- where.merge! more_where if more_where.is_a?(Hash)
797
- end
798
- [ create_document( o_class, attributes: set.merge(where) ) ]
799
- else
800
- possible_documents.map{| doc | doc.update( set: set ) }
801
- end
802
- end
803
- end
804
- =begin
805
- Deletes documents.
806
- They are defined by a query. All records which match the attributes are deleted.
807
- An Array with freed index-values is returned
808
- =end
809
- def delete_documents o_class, where: {}
810
- get_documents( from: o_class, where: where).map do |doc|
811
- if doc['@type']=='d' # document
812
- index = doc['@rid'][1,doc['@rid'].size] # omit the first character ('#')
813
- r=@res[ document_uri{ index }].delete
814
- index if r.code==204 && r.body.empty? # return_value
815
- end
816
-
817
- end
818
-
819
- end
820
- =begin
821
- Retrieves a Document from the Database as ActiveOrient::Model::{class}
822
- The argument can either be a rid (#[x}:{y}) or a link({x}:{y})
823
- If no Document is found, nil is returned
824
-
825
- In the optional block, a subset of properties can be defined (as array of names)
826
- =end
827
- def get_document rid
828
-
829
- rid = rid[1 .. rid.length] if rid[0]=='#'
830
-
831
- response = @res[ document_uri { rid } ].get
832
-
833
- raw_data = JSON.parse( response.body) #.merge( "#no_links" => "#no_links" )
834
- ActiveOrient::Model.orientdb_class( name: raw_data['@class']).new raw_data
835
-
836
- rescue RestClient::InternalServerError => e
837
- if e.http_body.split(':').last =~ /was not found|does not exist in database/
838
- nil
839
- else
840
- logger.progname 'OrientDB#GetDocument'
841
- logger.error "something went wrong"
842
- logger.error e.http_body.inspect
843
- raise
844
- end
845
- end
846
- =begin
847
- Lazy Updating of the given Document.
848
- =end
849
- def patch_document rid
850
- logger.progname = 'Rest#PatchDocument'
851
- content = yield
852
- if content.is_a? Hash
853
- content.each do | key, value |
854
- # puts "content: #{key}, #{value.class}"
855
- # content[key]= value.to_orient #if value.is_a? ActiveOrient::Model
856
- end
857
- @res[ document_uri { rid } ].patch content.to_orient.to_json
858
- else
859
- logger.error { "FAILED: The Block must provide an Hash with properties to be updated"}
860
- end
861
- end
862
- =begin
863
- update_documents classname,
864
- set: { :symbol => 'TWR' },
865
- where: { con_id: 340 }
866
-
867
- replaces the symbol to TWR in each record where the con_id is 340
868
- Both set and where take multible attributes
869
- returns the JSON-Response.
870
-
871
- =end
872
-
873
-
874
- def update_documents o_class, set: , where: {}
875
- url = "update #{class_name(o_class)} set "<< generate_sql_list(set) << compose_where(where)
876
- response = @res[ URI.encode( command_sql_uri << url) ].post '' #url.to_json
877
- end
878
-
879
- ## -----------------------------------------------------------------------------------------
880
- ##
881
- ## Functions and Batch
882
- ##
883
- ##
884
- ## -----------------------------------------------------------------------------------------
885
-
886
- =begin
887
- Execute a predefined Function
888
- =end
889
- def call_function *args
890
- #puts "uri:#{function_uri { args.join('/') } }"
891
- @res[ function_uri { args.join('/') } ].post ''
892
- rescue RestClient::InternalServerError => e
893
- puts JSON.parse(e.http_body)
894
- end
895
-
896
-
897
- =begin
898
- Executes a list of commands and returns the result-array (if present)
899
-
900
- structure of the provided block:
901
- [{ type: "cmd",
902
- language: "sql",
903
- command: "create class Person extends V"
904
- },
905
- (...)
906
- ]
907
-
908
- It's used by ActiveOrient::Query.execute_queries
909
-
910
- =end
911
- def execute classname = 'Myquery', transaction: true
912
- batch = { transaction: transaction, operations: yield }
913
- unless batch[:operations].blank?
914
- # puts "post: #{batch.to_json}"
915
- response = @res[ batch_uri ].post batch.to_json
916
- # puts "response: #{JSON.parse(response.body)['result'].inspect}"
917
- if response.code == 200
918
- if response.body['result'].present?
919
- result= JSON.parse(response.body)['result']
920
- result.map do |x|
921
- if x.is_a? Hash
922
- if x.has_key?('@class')
923
- ActiveOrient::Model.orientdb_class( name: x['@class']).new x
924
- elsif x.has_key?( 'value' )
925
- x['value']
926
- else
927
- # puts "ActiveOrient::Execute"
928
- # puts "o_class: #{o_class.inspect}"
929
- ActiveOrient::Model.orientdb_class( name: classname).new x
930
- end
931
- end
932
- end.compact # return_value
933
-
934
- else
935
- response.body
936
- end
937
- else
938
- nil
939
- end
940
- end
941
- rescue RestClient::InternalServerError => e
942
- raise
943
-
944
- end
945
- =begin
946
- Converts a given name to the camelized database-classname
947
-
948
- Converts a given class-constant to the corresponding database-classname
949
-
950
- returns a valid database-class name, nil if the class not exists
951
- =end
952
- def class_name name_or_class
953
- name= if name_or_class.is_a? Class
954
- name_or_class.to_s.split('::').last
955
- elsif name_or_class.is_a? ActiveOrient::Model
956
- name_or_class.classname
957
- else
958
- name_or_class.to_s.camelize
959
- end
960
- if database_classes.include?(name)
961
- name
962
- elsif database_classes.include?(name.underscore)
963
- name.underscore
964
- else
965
- logger.progname = 'OrientDB#ClassName'
966
- logger.info{ "Classname #{name} not present in active Database" }
967
- nil
968
- end
969
- end
970
- # def compose_where arg
971
- # if arg.blank?
972
- # ""
973
- # elsif arg.is_a? String
974
- # if arg =~ /[w|W]here/
975
- # arg
976
- # else
977
- # "where "+arg
978
- # end
979
- # elsif arg.is_a? Hash
980
- # " where " + generate_sql_list(arg)
981
- # end
982
- # end
983
- private
984
-
985
- #def generate_sql_list attributes={}
986
- # attributes.map do | key, value |
987
- # case value
988
- # when Numeric
989
- # key.to_s << " = " << value.to_s
990
- # else # String, Symbol
991
- # key.to_s << ' = ' << "\'#{ value }\'"
992
- # # else
993
- # # puts "ERROR, value-> #{value}, class -> #{value.class}"
994
- # end
995
- # end.join( ' and ' )
996
- #
997
- #end
998
- #
999
- def property_uri(this_class_name)
1000
- if block_given?
1001
- "property/#{ @database }/#{this_class_name}/" << yield
1002
- else
1003
- "property/#{ @database }/#{this_class_name}"
1004
- end
1005
- end
1006
-
1007
- def self.simple_uri *names
1008
- names.each do | name |
1009
- m_name = (name.to_s << '_uri').to_sym
1010
- define_method( m_name ) do | &b |
1011
- if b
1012
- "#{name.to_s}/"<< @database << "/" << b.call
1013
- else
1014
- "#{name.to_s}/"<< @database
1015
- end # branch
1016
- end # def
1017
- end
1018
- end
1019
-
1020
- def self.sql_uri *names
1021
- names.each do | name |
1022
- define_method ((name.to_s << '_sql_uri').to_sym) do
1023
- "#{name.to_s}/" << @database << "/sql/"
1024
- end
1025
- end
1026
- end
1027
-
1028
- simple_uri :database, :document, :class, :batch, :function
1029
- sql_uri :command , :query
1030
-
1031
-
1032
-
1033
- end # class
1034
- end # module
1035
- __END__
1036
-