active-orient 0.4 → 0.80

Sign up to get free protection for your applications and to get access to all the features.
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