active-orient 0.4 → 0.80

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 (61) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.graphs.txt.swp +0 -0
  4. data/Gemfile +9 -5
  5. data/Guardfile +12 -4
  6. data/README.md +70 -281
  7. data/VERSION +1 -1
  8. data/active-orient.gemspec +9 -7
  9. data/bin/active-orient-0.6.gem +0 -0
  10. data/bin/active-orient-console +97 -0
  11. data/changelog.md +60 -0
  12. data/config/boot.rb +70 -17
  13. data/config/config.yml +10 -0
  14. data/config/connect.yml +11 -6
  15. data/examples/books.rb +154 -65
  16. data/examples/streets.rb +89 -85
  17. data/graphs.txt +70 -0
  18. data/lib/active-orient.rb +78 -6
  19. data/lib/base.rb +266 -168
  20. data/lib/base_properties.rb +76 -65
  21. data/lib/class_utils.rb +187 -0
  22. data/lib/database_utils.rb +99 -0
  23. data/lib/init.rb +80 -0
  24. data/lib/java-api.rb +442 -0
  25. data/lib/jdbc.rb +211 -0
  26. data/lib/model/custom.rb +29 -0
  27. data/lib/model/e.rb +6 -0
  28. data/lib/model/edge.rb +114 -0
  29. data/lib/model/model.rb +134 -0
  30. data/lib/model/the_class.rb +657 -0
  31. data/lib/model/the_record.rb +313 -0
  32. data/lib/model/vertex.rb +371 -0
  33. data/lib/orientdb_private.rb +48 -0
  34. data/lib/other.rb +423 -0
  35. data/lib/railtie.rb +68 -0
  36. data/lib/rest/change.rb +150 -0
  37. data/lib/rest/create.rb +287 -0
  38. data/lib/rest/delete.rb +150 -0
  39. data/lib/rest/operations.rb +222 -0
  40. data/lib/rest/read.rb +189 -0
  41. data/lib/rest/rest.rb +120 -0
  42. data/lib/rest_disabled.rb +24 -0
  43. data/lib/support/conversions.rb +42 -0
  44. data/lib/support/default_formatter.rb +7 -0
  45. data/lib/support/errors.rb +41 -0
  46. data/lib/support/logging.rb +38 -0
  47. data/lib/support/orient.rb +305 -0
  48. data/lib/support/orientquery.rb +647 -0
  49. data/lib/support/query.rb +92 -0
  50. data/rails.md +154 -0
  51. data/rails/activeorient.rb +32 -0
  52. data/rails/config.yml +10 -0
  53. data/rails/connect.yml +17 -0
  54. metadata +89 -30
  55. data/lib/model.rb +0 -461
  56. data/lib/orient.rb +0 -98
  57. data/lib/query.rb +0 -88
  58. data/lib/rest.rb +0 -1036
  59. data/lib/support.rb +0 -347
  60. data/test.rb +0 -4
  61. data/usecase.md +0 -91
@@ -0,0 +1,120 @@
1
+ require_relative "read.rb" # manage get
2
+ require_relative "create.rb" # manage create
3
+ require_relative "change.rb" # manage update
4
+ require_relative "operations.rb" # manage count, functions and execute
5
+ require_relative "delete.rb" # manage delete
6
+ require_relative "../support/logging"
7
+ #require 'cgi'
8
+ require 'rest-client'
9
+ require 'pond'
10
+
11
+ module ActiveOrient
12
+
13
+ =begin
14
+ OrientDB points to an OrientDB-Database.
15
+ The communication is based on the OrientDB-REST-API.
16
+
17
+ Its usually initialised through ActiveOrient::Init.connect
18
+
19
+ =end
20
+
21
+ class OrientDB
22
+ include OrientSupport::Support
23
+ include OrientSupport::Logging
24
+ include OrientDbPrivate
25
+ include DatabaseUtils
26
+ include ClassUtils
27
+ include RestRead
28
+ include RestCreate
29
+ include RestChange
30
+ include RestOperations
31
+ include RestDelete
32
+
33
+
34
+ #### INITIALIZATION ####
35
+
36
+ =begin
37
+ OrientDB is conventionally initialized.
38
+
39
+
40
+ The first call initialises database-name and -classes, server-adress and user-credentials.
41
+
42
+ Subsequent initialisations are made to initialise namespaced database classes, ie.
43
+
44
+ ORD = ActiveOrient.init.connect database: 'temp'
45
+ server: 'localhost',
46
+ port: 2480,
47
+ user: root,
48
+ password: root
49
+ module HC; end
50
+ ActiveOrient::Init.define_namespace { HC }
51
+ ActiveOrient::OrientDB.new preallocate: true
52
+
53
+
54
+
55
+
56
+
57
+ =end
58
+
59
+ def initialize database: nil, preallocate: true, model_dir: nil, **defaults
60
+ ActiveOrient.database ||= database || 'temp'
61
+ ActiveOrient.database_classes ||= Hash.new
62
+
63
+ ActiveOrient.default_server ||= { :server => defaults[:server] || 'localhost' ,
64
+ :port => defaults[:port] ||= 2480,
65
+ :user => defaults[:user].to_s ,
66
+ :password => defaults[:password].to_s }
67
+ # setup connection pool
68
+ # database-settings: client.channel.maxPool = 100
69
+ ActiveOrient.db_pool ||= Pond.new( :maximum_size => 25, :timeout => 50) { get_resource }
70
+ # ActiveOrient.db_pool.collection = :stack
71
+ connect()
72
+ database_classes # initialize @classes-array and ActiveOrient.database_classes
73
+ ActiveOrient::Base.logger = logger
74
+ ActiveOrient::Model.orientdb = self
75
+ ActiveOrient::Model.db = self
76
+ ActiveOrient::Model.keep_models_without_file ||= nil
77
+ preallocate_classes( model_dir ) if preallocate
78
+ Thread.abort_on_exception = true
79
+ end
80
+
81
+ # thread safe method to allocate a resource
82
+ def get_resource
83
+ logger.debug {"ALLOCATING NEW RESOURCE --> #{ ActiveOrient.db_pool.size }" }
84
+ login = [ActiveOrient.default_server[:user] , ActiveOrient.default_server[:password]]
85
+ server_adress = "http://#{ActiveOrient.default_server[:server]}:#{ActiveOrient.default_server[:port]}"
86
+ RestClient::Resource.new(server_adress, *login)
87
+ end
88
+
89
+
90
+
91
+ # Used to connect to the database
92
+
93
+ def connect
94
+ first_tentative = true
95
+ begin
96
+ database = ActiveOrient.database
97
+ logger.progname = 'OrientDB#Connect'
98
+ r = ActiveOrient.db_pool.checkout do | conn |
99
+ r = conn["/connect/#{database}"].get
100
+ end
101
+ if r.code == 204
102
+ logger.info{"Connected to database #{database}"}
103
+ true
104
+ else
105
+ logger.error{"Connection to database #{database} could NOT be established"}
106
+ nil
107
+ end
108
+ rescue RestClient::Unauthorized => e
109
+ if first_tentative
110
+ logger.info{"Database #{database} NOT present --> creating"}
111
+ first_tentative = false
112
+ create_database database: database
113
+ retry
114
+ else
115
+ Kernel.exit
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,24 @@
1
+ =begin #nodoc#
2
+ If properties are allocated on class-level, they can be preinitialized using
3
+ this method.
4
+ This is disabled for now, because it does not seem nessesary
5
+ =end
6
+
7
+ def preallocate_class_properties o_class # :nodoc:
8
+ p= get_class_properties( o_class )['properties']
9
+ unless p.nil? || p.blank?
10
+ predefined_attributes = p.map do | property |
11
+ [ property['name'] ,
12
+ case property['type']
13
+ when 'LINKMAP'
14
+ Array.new
15
+ when 'STRING'
16
+ ''
17
+ else
18
+ nil
19
+ end ]
20
+ end.to_h
21
+ else
22
+ {}
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ =begin
2
+ Rails-specific stuff
3
+
4
+ Mimics ActiveModell::conversions
5
+ =end
6
+ module Conversions
7
+
8
+
9
+ =begin
10
+ Returns an Array of all key attributes if any is set, regardless if the object is persisted or not. Returns nil if there are no key attributes.
11
+ =end
12
+ def to_key
13
+ key = respond_to?(:rid) && rid
14
+ key ? [key] : nil
15
+ end
16
+
17
+ # Returns a +string+ representing the object's key suitable for use in URLs,
18
+ # # or +nil+ if <tt>persisted?</tt> is +false+.
19
+ def to_param
20
+ (persisted? && key = to_key) ? key.join('-') : nil
21
+ end
22
+
23
+
24
+ # Returns a +string+ identifying the path associated with the object.
25
+ # # ActionPack uses this to find a suitable partial to represent the object.
26
+ def to_partial_path
27
+ self.class._to_partial_path
28
+ end
29
+
30
+ # module ClassMethods #:nodoc:
31
+ # Provide a class level cache for #to_partial_path. This is an
32
+ # internal method and should not be accessed directly.
33
+
34
+ # def self._to_partial_path #:nodoc:
35
+ # @_to_partial_path ||= begin
36
+ # element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
37
+ # collection = ActiveSupport::Inflector.tableize(name)
38
+ # "#{collection}/#{element}".freeze
39
+ # end
40
+ # end
41
+ #end
42
+ end
@@ -0,0 +1,7 @@
1
+ module OrientSupport
2
+ class DefaultFormatter < Logger::Formatter
3
+ def self.call(severity, time, program_name, msg)
4
+ "#{time.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveOrient
2
+ module Error
3
+ # Error handling
4
+ class Error < RuntimeError
5
+ end
6
+
7
+ class ArgumentError < ArgumentError
8
+ end
9
+
10
+ class SymbolError < ArgumentError
11
+ end
12
+
13
+ class LoadError < LoadError
14
+ end
15
+
16
+ class ServerError < RuntimeError
17
+ end
18
+ end # module IB
19
+ end
20
+ # Patching Object with universally accessible top level error method.
21
+ # The method is used throughout the lib instead of plainly raising exceptions.
22
+ # This allows lib user to easily inject user-specific error handling into the lib
23
+ # by just replacing Object#error method.
24
+ def error message, type=:standard, backtrace=nil
25
+ e = case type
26
+ when :standard
27
+ ActiveOrientOrient::Error.new message
28
+ when :args
29
+ ActiveOrient::ArgumentError.new message
30
+ when :symbol
31
+ ActiveOrient::SymbolError.new message
32
+ when :load
33
+ AcitveOrient::LoadError.new message
34
+ when :server
35
+ ActiveOrient::Error::ServerError.new message
36
+ end
37
+ e.set_backtrace(backtrace) if backtrace
38
+ raise e
39
+ end
40
+
41
+ # resued from https://github.com/ib-ruby/ib-ruby
@@ -0,0 +1,38 @@
1
+ #require_relative 'default_formatter'
2
+ module OrientSupport
3
+ module Logging
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.send :define_method, :logger do
7
+ base.logger
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def logger
13
+ @logger
14
+ end
15
+
16
+ def logger=(logger)
17
+ @logger = logger
18
+ end
19
+
20
+ def configure_logger(log= nil)
21
+ if log
22
+ @logger = log
23
+ else
24
+ @logger = Logger.new(STDOUT)
25
+ @logger.level = Logger::INFO
26
+ @logger.formatter = DefaultFormatter
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ class DefaultFormatter < Logger::Formatter
33
+ def self.call(severity, time, program_name, msg)
34
+ "#{time.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
35
+ end
36
+ end
37
+ end
38
+ # source: https://github.com/jondot/sneakers/blob/master/lib/sneakers/concerns/logging.rb
@@ -0,0 +1,305 @@
1
+ module OrientSupport
2
+
3
+ # This Module fences specialized Ruby objects
4
+
5
+ # The Array _knows_ its database-class. This enables database-transactions outside the scope
6
+ # of ActiveOrient
7
+ #
8
+ # The Database-Class is available through Array#record
9
+ #
10
+ # *caution:*
11
+ # Don't mix ActiveOrient::Array's with conventional ones
12
+ # > t= G21.first
13
+ # > t.ll
14
+ # => ["test", "test_2", 5, 8, 7988, "uzg"]
15
+ # > t.ll = [9,6,7] # This is an assignment of an Array to the variable »ll»
16
+ # # It does NOT call ActiveOrient::Array#=[].
17
+ # => [9, 6, 7] # Instead an Array is assigned to the variable »ll»
18
+ #
19
+ # it is only updated localy, as shown if we reload the document
20
+ # > t= G21.first.attributes
21
+ # => {:ll=>["test", "test_2", 5, 8, 7988, "uzg"]}
22
+ #
23
+ # Thus its imperativ to safe the changes made.
24
+
25
+
26
+ class Array < Array
27
+ include OrientSupport::Support
28
+
29
+ =begin
30
+ During initialisation the model-instance to work on is stored in @orient.
31
+
32
+ The keyword_parameter »work_on« holds the record to work on.
33
+ The second argument holds the array to work with
34
+
35
+ If instead of a model-instance the model-class is provided, a new model-instance is created and returned
36
+ Its up to the caller to save the new instance in the database
37
+
38
+ Further a list of array-elements is expected, which are forwarded (as Array) to Array
39
+
40
+ Its used to initialize Objects comming from the database (i.e. /lib/base.rb)
41
+
42
+ elsif iv.is_a? Array
43
+ OrientSupport::Array.new( work_on: self, work_with: iv.from_orient){ key.to_sym }
44
+
45
+ =end
46
+
47
+ def initialize( work_on:, work_with: )
48
+ @orient = work_on.class == Class ? work_on.new : work_on
49
+ super work_with
50
+ begin
51
+ @name = block_given? ? yield : @orient.attributes.key(self)
52
+ rescue TypeError => e # not defined
53
+ ActiveOrient::Base.logger.debug{ "--------------------Type Error ----------------------------------" }
54
+ ActiveOrient::Base.logger.debug("OrientSupport::Array"){ "Attributes #{@orient.attributes.inspect}" }
55
+ ActiveOrient::Base.logger.debug("OrientSupport::Array"){ e.inspect
56
+ ActiveOrient::Base.logger.debug{ "indicates a try to access a non existing array element" }}
57
+ nil
58
+ rescue NameError =>e
59
+ ActiveOrient::Base.logger.debug{ "--------------------Name Error ------------" }
60
+ ActiveOrient::Base.logger.debug ("OrientSupport::Array"){ e.inspect }
61
+ #ActiveOrient::Base.logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
62
+ ActiveOrient::Base.logger.debug{ "due to a bug in ActiveSupport DateTime Calculations" }
63
+ # we just ignore the error
64
+ end
65
+ end
66
+ def as_json o=nil
67
+ map{|x| x.rid? ? x.rid : x }
68
+ end
69
+
70
+ def record
71
+ @orient
72
+ end
73
+
74
+ def to_human
75
+ map &:to_human
76
+ end
77
+ =begin
78
+
79
+ Appends the arguments to the Array.
80
+
81
+ Returns the modified database-document (not the array !!)
82
+ =end
83
+ def append *arg
84
+
85
+ @orient.update { "set #{@name.to_s} = #{@name} || #{arg.to_or} "}[@name] if check_if_complete
86
+ @orient.reload!
87
+ end
88
+ =begin
89
+ Append the argument to the Array, changes the Array itself.
90
+
91
+ Returns the modified Array ( and is chainable )
92
+ #
93
+ # i= V.get( '89:0')
94
+ # ii=i.zwoebelkuchen << 'z78' << 6 << [454, 787]
95
+ # => [7, 5, 6, "z78", 78, 45, "z78", 6, 454, 787]
96
+
97
+ The change is immediately transmitted to the database.
98
+
99
+ The difference to `append`: that method accepts a komma separated list of arguments
100
+ and returns the modified database-document. `<<` accepts only one argument. An
101
+ Array is translated into multi-arguments of `append`
102
+
103
+ > t = G21.create ll: ['test','test_2', 5, 8 , 7988, "uzg"]
104
+ INFO->CREATE VERTEX ml_g21 CONTENT {"ll":["test","test_2",5,8,7988,"uzg"]}
105
+ => #<ML::G21:0x0000000002622cb0 @metadata={:type=>"d", :class=>"ml_g21", :version=>1,
106
+ :fieldTypes=>nil, :cluster=>271, :record=>0},
107
+ @attributes={:ll=>["test", "test_2", 5, 8, 7988, "uzg"]}>
108
+ > t.ll << [9,10]
109
+ INFO->update #271:0 set ll = ll || [9, 10] return after @this
110
+ => ["test", "test_2", 5, 8, 7988, "uzg"]
111
+ > t.ll << [9,10] << 'u'
112
+ INFO->update #271:0 set ll = ll || [9, 10] return after @this
113
+ INFO->update #271:0 set ll = ll || ['u'] return after @this
114
+ => ["test", "test_2", 5, 8, 7988, "uzg", 9, 10]
115
+
116
+
117
+ The Array can be treated separately
118
+
119
+ > z = t.ll
120
+ => ["test", "test_2", 5, 8, 7988, "uzg"]
121
+ > z << 78
122
+ INFO->update #272:0 set ll = ll || [78] return after @this
123
+ => ["test", "test_2", 5, 8, 7988, "uzg", 78]
124
+
125
+ =end
126
+ def << arg
127
+ append( *arg).send @name
128
+ end
129
+
130
+ =begin
131
+
132
+ Removes the specified list entries from the Array
133
+
134
+ Returns the modified Array (and is chainable).
135
+
136
+ > t= G21.first
137
+ > t.ll
138
+ => ["test", "test_2", 7988, "uzg", 6789, "xvy"]
139
+ > u= t.ll << 'xvz'
140
+ # INFO->update #272:0 set ll = ll || ['xvz'] return after @this
141
+ => ["test", "test_2", 7988, "uzg", 6789, "xvy", "xvz"]
142
+ > z= u.remove 'xvy'
143
+ # INFO->update #272:0 remove ll = 'xvy' return after @this
144
+ => ["test", "test_2", 7988, "uzg", 6789, "xvz"]
145
+
146
+ The ModelInstance is updated, too, as shown by calling
147
+
148
+ > t.ll
149
+ => ["test", "test_2", 7988, "uzg", 6789, "xvz"]
150
+
151
+
152
+ Thus
153
+
154
+ > t.ll.remove 7988
155
+ # INFO->update #272:0 remove ll = 7988 return after @this
156
+ => ["test", "test_2", "uzg", 6789, "xvz"]
157
+
158
+ returns thea modified Array
159
+ =end
160
+ def remove *k
161
+ # todo combine queries in a transaction
162
+ ActiveOrient::Base.logger.debug { "delete: #{@name} --< #{k.map(&:to_or).join( ' :: ' )}"}
163
+ k.map{|l| @orient.update( {remove: { @name => l} } ) }
164
+ # @orient.reload!
165
+ # @orient.send @name
166
+ end
167
+
168
+ def remove_by_index index
169
+ @orient.update( { remove: { @name => "#{@name[index]}" } } )
170
+ end
171
+
172
+ def check_if_complete
173
+ if @name.blank?
174
+ @orient.logger.warn{ "Database is uneffected. Operation is incomplete/ not allowed" }
175
+ false
176
+ else
177
+ true
178
+ end
179
+ end
180
+ =begin
181
+ Updating of single items
182
+ =end
183
+ def []= key, value
184
+ super
185
+ @orient.update set: {@name => self} if @name.present? if check_if_complete
186
+ end
187
+
188
+
189
+ ###
190
+ ## just works with Hashes as parameters
191
+ def where *item
192
+ where_string = item.map{|m| where_string = compose_where( m ) }.join(' and ')
193
+ subquery= OrientSupport::OrientQuery.new from: @orient, projection: "expand( #{@name})"
194
+ q= OrientSupport::OrientQuery.new from: subquery, where: item
195
+ @orient.db.execute{ q.to_s } if check_if_complete
196
+
197
+ end
198
+
199
+ def method_missing method, *args
200
+ return if empty?
201
+ if @orient.is_a? ActiveOrient::Model # IB::Model
202
+ # delegate to public methods
203
+ self.map{|x| x.public_send(method, *args)}
204
+ else
205
+ self.map{|x| x.send method, *args }
206
+ end
207
+ rescue NoMethodError => e
208
+ ActiveOrient::Base.logger.error("OrientSupport::Array"){ "#{self.inspect} MethodMissing -> Undefined method: #{args.first} -- Args: #{args[1..-1].inspect}"}
209
+ ActiveOrient::Base.logger.error {" The Message #{e.message}"}
210
+ ActiveOrient::Base.logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
211
+ raise
212
+ end
213
+
214
+ end #Class
215
+
216
+
217
+
218
+
219
+ class Hash < Hash # WithIndifferentAccess
220
+ include OrientSupport::Support
221
+ def initialize modelinstance, args
222
+ super()
223
+ @orient = modelinstance
224
+ self.merge! args
225
+ @name = block_given? ? yield : modelinstance.attributes.key(self)
226
+ self
227
+ end
228
+
229
+ def store k, v
230
+ @orient.update { "set #{@name}[#{k.to_s.to_or}] = #{v.to_or} "}[@name] #if check_if_complete
231
+ @orient.reload!
232
+ end
233
+
234
+ alias []= store
235
+
236
+
237
+ # Inserts the provided Hash to the (possibly empty) list-property and updates the dataset
238
+ #
239
+ # Keys are translated to symbols
240
+ #
241
+ def merge **arg
242
+ @orient.update @name => super(**arg)
243
+ @orient.reload!
244
+ end
245
+
246
+ alias << merge
247
+
248
+ # removes a key-value entry from the hash.
249
+ #
250
+ # parameter: list of key's
251
+ #
252
+ # returns the modified OrientSupport::Hash
253
+ #
254
+ # ie, given
255
+ # b => <Base[51:0]: < Base: 51:0 >, a_set : {:warrant_value=>["8789", "HKD"], :what_if_pm_enabled=>["true", ""], :t_bill_value=>["0", "HKD"]}>
256
+ # c= b.a_set.remove :warrant_value
257
+ # INFO->update #51:0 remove a_set = 'warrant_value' return after $current
258
+ # c => {:what_if_pm_enabled=>["true", ""], :t_bill_value=>["0", "HKD"]}
259
+
260
+
261
+ def remove *k
262
+ # todo combine queries in a transaction
263
+
264
+ r= k.map{|key| @orient.update{ "remove #{@name} = #{key.to_s.to_or} " } }
265
+ @orient.reload!.send @name
266
+
267
+ end
268
+
269
+ def delete_if &b
270
+ super &b
271
+ @orient.update set:{ @name => self}
272
+
273
+ end
274
+
275
+ # slice returns a subset of the hash
276
+ #
277
+ # excepts a regular expression as well
278
+ def slice arg
279
+ if arg.is_a? Regexp
280
+ find_all{ |key| key.to_s.match(arg) }.to_h
281
+ else
282
+ super arg.to_sym
283
+ end
284
+ end
285
+ def [] arg
286
+ super
287
+ end
288
+ end
289
+ end #Module
290
+
291
+ class Hash
292
+
293
+ def to_human
294
+ "{ " + self.map{ |k,v| [k.to_s,": ", v.to_orient].join }.join(', ') + " }"
295
+ end
296
+
297
+ # def coerce arg
298
+ # if arg.is_a? DateTime
299
+ # nil
300
+ # else
301
+ # super
302
+ #
303
+ # end
304
+ # end
305
+ end