active-orient 0.42 → 0.79

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/Gemfile +13 -5
  4. data/Guardfile +12 -4
  5. data/README.md +67 -280
  6. data/VERSION +1 -1
  7. data/active-orient.gemspec +6 -5
  8. data/bin/active-orient-0.6.gem +0 -0
  9. data/bin/active-orient-console +85 -0
  10. data/config/boot.rb +72 -1
  11. data/config/config.yml +10 -0
  12. data/config/connect.yml +9 -4
  13. data/examples/books.rb +92 -40
  14. data/examples/streets.rb +89 -85
  15. data/examples/test_commands.rb +97 -0
  16. data/examples/test_commands_2.rb +59 -0
  17. data/examples/test_commands_3.rb +55 -0
  18. data/examples/test_commands_4.rb +33 -0
  19. data/examples/time_graph.md +162 -0
  20. data/lib/active-orient.rb +75 -9
  21. data/lib/base.rb +238 -169
  22. data/lib/base_properties.rb +68 -60
  23. data/lib/class_utils.rb +226 -0
  24. data/lib/database_utils.rb +98 -0
  25. data/lib/init.rb +79 -0
  26. data/lib/java-api.rb +442 -0
  27. data/lib/jdbc.rb +211 -0
  28. data/lib/model/custom.rb +26 -0
  29. data/lib/model/edge.rb +70 -0
  30. data/lib/model/model.rb +134 -0
  31. data/lib/model/the_class.rb +607 -0
  32. data/lib/model/the_record.rb +266 -0
  33. data/lib/model/vertex.rb +236 -0
  34. data/lib/orientdb_private.rb +48 -0
  35. data/lib/other.rb +371 -0
  36. data/lib/railtie.rb +68 -0
  37. data/lib/rest/change.rb +147 -0
  38. data/lib/rest/create.rb +279 -0
  39. data/lib/rest/delete.rb +134 -0
  40. data/lib/rest/operations.rb +211 -0
  41. data/lib/rest/read.rb +171 -0
  42. data/lib/rest/rest.rb +112 -0
  43. data/lib/rest_disabled.rb +24 -0
  44. data/lib/support/logging.rb +38 -0
  45. data/lib/support/orient.rb +196 -0
  46. data/lib/support/orientquery.rb +469 -0
  47. data/rails.md +154 -0
  48. data/rails/activeorient.rb +32 -0
  49. data/rails/config.yml +10 -0
  50. data/rails/connect.yml +17 -0
  51. metadata +65 -24
  52. data/active-orient-0.4.gem +0 -0
  53. data/active-orient-0.41.gem +0 -0
  54. data/lib/model.rb +0 -468
  55. data/lib/orient.rb +0 -98
  56. data/lib/query.rb +0 -88
  57. data/lib/rest.rb +0 -1059
  58. data/lib/support.rb +0 -372
  59. data/test.rb +0 -4
  60. data/usecase.md +0 -91
@@ -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,26 @@
1
+ module CustomClass
2
+ =begin
3
+ The REST-Interface does not work with "select from SomeClass where a_property like 'pattern%' "
4
+
5
+ This is rewritten as
6
+
7
+ SomeClass.like "name = D*", order: 'asc'
8
+
9
+ The order-argument is optional, "asc" is the default.
10
+ This Primitiv-Version only accepts the wildcards "*" and "%" at the end of the seach-string.
11
+ The Wildcard can be omitted.
12
+
13
+ The method does not accept further arguments.
14
+ =end
15
+ def like operation, order: 'asc'
16
+ # remove all spaces and split the resulting word
17
+ p, s = operation.gsub(/\s+/, "").split("=")
18
+ if ["%","*"].include?(s[-1])
19
+ s.chop!
20
+ end
21
+
22
+ q = OrientSupport::OrientQuery.new where: { "#{p}.left(#{s.length})" => s } ,order: { p => order }
23
+ query_database q
24
+
25
+ end
26
+ end
@@ -0,0 +1,70 @@
1
+ class E < ActiveOrient::Model
2
+ ## class methods
3
+ class << self
4
+ =begin
5
+ Establish constrains on Edges
6
+
7
+ After applying this method Edges are uniq!
8
+
9
+ Creates individual indices for child-classes if applied to the class itself.
10
+ =end
11
+ def uniq_index
12
+ create_property :in, type: :link, linked_class: :V
13
+ create_property :out, type: :link, linked_class: :V
14
+ create_index "#{ref_name}_idx", on: [ :in, :out ]
15
+ end
16
+
17
+ =begin
18
+ Instantiate a new Edge between two Vertices
19
+
20
+ =end
21
+ def create from:, to: , attributes: {}, transaction: false
22
+ return nil if from.blank? || to.blank?
23
+ statement = "CREATE EDGE #{ref_name} from #{from.to_or} to #{to.to_or}"
24
+ transaction = true if [:fire, :complete, :run].include?(transaction)
25
+ ir= db.execute( transaction: transaction ){ statement }
26
+ from.reload! # get last version
27
+ to.is_a?(Array)? to.each( &:reload! ) : to.reload!
28
+ ir
29
+
30
+ rescue ArgumentError => e
31
+ logger.error{ "wrong parameters #{keyword_arguments} \n\t\t required: from: , to: , attributes:\n\t\t Edge is NOT created"}
32
+ end
33
+
34
+ =begin
35
+ Fires a "delete edge" command to the database.
36
+
37
+ The where statement can be empty ( "" or {}"), then all edges are removed
38
+
39
+ The rid-cache is resetted
40
+
41
+ :call-seq:
42
+ delete where:
43
+ =end
44
+ def delete where:
45
+
46
+ db.execute { "delete edge #{ref_name} #{db.compose_where(where)}" }
47
+ reset_rid_store
48
+
49
+ end
50
+
51
+ end # class methods
52
+
53
+ ### instance methods ###
54
+
55
+ =begin
56
+ Removes the actual ActiveOrient::Model-Edge-Object
57
+
58
+ This method overloads the unspecified ActiveOrient::Model#remove-Method
59
+ =end
60
+ def remove
61
+ # remove works on record-level
62
+ db.delete_edge self
63
+ end
64
+
65
+ def to_human
66
+ displayed_attributes = content_attributes.reject{|k,_| [:in, :out].include?(k) }
67
+ "<#{self.class.to_s.demodulize}[#{rrid}] -i-> ##{ attributes[:in].rid} #{displayed_attributes.to_human} -o-> #{out.rrid}>"
68
+ end
69
+
70
+ end
@@ -0,0 +1,134 @@
1
+ require_relative "the_class.rb"
2
+ require_relative "the_record.rb"
3
+ require_relative "custom.rb"
4
+ module ActiveOrient
5
+ class Model < ActiveOrient::Base
6
+
7
+ include BaseProperties
8
+ include ModelRecord # For objects (file: lib/record.rb)
9
+ extend CustomClass # for customized class-methods aka like
10
+ extend ModelClass # For classes
11
+
12
+ =begin
13
+ Either retrieves the object from the rid_store or loads it from the DB.
14
+
15
+ Example:
16
+ ActiveOrient::Model.autoload_object "#00:00"
17
+
18
+
19
+ The rid_store is updated!
20
+
21
+ _Todo:_ fetch for version in the db and load the object only if a change is detected
22
+
23
+ _Note:_ This function is not located in ModelClass since it needs to use @@rid_store
24
+ =end
25
+
26
+ def self.autoload_object rid
27
+ rid = rid[1..-1] if rid[0]=='#'
28
+ if rid.rid?
29
+ if @@rid_store[rid].present?
30
+ @@rid_store[rid] # return_value
31
+ else
32
+ get(rid)
33
+ end
34
+ else
35
+ logger.progname = "ActiveOrient::Model#AutoloadObject"
36
+ logger.info{"#{rid} is not a valid rid."}
37
+ end
38
+ end
39
+
40
+ ## used for active-model-compatibility
41
+ def persisted?
42
+ true
43
+ end
44
+
45
+
46
+
47
+ =begin
48
+ Based on the parameter rid (as "#{a}:{b}" or "{a}:{b}") the cached value is used if found.
49
+ Otherwise the provided Block is executed, which is responsible for the allocation of a new dataset
50
+
51
+ i.e.
52
+ ActiveOrient::Model.use_or_allocated my_rid do
53
+ ActiveOrient::Model.orientdb_class(name: raw_data['@class']).new raw_data
54
+ end
55
+
56
+ =end
57
+
58
+ def self.use_or_allocate rid
59
+ cached_obj = get_rid( rid )
60
+ if cached_obj.present?
61
+ cached_obj
62
+ else
63
+ yield
64
+ end
65
+ end
66
+
67
+
68
+ def self._to_partial_path #:nodoc:
69
+ @_to_partial_path ||= begin
70
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
71
+ collection = ActiveSupport::Inflector.tableize(name)
72
+ "#{collection}/#{element}".freeze
73
+ end
74
+ end
75
+ ## to prevent errors when calling to_a
76
+ def to_ary # :nodoc:
77
+ attributes.to_a
78
+ end
79
+
80
+ def document # :nodoc:
81
+ @d
82
+ end
83
+
84
+ =begin
85
+ Deletes the database class and removes the ruby-class
86
+ =end
87
+ def self.delete_class what= :all
88
+ orientdb.delete_class( self ) if what == :all # remove the database-class
89
+ ## namespace is defined in config/boot
90
+ ns = namespace.to_s == 'Object' ? "" : namespace.to_s
91
+ ns_found = -> ( a_class ) do
92
+ to_compare = a_class.to_s.split(':')
93
+ if ns == "" && to_compare.size == 1
94
+ true
95
+ elsif to_compare.first == ns
96
+ true
97
+ else
98
+ false
99
+ end
100
+ end
101
+ self.allocated_classes.delete_if{|x,y| x == self.ref_name && ns_found[y]} if allocated_classes.is_a?(Hash)
102
+ namespace.send(:remove_const, naming_convention.to_sym) if namespace &.send( :const_defined?, naming_convention)
103
+ end
104
+
105
+ # provides an unique accessor on the Class
106
+ # works with a class-variable, its unique through all Subclasses
107
+ mattr_accessor :orientdb # points to the instance of the REST-DB-Client used for Administration
108
+ # i.e. creation and deleting of classes and databases
109
+ mattr_accessor :db # points to the instance of the Client used for Database-Queries
110
+ mattr_accessor :api
111
+ # mattr_accessor :logger ... already inherented from ::Base
112
+ mattr_accessor :namespace # Namespace in which Model records are initialized, a constant ( defined in config.yml )
113
+ mattr_accessor :model_dir # path to model-files
114
+ mattr_accessor :keep_models_without_file
115
+ mattr_accessor :allocated_classes
116
+
117
+ # mattr_accessor :ref_name
118
+ # Used to read the metadata
119
+ attr_reader :metadata
120
+
121
+ # provides an accessor at class level
122
+ # (unique on all instances)
123
+ class << self
124
+ attr_accessor :ref_name
125
+ attr_accessor :abstract
126
+
127
+ def to_or
128
+ ref_name.to_or
129
+ end
130
+
131
+
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,607 @@
1
+ module ModelClass
2
+ require 'stringio'
3
+ include OrientSupport::Support
4
+
5
+
6
+ ########### CLASS FUNCTIONS ######### SELF ####
7
+
8
+
9
+ ######## INITIALIZE A RECORD FROM A CLASS ########
10
+
11
+
12
+ =begin
13
+ NamingConvention provides a translation from database-names to class-names.
14
+
15
+ It can be overwritten to provide different conventions for different classes, eg. Vertexes or edges
16
+ and to introduce distinct naming-conventions in differrent namespaces
17
+
18
+ To overwrite use
19
+ class Model # < ActiveOrient::Model[:: ...]
20
+ def self.naming_convention
21
+ ( conversion code )
22
+ end
23
+ end
24
+ =end
25
+ def naming_convention name=nil
26
+ nc = name.present?? name.to_s : ref_name
27
+ if namespace_prefix.present?
28
+ nc.split(namespace_prefix).last.camelize
29
+ else
30
+ nc.camelize
31
+ end
32
+ rescue
33
+ nil
34
+ end
35
+
36
+
37
+ =begin
38
+ Set the namespace_prefix for database-classes.
39
+
40
+ If a namespace is set by
41
+ ActiveOrient::Init.define_namespace { ModuleName }
42
+ ActiveOrient translates this to
43
+ ModuleName::CamelizedClassName
44
+ The database-class becomes
45
+ modulename_class_name
46
+
47
+ If the namespace is set to a class (Object, ActiveOrient::Model ) namespace_prefix returns an empty string.
48
+
49
+ Override to change its behavior
50
+ =end
51
+ def namespace_prefix
52
+ namespace.is_a?(Class )? '' : namespace.to_s.downcase+'_'
53
+ end
54
+ =begin
55
+ orientdb_class is used to refer a ActiveOrient:Model-Object providing its name
56
+
57
+ Parameter: name: string or symbol
58
+ =end
59
+
60
+ def orientdb_class name:, superclass: nil # :nodoc: # public method: autoload_class
61
+
62
+ ActiveOrient.database_classes[name].presence || ActiveOrient::Model
63
+ rescue NoMethodError => e
64
+ logger.error { "Error in orientdb_class: is ActiveOrient.database_classes initialized ? \n\n\n" }
65
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
66
+ Kernel.exit
67
+ end
68
+
69
+
70
+ =begin
71
+ setter method to initialise a dummy ActiveOrient::Model class to enable multi-level
72
+ access to links and linklists
73
+ =end
74
+
75
+ def link_list *property
76
+ property.each do |p|
77
+
78
+ the_dummy_class = orientdb.allocate_class_in_ruby("dummy_"+p.to_s)
79
+ the_dummy_class.ref_name = ref_name + "." + p.to_s
80
+ singleton_class.send :define_method, p do
81
+ the_dummy_class
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ =begin
88
+ requires the file specified in the model-dir
89
+
90
+ In fact, the model-files are loaded instead of required.
91
+ Thus, even after recreation of a class (Class.delete_class, ORD.create_class classname)
92
+ custom methods declared in the model files are present.
93
+
94
+ Required modelfiles are gone, if the class is destroyed.
95
+
96
+ The directory specified is expanded by the namespace. The parameter itself is the base-dir.
97
+
98
+ Example:
99
+ Namespace: HC
100
+ model_dir : 'lib/model'
101
+ searched directory: 'lib/model/hc'
102
+
103
+ =end
104
+ def require_model_file the_directory = nil
105
+ logger.progname = 'ModelClass#RequireModelFile'
106
+ the_directory = Pathname( the_directory.presence || ActiveOrient::Model.model_dir ) rescue nil # the_directory is a Pathname
107
+ return nil if the_directory.nil?
108
+ if File.exists?( the_directory )
109
+ model= self.to_s.underscore + ".rb"
110
+ filename = the_directory + model
111
+ if File.exists?(filename )
112
+ if load filename
113
+ logger.info{ "#{filename} sucessfully loaded" }
114
+ self #return_value
115
+ else
116
+ logger.error{ "#{filename} load error" }
117
+ nil #return_value
118
+ end
119
+ else
120
+ logger.info{ "model-file not present: #{filename}" }
121
+ nil #return_value
122
+ end
123
+ else
124
+ logger.info{ "Directory #{ the_directory } not present " }
125
+ nil #return_value
126
+ end
127
+ rescue TypeError => e
128
+ puts "TypeError: #{e.message}"
129
+ puts "Working on #{self.to_s} -> #{self.superclass}"
130
+ puts "Class_hierarchy: #{orientdb.class_hierarchy.inspect}."
131
+ print e.backtrace.join("\n")
132
+ raise
133
+ #
134
+ end
135
+
136
+ ########## CREATE ############
137
+
138
+ =begin
139
+ Universal method to create a new record.
140
+ It's overloaded to create specific kinds, eg. edge and vertex and is called only for abstract classes
141
+
142
+ Example:
143
+ ORD.create_class :test
144
+ Test.create string_attribute: 'a string', symbol_attribute: :a_symbol, array_attribute: [34,45,67]
145
+ Test.create link_attribute: Test.create( :a_new_attribute => 'new' )
146
+
147
+ =end
148
+ def create **attributes
149
+ attributes.merge :created_at => DateTime.new
150
+ result = db.create_record self, attributes: attributes
151
+ if result.nil
152
+ logger.error('Model::Class'){ "Table #{refname}: create failed: #{attributes.inspect}" }
153
+ elsif block_given?
154
+ yield result
155
+ else
156
+ result # return value
157
+ end
158
+ end
159
+
160
+ =begin
161
+ Creates or updates a record.
162
+ Parameter:
163
+ - set: A hash of attributes to insert or update unconditionally
164
+ - where: A string or hash as condition which should return just one record.
165
+
166
+ The where-part should be covered with an unique-index.
167
+
168
+ returns the affected record
169
+ =end
170
+ def upsert set: nil, where:
171
+ set = where if set.nil?
172
+ db.upsert self, set: set, where: where
173
+ end
174
+ =begin
175
+ Sets a value to certain attributes, overwrites existing entries, creates new attributes if nessesary
176
+
177
+ IB::Account.update_all connected: false
178
+ IB::Account.update_all where: "account containsText 'F'", set:{ connected: false }
179
+
180
+ **note: By calling UpdateAll, all records of the Class previously stored in the rid-cache are removed from the cache. Thus autoload gets the updated records.
181
+ =end
182
+
183
+ def update_all where: {} , set: {}, **arg
184
+ if where.empty?
185
+ set.merge! arg
186
+ end
187
+ db.update_records self, set: set, where: where
188
+
189
+ end
190
+ #
191
+ # removes a property from the collection (where given) or the entire class
192
+ def remove attribute, where:{}
193
+ db.update_records self, remove: attribute, where: where
194
+ end
195
+
196
+ =begin
197
+ Create a Property in the Schema of the Class and optionaly create an automatic index
198
+
199
+ Examples:
200
+
201
+ create_property :customer_id, type: integer, index: :unique
202
+ create_property( :name, type: :string ) { :unique }
203
+ create_property :in, type: :link, linked_class: V (used by edges)
204
+
205
+ :call-seq: create_property(field (required),
206
+ type: :a_supported_type',
207
+ linked_class: nil
208
+
209
+ supported types:
210
+ :bool :double :datetime :float :decimal
211
+ :embedded_list = :list :embedded_map = :map :embedded_set = :set
212
+ :int :integer :link_list :link_map :link_set
213
+
214
+ If `:list`, `:map`, `:set`, `:link`, `:link_list`, `:link_map` or `:link_set` is specified
215
+ a `linked_class:` parameter can be specified. Argument is the OrientDB-Class-Constant
216
+ =end
217
+ def create_property field, type: :integer, index: nil, **args
218
+ arguments = args.values.map do |y|
219
+ if y.is_a?(Class) && ActiveOrient.database_classes.values.include?(y)
220
+ y.ref_name
221
+ elsif ActiveOrient.database_classes.keys.include?(y.to_s)
222
+ y
223
+ else
224
+ puts ActiveOrient.database_classes.inspect
225
+ puts "YY : #{y.to_s} #{y.class}"
226
+ raise ArgumentError , "database class #{y.to_s} not allocated"
227
+ end
228
+ end.compact.join(',')
229
+
230
+ supported_types = {
231
+ :bool => "BOOLEAN",
232
+ :double => "BYTE",
233
+ :datetime => "DATE",
234
+ :float => "FLOAT",
235
+ :decimal => "DECIMAL",
236
+ :embedded_list => "EMBEDDEDLIST",
237
+ :list => "EMBEDDEDLIST",
238
+ :embedded_map => "EMBEDDEDMAP",
239
+ :map => "EMBEDDEDMAP",
240
+ :embedded_set => "EMBEDDEDSET",
241
+ :set => "EMBEDDEDSET",
242
+ :string => "STRING",
243
+ :int => "INTEGER",
244
+ :integer => "INTEGER",
245
+ :link => "LINK",
246
+ :link_list => "LINKLIST",
247
+ :link_map => "LINKMAP",
248
+ :link_set => "LINKSET",
249
+ }
250
+
251
+ ## if the »type« argument is a string, it is used unchanged
252
+ type = supported_types[type] if type.is_a?(Symbol)
253
+
254
+ raise ArgumentError , "unsupported type" if type.nil?
255
+ s= " CREATE PROPERTY #{ref_name}.#{field} #{type} #{arguments}"
256
+ puts s
257
+ db.execute { s }
258
+
259
+ i = block_given? ? yield : index
260
+ ## supported format of block: index: { name: 'something' , on: :automatic, type: :unique }
261
+ ## or { name: 'something' , on: :automatic, type: :unique } #
262
+ ## or { some_name: :unique } # manual index
263
+ ## or { :unique } # automatic index
264
+ if i.is_a? Hash
265
+ att= i.key( :index ) ? i.values.first : i
266
+ name, on, type = if att.size == 1 && att[:type].nil?
267
+ [att.keys.first, field, att.values.first ]
268
+ else
269
+ [ att[:name] || field , att[:on] || field , att[:type] || :unique ]
270
+ end
271
+ create_index( name , on: on, type: type)
272
+ elsif i.is_a?(Symbol) || i.is_a?(String)
273
+ create_index field, type: i
274
+ end
275
+
276
+ # orientdb.create_property self, field, **keyword_arguments, &b
277
+ end
278
+
279
+ # Create more Properties in the Schema of the Class
280
+
281
+ def create_properties argument_hash, &b
282
+ orientdb.create_properties self, argument_hash, &b
283
+ end
284
+
285
+
286
+ # Add an Index
287
+ def create_index name, **attributes
288
+ orientdb.create_index self, name: name, **attributes
289
+ end
290
+
291
+ # list all Indexes
292
+ def indexes
293
+ properties[:indexes]
294
+ end
295
+ ########## GET ###############
296
+
297
+ def classname # :nodoc: #
298
+ ref_name
299
+ end
300
+
301
+ # get elements by rid
302
+
303
+ def get rid
304
+ if @excluded.blank?
305
+ db.get_record(rid)
306
+ else
307
+ db.execute{ "select expand( @this.exclude( #{@excluded.map(&:to_or).join(",")})) from #{rid} "}
308
+ end
309
+ end
310
+
311
+ # get all the elements of the class
312
+
313
+ def all
314
+ db.get_records from: self
315
+ end
316
+
317
+ # get the first element of the class
318
+
319
+ def first where: {}
320
+ db.get_records(from: self, where: where, limit: 1).pop
321
+ end
322
+
323
+ # get the last element of the class
324
+
325
+ def last where: {}
326
+ db.get_records(from: self, where: where, order: {"@rid" => 'desc'}, limit: 1).pop
327
+ end
328
+ # Used to count of the elements in the class
329
+
330
+ def count **args
331
+ orientdb.count from: self, **args
332
+ end
333
+
334
+ # Get the properties of the class
335
+
336
+ def properties
337
+ object = orientdb.get_class_properties self
338
+ #HashWithIndifferentAccess.new :properties => object['properties'], :indexes => object['indexes']
339
+ {:properties => object['properties'], :indexes => object['indexes']}
340
+ end
341
+ alias get_class_properties properties
342
+
343
+ # Print the properties of the class
344
+
345
+ def print_properties
346
+ orientdb.print_class_properties self
347
+ end
348
+
349
+ =begin
350
+ »GetRecords« uses the REST-Interface to query the database. The alternative »QueryDatabase« submits
351
+ the query via Batch.
352
+
353
+ Both methods rely on OrientSupport::OrientQuery and its capacity to support complex query-builds.
354
+ The method requires a hash of arguments. The following keys are supported:
355
+
356
+ *projection:*
357
+
358
+ SQL-Queries use »select« to specify a projection (ie. `select sum(a), b+5 as z from class where ...`)
359
+
360
+ In ruby »select« is a method of enumeration. To specify anything etween »select« and »from« in the query-string
361
+ we use »projection«, which acceps different arguments
362
+
363
+ projection: a_string --> inserts the sting as it appears
364
+ projection: an OrientSupport::OrientQuery-Object --> performs a sub-query and uses the result for further querying though the given parameters.
365
+ projection: [a, b, c] --> "a, b, c" (inserts a comma-separated list)
366
+ projection: {a: b, "sum(x)" => f} --> "a as b, sum(x) as f" (renames properties and uses functions)
367
+
368
+ *distinct:*
369
+
370
+ Constructs a query like »select distinct(property) [as property] from ...«
371
+
372
+ distinct: :property --> the result is mapped to the property »distinct«.
373
+ distinct: [:property] --> the result replaces the property
374
+ distinct: {property: :some_name} --> the result is mapped to ModelInstance.some_name
375
+
376
+ *order:*
377
+
378
+ Sorts the result-set. If new properties were introduced via select:, distinct: etc. Sorting takes place on these properties
379
+
380
+ order: :property {property: asc, property: desc}[property, property, .. ](orderdirection is 'asc')
381
+
382
+
383
+ Further supported Parameter:
384
+
385
+ group_by
386
+ skip
387
+ limit
388
+ unwind
389
+
390
+ see orientdb- documentation (https://orientdb.com/docs/last/SQL-Query.html)
391
+
392
+ *query:*
393
+
394
+ Instead of providing the parameter to »get_records«, a OrientSupport::OrientQuery can build and
395
+ tested prior to the method-call. The OrientQuery-Object is then provided with the query-parameter. I.e.
396
+
397
+ q = OrientSupport::OrientQuery.new
398
+ ORD.create_class :test_model
399
+ q.from TestModel
400
+ q.where {name: 'Thomas'}
401
+ count = TestModel.count query: q
402
+ q.limit 10
403
+ 0.step(count,10) do |x|
404
+ q.skip = x
405
+ puts TestModel.get_documents(query: q).map{|x| x.adress }.join('\t')
406
+ end
407
+ prints a Table with 10 columns.
408
+ =end
409
+
410
+ def get_records **args
411
+ db.get_records(from: self, **args){self}
412
+ end
413
+ alias get_documents get_records
414
+
415
+
416
+ def custom_where search_string
417
+ q = OrientSupport::OrientQuery.new from: self, where: search_string
418
+ #puts q.compose
419
+ query_database q
420
+ end
421
+ =begin
422
+ Performs a query on the Class and returns an Array of ActiveOrient:Model-Records.
423
+
424
+ Example:
425
+ Log.where priority: 'high'
426
+ --> submited database-request: query/hc_database/sql/select from Log where priority = 'high'/-1
427
+ => [ #<Log:0x0000000480f7d8 @metadata={ ... }, ...
428
+
429
+ Multible arguments are joined via "and" eg
430
+ Aktie.where symbol: 'TSL, exchange: 'ASX'
431
+ ---> select from aktie where symbol = 'TLS' and exchange = 'ASX'
432
+
433
+
434
+ Where performs a »match-Query« that returns only links to the queries records.
435
+ These are autoloaded (and reused from the cache). If changed database-records should be obtained,
436
+ custom_query should be used. It performs a "select form class where ... " query which returns records
437
+ instead of links.
438
+
439
+ Property.custom_where( "'Hamburg' in exchanges.label")
440
+
441
+ =end
442
+
443
+ def where *attributes
444
+ query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }
445
+ query.match_statements[0].where = attributes unless attributes.empty?
446
+ # the block contains a result-record :
447
+ #<ActiveOrient::Model:0x0000000003972e00
448
+ # @metadata={:type=>"d", :class=>nil, :version=>0, :fieldTypes=>"test_models=x"}, @d=nil,
449
+ # @attributes={:test_models=>"#29:3", :created_at=>Thu, 28 Mar 2019 10:43:51 +0000}>]
450
+ # ^...........° -> classname.pluralize
451
+ query_database( query, set_from: false){| record | record.is_a?(ActiveOrient::Model) ? record : record.send( self.classnamepluralize.to_sym ) }
452
+ end
453
+ =begin
454
+ Performs a Match-Query
455
+
456
+ The Query starts at the given ActiveOrient::Model-Class. The where-cause narrows the sample to certain
457
+ records. In the simplest version this can be returned:
458
+
459
+ Industry.match where:{ name: "Communications" }
460
+ => #<ActiveOrient::Model::Query:0x00000004309608 @metadata={"type"=>"d", "class"=>nil, "version"=>0, "fieldTypes"=>"Industries=x"}, @attributes={"Industries"=>"#21:1", (...)}>
461
+
462
+ 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.
463
+
464
+ I.match( where: { name: 'Communications' }).first.Industries
465
+
466
+ is the same then
467
+ Industry.where name: "Communications"
468
+
469
+
470
+ The Match-Query uses this result-set as start for subsequent queries on connected records.
471
+ These connections are defined in the Block
472
+
473
+ var = Industry.match do | query |
474
+ query.connect :in, count: 2, as: 'Subcategories'
475
+ puts query.to_s # print the query before sending it to the database
476
+ query # important: block has to return the query
477
+ end
478
+ => MATCH {class: Industry, as: Industries} <-- {} <-- { as: Subcategories } RETURN Industries, Subcategories
479
+
480
+ The result-set has two attributes: Industries and Subcategories, pointing to the filtered datasets.
481
+
482
+ By using subsequent »connect« and »statement« method-calls even complex Match-Queries can be clearly constructed.
483
+
484
+ =end
485
+
486
+ def match where: {}
487
+ query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }
488
+ query.match_statements[0].where = where unless where.empty?
489
+ if block_given?
490
+ query_database yield(query), set_from: false
491
+ else
492
+ send :where, where
493
+ end
494
+
495
+ end
496
+
497
+
498
+ =begin
499
+ QueryDatabase sends the Query directly to the database.
500
+
501
+ The result is not nessessary an Object of the Class.
502
+
503
+ The result can be modified further by passing a block.
504
+ This is helpful, if a match-statement is used and the records should be autoloaded:
505
+
506
+ result = query_database(query, set_from: false){| record | record[ self.classname.pluralize ] }
507
+
508
+ This autoloads (fetches from the cache/ or database) the attribute self.classname.pluralize (taken from method: where )
509
+
510
+
511
+ query_database is used on model-level and submits
512
+ select (...) from class
513
+
514
+ #query performs queries on the instance-level and submits
515
+ select (...) from #{a}:{b}
516
+
517
+ =end
518
+
519
+ def query_database query, set_from: true
520
+ query.from self if set_from && query.is_a?(OrientSupport::OrientQuery) && query.from.nil?
521
+ sql_cmd = -> (command) {{ type: "cmd", language: "sql", command: command }}
522
+ result = db.execute do
523
+ query.to_s # sql_cmd[query.to_s]
524
+ end
525
+ if block_given?
526
+ result.is_a?(Array)? result.map{|x| yield x } : yield(result)
527
+ else
528
+ result
529
+ end
530
+ if result.is_a? Array
531
+ OrientSupport::Array.new work_on: self, work_with: result
532
+ else
533
+ result
534
+ end # return value
535
+ end
536
+
537
+ ########### DELETE ###############
538
+
539
+ # Delete a property from the class
540
+
541
+ def delete_property field
542
+ orientdb.delete_property self, field
543
+ end
544
+
545
+ # Delete record(s) specified by their rid's
546
+
547
+ def delete_record *rid
548
+ db.delete_record rid
549
+ end
550
+ alias delete_document delete_record
551
+
552
+ # Query the database and delete the records of the resultset
553
+
554
+ def delete_records where: {}
555
+ orientdb.delete_records self, where: where
556
+ end
557
+ alias delete_documents delete_records
558
+
559
+
560
+
561
+ ##################### EXPERIMENT #################
562
+
563
+ =begin
564
+ Suppose that you created a graph where vertexes month is connected with
565
+ the vertexes day by the edge TIMEOF.
566
+ Suppose we want to find all the days in the first month and in the third month..
567
+
568
+ Usually we can do in the following way.
569
+
570
+ ORD.create_class :month
571
+ (.. put some records into Month ... )
572
+ firstmonth = Month.first
573
+ thirdmonth = Month.all[2]
574
+ days_firstmonth = firstmonth.out_TIMEOF.map{|x| x.in}
575
+ days_thirdmonth = thirdmonth.out_TIMEOF.map{|x| x.in}
576
+
577
+ However we can obtain the same result with the following command
578
+
579
+ Month.add_edge_link name: "days", direction: "out", edge: TIME_OF
580
+ firstmonth = month.first
581
+ thirdmonth = month.all[2]
582
+ days_firstmonth = firstmonth.days
583
+ days_thirdmonth = thirdmonth.days
584
+
585
+ To get their value you can do:
586
+ thirdmonth.days.value
587
+ =end
588
+
589
+
590
+ def add_edge_link name:, direction: :out, edge:
591
+ dir = direction.to_s == "out" ? :out : :in
592
+ define_method(name.to_sym) do
593
+ return self["#{dir}_#{edge.classname}"].map{|x| x["in"]}
594
+ end
595
+ end
596
+
597
+ =begin
598
+ See http://orientdb.com/docs/2.1/SQL-Alter-Property.html
599
+ =end
600
+
601
+ def alter_property property:, attribute: "DEFAULT", alteration: # :nodoc:
602
+ orientdb.alter_property self, property: property, attribute: attribute, alteration: alteration
603
+ end
604
+
605
+
606
+
607
+ end