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
@@ -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