active-orient 0.4 → 0.5

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 (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
@@ -0,0 +1,211 @@
1
+ require 'orientdb'
2
+ require_relative "database_utils.rb" #common methods without rest.specific content
3
+ require_relative "class_utils.rb" #common methods without rest.specific content
4
+ require_relative "orientdb_private.rb"
5
+
6
+
7
+ module ActiveOrient
8
+
9
+
10
+ class API
11
+ include OrientSupport::Support
12
+ include DatabaseUtils
13
+ include ClassUtils
14
+ include OrientDbPrivate
15
+ include OrientDB
16
+
17
+ mattr_accessor :logger # borrowed from active_support
18
+ mattr_accessor :default_server
19
+ attr_reader :database # Used to read the working database
20
+
21
+ #### INITIALIZATION ####
22
+
23
+
24
+ def initialize database: nil, connect: true, preallocate: true
25
+ self.logger = Logger.new('/dev/stdout') unless logger.present?
26
+ self.default_server = {
27
+ :server => 'localhost',
28
+ :port => 2480,
29
+ :protocol => 'http',
30
+ :user => 'root',
31
+ :password => 'root',
32
+ :database => 'temp'
33
+ }.merge default_server.presence || {}
34
+ @database = database || default_server[:database]
35
+ @all_classes=[]
36
+ #puts ["remote:#{default_server[:server]}/#{@database}",
37
+ # default_server[:user], default_server[:password] ]
38
+ connect() if connect
39
+ # @db = DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
40
+ # default_server[:user], default_server[:password] )
41
+ ActiveOrient::Model.api = self
42
+ preallocate_classes if preallocate
43
+
44
+ end
45
+
46
+ def db
47
+ @db
48
+ end
49
+
50
+ # Used for the connection on the server
51
+ #
52
+
53
+ # Used to connect to the database
54
+
55
+ def connect
56
+
57
+ @db = DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
58
+ default_server[:user], default_server[:password] )
59
+ @classes = get_database_classes
60
+ end
61
+
62
+
63
+
64
+ def get_classes *attributes
65
+ classes= @db.metadata.schema.classes.map{|x| { 'name' => x.name , 'superClass' => x.get_super_class.nil? ? '': x.get_super_class.name } }
66
+ unless attributes.empty?
67
+ classes.map{|y| y.select{|v,_| attributes.include?(v)}}
68
+ else
69
+ classes
70
+ end
71
+
72
+ end
73
+
74
+
75
+ def create_classes classes , &b
76
+ consts = allocate_classes_in_ruby( classes , &b )
77
+
78
+ all_classes = consts.is_a?( Array) ? consts.flatten : [consts]
79
+ get_database_classes(requery: true)
80
+ selected_classes = all_classes.map do | this_class |
81
+ this_class unless get_database_classes(requery: false).include?( this_class.ref_name ) rescue nil
82
+ end.compact.uniq
83
+ command= selected_classes.map do | database_class |
84
+ ## improper initialized ActiveOrient::Model-classes lack a ref_name class-variable
85
+ next if database_class.ref_name.blank?
86
+ c = if database_class.superclass == ActiveOrient::Model || database_class.superclass.ref_name.blank?
87
+ puts "CREATE CLASS #{database_class.ref_name} "
88
+ OClassImpl.create @db, database_class.ref_name
89
+ else
90
+ puts "CREATE CLASS #{database_class.ref_name} EXTENDS #{database_class.superclass.ref_name}"
91
+ OClassImpl.create @db, superClass: database_class.ref_name
92
+ end
93
+ end
94
+
95
+ # update the internal class hierarchy
96
+ get_database_classes requery: true
97
+ # return all allocated classes, no matter whether they had to be created in the DB or not.
98
+ # keep the format of the input-parameter
99
+ consts.shift if block_given? && consts.is_a?( Array) # remove the first element
100
+ # remove traces of superclass-allocations
101
+ if classes.is_a? Hash
102
+ consts = Hash[ consts ]
103
+ consts.each_key{ |x| consts[x].delete_if{|y| y == x} if consts[x].is_a? Array }
104
+ end
105
+ consts
106
+ end
107
+
108
+
109
+
110
+ def delete_class o_class
111
+ @db.schema.drop_class classname(o_class)
112
+ get_database_classes requery: true
113
+ end
114
+ =begin
115
+ Creates properties and optional an associated index as defined in the provided block
116
+ create_properties(classname or class, properties as hash){index}
117
+
118
+ The default-case
119
+ create_properties(:my_high_sophisticated_database_class,
120
+ con_id: {type: :integer},
121
+ details: {type: :link, linked_class: 'Contracts'}) do
122
+ contract_idx: :notunique
123
+ end
124
+
125
+ A composite index
126
+ create_properties(:my_high_sophisticated_database_class,
127
+ con_id: {type: :integer},
128
+ symbol: {type: :string}) do
129
+ {name: 'indexname',
130
+ on: [:con_id, :details] # default: all specified properties
131
+ type: :notunique # default: :unique
132
+ }
133
+ end
134
+ =end
135
+
136
+ def create_properties o_class, **all_properties, &b
137
+ logger.progname = 'JavaApi#CreateProperties'
138
+ ap = all_properties
139
+ created_properties = ap.map do |property, specification |
140
+ puts "specification: #{specification.inspect}"
141
+ field_type = ( specification.is_a?( Hash) ? specification[:type] : specification ).downcase.to_sym rescue :string
142
+ the_other_class = specification.is_a?(Hash) ? specification[:other_class] : nil
143
+ other_class = if the_other_class.present?
144
+ @db.get_class( the_other_class)
145
+ end
146
+ index = ap.is_a?(Hash) ? ap[:index] : nil
147
+ if other_class.present?
148
+ @db.get_class(classname(o_class)).add property,[ field_type, other_class ], { :index => index }
149
+ else
150
+ @db.get_class(classname(o_class)).add property, field_type, { :index => index }
151
+ end
152
+ end
153
+ if block_given?
154
+ attr = yield
155
+ index_parameters = case attr
156
+ when String, Symbol
157
+ { name: attr }
158
+ when Hash
159
+ { name: attr.keys.first , type: attr.values.first, on: all_properties.keys.map(&:to_s) }
160
+ else
161
+ nil
162
+ end
163
+ create_index o_class, **index_parameters unless index_parameters.blank?
164
+ end
165
+ created_properties.size # return_value
166
+
167
+ end
168
+
169
+ def get_properties o_class
170
+ @db.get_class(classname(o_class)).propertiesMap
171
+ end
172
+
173
+
174
+
175
+ def create_index o_class, name:, on: :automatic, type: :unique
176
+ logger.progname = 'JavaApi#CreateIndex'
177
+ begin
178
+ c = @db.get_class( classname( o_class ))
179
+ index = if on == :automatic
180
+ nil # not implemented
181
+ elsif on.is_a? Array
182
+ c.createIndex name.to_s, INDEX_TYPES[type], *on
183
+ else
184
+ c.createIndex name.to_s, INDEX_TYPES[type], on
185
+ end
186
+ end
187
+ end
188
+
189
+ def create_record o_class, attributes: {}
190
+ logger.progname = 'HavaApi#CreateRecord'
191
+ attributes = yield if attributes.empty? && block_given?
192
+ new_record = insert_document( o_class, attributes.to_orient )
193
+
194
+
195
+ end
196
+ alias create_document create_record
197
+
198
+ def insert_document o_class, attributes
199
+ d = Document.create @db, classname(o_class), **attributes
200
+ d.save
201
+ ActiveOrient::Model.get_model_class(classname(o_class)).new attributes.merge( { "@rid" => d.rid,
202
+ "@version" => d.version,
203
+ "@type" => 'd',
204
+ "@class" => classname(o_class) } )
205
+
206
+
207
+
208
+ end
209
+ end
210
+ end
211
+
@@ -0,0 +1,53 @@
1
+ # to do
2
+ # instead of creating a class, use a module which is included on startup
3
+ # then, after specifying the namespace and before autoaccolating the database-classes create the proper E-Base-class and include this stuff
4
+ class E < ActiveOrient::Model
5
+ ## link to the library-class
6
+ class << self
7
+ =begin
8
+ establish contrains on Edges
9
+
10
+ Edges are uniq!
11
+
12
+ Creates individual indices for child-classes if applied to the class itself.
13
+ =end
14
+ def uniq_index
15
+ create_property :in, type: :link, linked_class: :V
16
+ create_property :out, type: :link, linked_class: :V
17
+ create_index "#{self.name}_idx", on: [ :in, :out ]
18
+ end
19
+ =begin
20
+ Instantiate a new Edge between two Vertices
21
+
22
+ The parameters »from« **or** »to« can take a list of model-records. Then subsequent edges are created.
23
+
24
+ :call-seq:
25
+ Model.create from:, to:, attributes:{}
26
+ =end
27
+
28
+
29
+ def create **keyword_arguments
30
+ new_edge = db.create_edge self, **keyword_arguments
31
+ new_edge = new_edge.pop if new_edge.is_a?( Array) && new_edge.size == 1
32
+ # vertices must be reloaded
33
+
34
+ new_edge # returns the created edge (or an array of created edges
35
+ end
36
+
37
+ # to do
38
+ # def delete
39
+ # delete an edge (as class method)
40
+ # and
41
+ # def remove
42
+ # delete an edge (as instance method)
43
+ #
44
+ def delete where: attributes
45
+ puts "work in progress"
46
+ end
47
+
48
+ # remove works on record-level
49
+ end
50
+ def remove
51
+ db.delete_edge self
52
+ end
53
+ end
@@ -0,0 +1,77 @@
1
+ require_relative "the_class.rb"
2
+ require_relative "the_record.rb"
3
+
4
+ module ActiveOrient
5
+ class Model < ActiveOrient::Base
6
+
7
+ include BaseProperties
8
+ include ModelRecord # For objects (file: lib/record.rb)
9
+ extend ModelClass # For classes
10
+
11
+ =begin
12
+ Example:
13
+ ActiveOrient::Model.autoload_object "#00:00"
14
+
15
+ either retrieves the object from the rid_store or loads it from the DB.
16
+
17
+ The rid_store is updated!
18
+
19
+ To_do: fetch for version in the db and load the object if a change is detected
20
+
21
+ Note: This function is not in ModelClass since it needs to use @@rid_store
22
+ =end
23
+
24
+ def self.autoload_object rid
25
+ rid = rid[1..-1] if rid[0]=='#'
26
+ if rid.rid?
27
+ if @@rid_store[rid].present?
28
+ @@rid_store[rid] # return_value
29
+ else
30
+ db.get_record(rid)
31
+ end
32
+ else
33
+ logger.progname = "ActiveOrient::Model#AutoloadObject"
34
+ logger.info{"#{rid} is not a valid rid."}
35
+ end
36
+ end
37
+
38
+ ## to prevent errors when calling to_a
39
+ def to_ary # :nodoc:
40
+ attributes.to_a
41
+ end
42
+
43
+ def document # :nodoc:
44
+ @d
45
+ end
46
+
47
+ =begin
48
+ Deletes the database class and removes the ruby-class
49
+ =end
50
+ def self.delete_class
51
+ orientdb.delete_class self
52
+ ## namespace is defined in config/boot
53
+ namespace.send(:remove_const, naming_convention.to_sym)
54
+ end
55
+
56
+ # provides an unique accessor on the Class
57
+ # works with a class-variable, its unique through all Subclasses
58
+ mattr_accessor :orientdb # points to the instance of the REST-DB-Client used for Administration
59
+ # i.e. creation and deleting of classes and databases
60
+ mattr_accessor :db # points to the instance of the Client used for Database-Queries
61
+ mattr_accessor :api
62
+ # mattr_accessor :logger ... already inherented from ::Base
63
+ mattr_accessor :namespace # Namespace in which Model records are initialized, a constant ( defined in config.yml )
64
+ mattr_accessor :model_dir # path to model-files
65
+
66
+ # mattr_accessor :ref_name
67
+ # Used to read the metadata
68
+ attr_reader :metadata
69
+
70
+ # provides an accessor at class level
71
+ # its unique on all instances
72
+ class << self
73
+ attr_accessor :ref_name
74
+ attr_accessor :abstract
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,480 @@
1
+ module ModelClass
2
+
3
+ ########### CLASS FUNCTIONS ######### SELF ####
4
+
5
+
6
+ ######## INITIALIZE A RECORD FROM A CLASS ########
7
+
8
+
9
+ =begin
10
+ NamingConvention provides a translation from database-names to class-names.
11
+
12
+ Should provide
13
+ to_s.capitalize_first_letter
14
+ as minimum.
15
+ Can be overwritten to provide different conventions for different classes, eg. Vertexes or edges.
16
+
17
+ To overwrite use
18
+ class Model < ActiveOrient::Model[:: ...]
19
+ def self.naming_convention
20
+ ( conversion code )
21
+ end
22
+ end
23
+ =end
24
+ def naming_convention name=nil # :nodoc:
25
+ name.present? ? name.to_s.camelize : ref_name.camelize
26
+ end
27
+
28
+ =begin
29
+ orientdb_class is used to create or refer a ActiveOrient:Model:{class} by providing its name
30
+
31
+ Parameter: name: string or symbol
32
+ Parameter: superclass: If class, then this is used unmodified
33
+ If string or symbol, its used to reference an existing class
34
+ if :find_ME, its derived from the classes-hash
35
+ Attention: If a class is created by orientdb_class, its only allocated in ruby-space.
36
+ The class is thus not present in the classes-array, which reflects the database-classes.
37
+ If a class depending on a superclass is to be created, the superclass is derived from
38
+ the classes-array. In such a case, the allocation only works, if the class itself is
39
+ used as parameter "superclass"
40
+ i.e.
41
+ ActiveOrient::Model.orientdb_class name: 'hurra'
42
+ AvtiveOrient::Model.orientdb_class name: 'hip_hip' , superclass: Hurra
43
+ =end
44
+
45
+ def orientdb_class name:, superclass: nil # :nodoc: # public method: autoload_class
46
+ logger.progname = "ModelClass#OrientDBClass"
47
+ # @s-class is a cash for actual String -> Class relations
48
+ @s_class = HashWithIndifferentAccess.new( V: V, E: E) unless @s_class.present?
49
+
50
+ update_my_array = ->(s) { @s_class[s.ref_name] = s unless @s_class[s.ref_name].present? }
51
+ get_class = ->(n) { @s_class[n] }
52
+
53
+
54
+ ref_name = name.to_s
55
+ klass = if superclass.present? # superclass is parameter, use if class, otherwise transfer to class
56
+ s= if superclass.is_a? Class
57
+ namespace.send( :const_get, superclass.to_s )
58
+ else
59
+ superclass = orientdb.get_db_superclass( ref_name ) if superclass == :find_ME
60
+ if superclass.present?
61
+ namespace.send( :const_get, get_class[superclass].to_s )
62
+ else
63
+ self
64
+ end
65
+ end
66
+ Class.new(s)
67
+ else
68
+ Class.new(self)
69
+ end
70
+ # namespace is defined in config/boot
71
+ name = klass.naming_convention ref_name #
72
+ if namespace.send :const_defined?, name
73
+ retrieved_class = namespace.send :const_get, name
74
+ else
75
+
76
+ new_class = namespace.send :const_set, name, klass
77
+ new_class.ref_name = ref_name
78
+ update_my_array[new_class]
79
+ # logger.debug{"created:: Class #{new_class} < #{new_class.superclass} "}
80
+ # logger.debug{"database-table:: #{ref_name} "}
81
+ new_class # return_value
82
+ end
83
+ rescue NameError => e
84
+ logger.error "ModelClass #{name.inspect} cannot be initialized."
85
+ logger.error e.message
86
+ logger.error e.backtrace.map {|l| " #{l}\n"}.join
87
+ nil # return_value
88
+ #end
89
+ end
90
+ =begin
91
+ Retrieves the preallocated class derived from ActiveOrient::Model
92
+
93
+ Only classes noted in the @classes-Array of orientdb are fetched.
94
+ =end
95
+ def get_model_class name
96
+ if orientdb.database_classes.include?(name)
97
+ orientdb_class name: name, superclass: :find_ME
98
+ else
99
+ nil
100
+ end
101
+ end
102
+
103
+
104
+
105
+ =begin
106
+ requires the file specified in the model-dir
107
+
108
+ In fact, the model-files are loaded instead of required. After recreation of a class (Class.delete_class,
109
+ ORD.create_class classname) custom methods declared in the model files are present. Required modelfiles are
110
+ gone, if the class is destroyed, but the interpreter thinks, they have already been required. Rebuilding the
111
+ class does not reestablish the connection to the required model file.
112
+
113
+ Actual only a flat directory is supported. However -the Parameter model has the format: [ superclass, class ]. Its possible to extend the method adress a model-tree.
114
+ =end
115
+ def require_model_file
116
+ logger.progname = 'ModelClass#RequireModelFile'
117
+ if File.exists?( ActiveOrient::Model.model_dir )
118
+ model= model.flatten.last if model.is_a?( Array )
119
+ filename = ActiveOrient::Model.model_dir + "/" + self.to_s.underscore + '.rb'
120
+ puts "REQUIRE_MODEL_FILE: #{self.to_s} <-- #{self.superclass}"
121
+ if File.exists?(filename )
122
+ if load filename
123
+ logger.info{ "#{filename} sucessfully loaded" }
124
+ else
125
+ logger.error{ "#{filename} load error" }
126
+ end
127
+ else
128
+ logger.info{ "model-file not present: #{filename}" }
129
+ end
130
+ else
131
+ logger.info{ "Directory #{ ActiveOrient::Model.model_dir } not present " }
132
+ end
133
+ rescue TypeError => e
134
+ puts "TypeError: #{e.message}"
135
+ puts "Working on #{self.to_s} -> #{self.superclass}"
136
+ puts "Class_hierarchy: #{orientdb.class_hierarchy.inspect}."
137
+ print e.backtrace.join("\n")
138
+ raise
139
+ #
140
+ end
141
+
142
+ ########## CREATE ############
143
+
144
+ =begin
145
+ Universal method to create a new record.
146
+ It's obverloaded to create specific kinds, eg. edges
147
+
148
+ Example:
149
+ ORD.create_class :test
150
+ Test.create string_attribute: 'a string', symbol_attribute: :a_symbol, array_attribute: [34,45,67]
151
+ Test.create link_attribute: Test.create( :a_new_attribute => 'new' )
152
+
153
+ =end
154
+ def create **attributes
155
+ attributes.merge :created_at => Time.new
156
+ db.create_record self, attributes: attributes
157
+ end
158
+
159
+ =begin
160
+ Creates or updates a record.
161
+ Parameter:
162
+ set: A hash of attributes to insert or update unconditionally
163
+ where: A string or hash as condition which should return just one record.
164
+
165
+ The where-part should be covered with an unique-index.
166
+ If :where is omitted, #Upsert becomes #Create, attributes are taken from :set.
167
+
168
+ returns the affected record
169
+ =end
170
+ def upsert set: {}, where: {}, &b
171
+ db.upsert self, set: set, where: where, &b
172
+ end
173
+ =begin
174
+ Create a new Instance of the Class with the applied attributes if does not exists,
175
+ otherwise update it. It returns the freshly instantiated Objects
176
+ =end
177
+
178
+ def update_or_create_records set: {}, where: {}, **args, &b
179
+ db.update_or_create_records self, set: set, where: where, **args, &b
180
+ end
181
+
182
+ alias update_or_create_documents update_or_create_records
183
+ alias create_or_update_document upsert
184
+ alias update_or_create upsert
185
+
186
+
187
+ =begin
188
+ Create a Property in the Schema of the Class
189
+ :call-seq: Model.create_property(field (required), type:'string', linked_class: nil, index: nil) do
190
+ index
191
+ end
192
+
193
+ Examples:
194
+
195
+ create_property :customer_id, type: integer, index: :unique
196
+ create_property :name, type: :string, index: :not_unique
197
+ create_property :in, type: :link, linked_class: :V (used by edges)
198
+ =end
199
+
200
+ def create_property field, **keyword_arguments, &b
201
+ orientdb.create_property self, field, **keyword_arguments, &b
202
+ end
203
+
204
+ # Create more Properties in the Schema of the Class
205
+
206
+ def create_properties argument_hash, &b
207
+ orientdb.create_properties self, argument_hash, &b
208
+ end
209
+
210
+
211
+ # Add an Index
212
+ def create_index name, **attributes
213
+ orientdb.create_index self, name: name, **attributes
214
+ end
215
+
216
+ ########## GET ###############
217
+
218
+ def classname # :nodoc: #
219
+ ref_name
220
+ end
221
+
222
+ # get elements by rid
223
+
224
+ def get rid
225
+ db.get_record rid
226
+ end
227
+
228
+ # get all the elements of the class
229
+
230
+ def all
231
+ db.get_records from: self
232
+ end
233
+
234
+ # get the first element of the class
235
+
236
+ def first where: {}
237
+ db.get_records(from: self, where: where, limit: 1).pop
238
+ end
239
+
240
+ # get the last element of the class
241
+
242
+ def last where: {}
243
+ db.get_records(from: self, where: where, order: {"@rid" => 'desc'}, limit: 1).pop
244
+ end
245
+ # Used to count of the elements in the class
246
+
247
+ def count **args
248
+ orientdb.count from: self, **args
249
+ end
250
+
251
+ # Get the properties of the class
252
+
253
+ def get_properties
254
+ object = orientdb.get_class_properties self
255
+ HashWithIndifferentAccess.new :properties => object['properties'], :indexes => object['indexes']
256
+ end
257
+ alias get_class_properties get_properties
258
+
259
+ # Print the properties of the class
260
+
261
+ def print_class_properties
262
+ orientdb.print_class_properties self
263
+ end
264
+
265
+ =begin
266
+ Parameter projection:
267
+ »select« is a method of enumeration, we use »projection:« to specify anything between »select« and »from« in the query-string.
268
+ projection: a_string --> inserts the sting as it appearsb
269
+ an OrientSupport::OrientQuery-Object --> performs a sub-query and uses the result for further querying though the given parameters.
270
+ [a, b, c] --> "a, b, c" (inserts a comma-separated list)
271
+ {a: b, "sum(x)" => f} --> "a as b, sum(x) as f" (renames properties and uses functions)
272
+
273
+ Parameter distinct:
274
+ Performs a Query like » select distinct(property) [as property] from ...«
275
+ distinct: :property --> the result is mapped to the property »distinct«.
276
+ [:property] --> the result replaces the property
277
+ {property: :some_name} --> the result is mapped to ModelInstance.some_name
278
+
279
+ Parameter Order
280
+ Sorts the result-set. If new properties are introduced via select:, distinct: etc. Sorting takes place on these properties
281
+ order: :property {property: asc, property: desc}[property, property, .. ](orderdirection is 'asc')
282
+
283
+ Further supported Parameter:
284
+ group_by
285
+ skip
286
+ limit
287
+ unwind
288
+
289
+ see orientdb- documentation (https://orientdb.com/docs/last/SQL-Query.html)
290
+
291
+ Parameter query:
292
+ Instead of providing the parameter, the OrientSupport::OrientQuery can build and tested before the method-call. The OrientQuery-Object can be provided with the query-parameter. I.e.
293
+ q = OrientSupport::OrientQuery.new
294
+ ORD.create_class :test_model
295
+ q.from TestModel
296
+ q.where {name: 'Thomas'}
297
+ count = TestModel.count query: q
298
+ q.limit 10
299
+ 0.step(count,10) do |x|
300
+ q.skip = x
301
+ puts TestModel.get_documents(query: q).map{|x| x.adress }.join('\t')
302
+ end
303
+ prints a Table with 10 columns.
304
+ =end
305
+
306
+ def get_records **args
307
+ db.get_records(from: self, **args){self}
308
+ end
309
+ alias get_documents get_records
310
+
311
+ =begin
312
+ Performs a query on the Class and returns an Array of ActiveOrient:Model-Records.
313
+
314
+ Example:
315
+ Log.where priority: 'high'
316
+ --> submited database-request: query/hc_database/sql/select from Log where priority = 'high'/-1
317
+ => [ #<Log:0x0000000480f7d8 @metadata={ ... }, ...
318
+ =end
319
+
320
+ def custom_where search_string
321
+ q = OrientSupport::OrientQuery.new from: self, where: search_string
322
+ puts q.compose
323
+ query_database q
324
+ end
325
+ def where **attributes
326
+ ##puts "ATTRIBUTES: "+attributes.inspect
327
+ q = OrientSupport::OrientQuery.new from: self, where: attributes
328
+ query_database q
329
+ end
330
+ =begin
331
+ Performs a Match-Query
332
+
333
+ The Query starts at the given ActiveOrient::Model-Class. The where-cause narrows the sample to certain
334
+ records. In the simplest version this can be returnd:
335
+
336
+ Industry.match where:{ name: "Communications" }
337
+ => #<ActiveOrient::Model::Query:0x00000004309608 @metadata={"type"=>"d", "class"=>nil, "version"=>0, "fieldTypes"=>"Industries=x"}, @attributes={"Industries"=>"#21:1", (...)}>
338
+
339
+ The attributes are the return-Values of the Match-Query. Unless otherwise noted, the pluralized Model-Classname is used as attribute in the result-set.
340
+
341
+ I.match( where: { name: 'Communications' }).first.Industries
342
+
343
+ is the same then
344
+ Industry.where name: "Communications"
345
+
346
+
347
+ The Match-Query uses this result-set as start for subsequent queries on connected records.
348
+ These connections are defined in the Block
349
+
350
+ var = Industry.match do | query |
351
+ query.connect :in, count: 2, as: 'Subcategories'
352
+ puts query.to_s # print the query send to the database
353
+ query # important: block has to return the query
354
+ end
355
+ => MATCH {class: Industry, as: Industries} <-- {} <-- { as: Subcategories } RETURN Industries, Subcategories
356
+
357
+ The result-set has two attributes: Industries and Subcategories, pointing to the filtered datasets.
358
+
359
+ By using subsequent »connect« and »statement« method-calls even complex Match-Queries can be clearly constructed.
360
+
361
+ =end
362
+
363
+ def match where: {}
364
+ query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }
365
+ query.match_statements[0].where = where unless where.empty?
366
+ if block_given?
367
+ query_database yield(query), set_from: false
368
+ else
369
+ logger.progname = 'ActiveOrient::Model#Match'
370
+ logger.error{ "Query-Details have to be specified in a Block" }
371
+ end
372
+
373
+ end
374
+
375
+
376
+ # Get the superclass of the class
377
+
378
+ def superClass
379
+ { superclass => superclass.ref_name }
380
+ # logger.progname = 'ActiveOrient::Model#Superclass'
381
+ # r = orientdb.get_classes('name', 'superClass').detect{|x|
382
+ # x["name"].downcase == new.class.to_s.downcase.split(':')[-1].to_s
383
+ # }['superClass']
384
+ # if r.empty?
385
+ # logger.info{"#{self} does not have any superclass. Probably it is a Document"}
386
+ # end
387
+ # return r
388
+ end
389
+
390
+ =begin
391
+ QueryDatabase sends the Query, direct to the database.
392
+ The result is not nessessary an Object of self.
393
+ However, if the query does not return an array of Active::Model-Objects, then the entries become self
394
+ =end
395
+
396
+ def query_database query, set_from: true
397
+ query.from self if set_from && query.is_a?(OrientSupport::OrientQuery) && query.from.nil?
398
+ sql_cmd = -> (command) {{ type: "cmd", language: "sql", command: command }}
399
+ db.execute do
400
+ sql_cmd[query.to_s]
401
+ end
402
+ end
403
+
404
+ ########### DELETE ###############
405
+
406
+ # Delete a property from the class
407
+
408
+ def delete_property field
409
+ orientdb.delete_property self, field
410
+ end
411
+
412
+ # Delete a record
413
+
414
+ def delete_record *rid
415
+ db.delete_record rid
416
+ end
417
+ alias delete_document delete_record
418
+
419
+ # Delete a record from the class
420
+
421
+ def delete_records where: {}
422
+ orientdb.delete_records self, where: where
423
+ end
424
+ alias delete_documents delete_records
425
+
426
+
427
+ ########### UPDATE #############
428
+
429
+ # Update records of a class
430
+
431
+ def update_records set:, where:
432
+ db.update_records self, set: set, where: where
433
+ end
434
+ alias update_documents update_records
435
+
436
+ ##################### EXPERIMENT #################
437
+
438
+ =begin
439
+ Suppose that you created a graph where vertexes month is connected with
440
+ the vertexes day by the edge TIMEOF.
441
+ Suppose we want to find all the days in the first month and in the third month..
442
+
443
+ Usually we can do in the following way.
444
+
445
+ ORD.create_class "Month"
446
+ (.. put some records into Month ... )
447
+ firstmonth = Month.first
448
+ thirdmonth = month.all[2]
449
+ days_firstmonth = firstmonth.out_TIMEOF.map{|x| x.in}
450
+ days_thirdmonth = thirdmonth.out_TIMEOF.map{|x| x.in}
451
+
452
+ However we can obtain the same result with the following command
453
+
454
+ Month.add_edge_link name: "days", direction: "out", edge: TIME_OF
455
+ firstmonth = month.first
456
+ thirdmonth = month.all[2]
457
+ days_firstmonth = firstmonth.days
458
+ days_thirdmonth = thirdmonth.days
459
+
460
+ To get their value you can do:
461
+ thirdmonth.days.value
462
+ =end
463
+
464
+
465
+ def add_edge_link name:, direction: :out, edge:
466
+ dir = direction.to_s == "out" ? :out : :in
467
+ define_method(name.to_sym) do
468
+ return self["#{dir}_#{edge.classname}"].map{|x| x["in"]}
469
+ end
470
+ end
471
+
472
+ =begin
473
+ See http://orientdb.com/docs/2.1/SQL-Alter-Property.html
474
+ =end
475
+
476
+ def alter_property property:, attribute: "DEFAULT", alteration:
477
+ orientdb.alter_property self, property: property, attribute: attribute, alteration: alteration
478
+ end
479
+
480
+ end