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