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,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,29 @@
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
+ case operation
18
+ when Hash
19
+ p,s = operation.keys.first, operation.values.first
20
+ else
21
+ p, s = operation.gsub(/\s+/, "").split("=")
22
+ end
23
+ if ["%","*"].include?(s[-1])
24
+ s.chop!
25
+ end
26
+
27
+ query( where: { "#{p}.left(#{s.length})" => s } ,order: { p => order }).execute
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ class E #< ActiveOrient::Model
2
+ def self.naming_convention name=nil
3
+ name.present? ? name.upcase : ref_name.upcase
4
+ end
5
+ end
6
+
@@ -0,0 +1,114 @@
1
+ class E < ActiveOrient::Model
2
+ ## class methods
3
+ class << self
4
+ def naming_convention name=nil
5
+ name.present? ? name.upcase : ref_name.upcase
6
+ end
7
+
8
+ =begin
9
+ Establish constrains on Edges
10
+
11
+ After applying this method Edges are uniq!
12
+
13
+ Creates individual indices for child-classes if applied to the class itself.
14
+ =end
15
+ def uniq_index
16
+ create_property :in, type: :link, linked_class: :V
17
+ create_property :out, type: :link, linked_class: :V
18
+ create_index "#{ref_name}_idx", on: [ :in, :out ]
19
+ end
20
+
21
+ =begin
22
+ Instantiate a new Edge between two Vertices
23
+
24
+ Properties can be placed using the :set-directive or simply by adding key: value- parameter-pairs
25
+
26
+ if the creation of an edged is not possible, due to constrains (uniq_index), the already
27
+ connecting edge is returned
28
+
29
+ the method is thread safe, if transaction and update_cache are set to false
30
+ =end
31
+ def create from:, to: , set: {}, transaction: false, update_cache: false, **attributes
32
+ return nil if from.blank? || to.blank?
33
+ set.merge!(attributes)
34
+ content = set.empty? ? "" : "content #{set.to_orient.to_json}"
35
+ statement = "CREATE EDGE #{ref_name} from #{from.to_or} to #{to.to_or} #{content}"
36
+ transaction = true if [:fire, :complete, :run].include?(transaction)
37
+ ir= db.execute( transaction: transaction, process_error: false ){ statement }
38
+ if update_cache
39
+ from.reload! # get last version
40
+ to.is_a?(Array)? to.each( &:reload! ) : to.reload!
41
+ end
42
+ to.is_a?(Array) ? ir : ir.first # return the plain edge, if only one is created
43
+ rescue RestClient::InternalServerError => e
44
+ sentence= JSON.parse( e.response)['errors'].last['content']
45
+ if sentence =~ /found duplicated key/
46
+ ref_rid = sentence.split.last.expand # return expanded rid
47
+ else
48
+ raise
49
+ end
50
+ rescue ArgumentError => e
51
+ logger.error{ "wrong parameters #{keyword_arguments} \n\t\t required: from: , to: , attributes:\n\t\t Edge is NOT created"}
52
+ end
53
+
54
+ =begin
55
+ Fires a "delete edge" command to the database.
56
+
57
+
58
+ The where statement can be empty ( "" or {}"), then all edges are removed
59
+
60
+ The rid-cache is resetted
61
+
62
+
63
+ to_do: Implement :all=> true directive
64
+ support from: , to: syntax
65
+
66
+ :call-seq:
67
+ delete where:
68
+ =end
69
+ def delete where:
70
+
71
+ db.execute { "delete edge #{ref_name} #{db.compose_where(where)}" }
72
+ reset_rid_store
73
+
74
+ end
75
+
76
+
77
+ def connect dir= "-" , **args # arguments: direction: :both,
78
+ # count: 1,
79
+ # # as: nil
80
+
81
+ direction = case dir
82
+ when "-"
83
+ :both
84
+ when '->'
85
+ :out
86
+ when '<-'
87
+ :in
88
+ when Symbol
89
+ dir
90
+ end
91
+ args[:direction] ||= direction
92
+
93
+
94
+ OrientSupport::MatchConnection.new self, **args
95
+ end
96
+
97
+ end # class methods
98
+
99
+ ### instance methods ###
100
+
101
+ =begin
102
+ Deletes the actual ActiveOrient::Model-Edge-Object
103
+
104
+ =end
105
+
106
+ def delete
107
+ db.execute{ "delete edge #{ref_name} #{rrid}" }
108
+ end
109
+ def to_human
110
+ displayed_attributes = attributes.reject{|k,_| [:in, :out].include?(k) }
111
+ "<#{self.class.to_s.demodulize}[#{rrid}] :.: #{ attributes[:out].rid}->#{displayed_attributes.to_human}->#{attributes[:in].rid}>"
112
+ end
113
+
114
+ 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