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,68 @@
1
+
2
+ module ActiveOrient
3
+ class Railtie < Rails::Railtie
4
+ puts "Railtie included!!"
5
+ initializer 'active_orient.logger' do
6
+ ActiveSupport.on_load(:active_orient) do
7
+ ActiveOrient::Base.logger = Rails.logger
8
+ ActiveOrient::OrientDB.logger = Rails.logger
9
+ #self.logger ||= Rails.logger
10
+ end
11
+ end
12
+
13
+
14
+ initializer 'active_orient.initialize_database_access' do
15
+ # config.active_orient = ActiveSupport::OrderedOptions.new
16
+ begin
17
+ config_file = Rails.root.join('config', 'config.yml')
18
+ configyml = YAML.load_file( config_file )[:active_orient]
19
+ connect_file = Rails.root.join('config', 'connect.yml')
20
+ connectyml = YAML.load_file( connect_file )[:orientdb][:admin]
21
+ databaseyml = YAML.load_file( connect_file )[:orientdb][:database]
22
+ rescue Errno::ENOENT => e
23
+ Rails.logger.error{ "config/connect.yml/config.yml not present" }
24
+ puts "config/***yml not present"
25
+ Rails.logger.error{ "Using defaults to connect database-server" }
26
+ puts "Using defaults to connect database-server"
27
+ end
28
+ ## Rails.root is a Pathname, as well as model_dir
29
+ ActiveOrient::Model.model_dir = Rails.root.join configyml.present? ? configyml[:model_dir] : "app/model"
30
+
31
+ # set the database
32
+ ActiveOrient.database = databaseyml[Rails.env.to_sym] || 'temp'
33
+
34
+ # don't allocate models if no file is provided
35
+ ActiveOrient::Model.keep_models_without_file = false
36
+
37
+ if connectyml.present? and connectyml[:user].present? and connectyml[:pass].present?
38
+ ActiveOrient.default_server= { user: connectyml[:user], password: connectyml[:pass] ,
39
+ server: 'localhost', port: 2480 }
40
+ end
41
+ ActiveOrient::Init.define_namespace namespace: :object
42
+ ::DB = ::ORD = ActiveOrient::OrientDB.new preallocate: true
43
+
44
+ end
45
+
46
+ # We're not doing migrations (yet)
47
+ config.send(:app_generators).orm :active_orient, migration: false
48
+ console do
49
+ Rails.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
50
+ puts '_'*45
51
+ puts "ORD points to the REST-Instance, Database: #{ActiveOrient.database}"
52
+ puts "DB is the API-Instance of the database, DB.db gets the DB-Api-base " if RUBY_PLATFORM == 'java'
53
+
54
+ puts '-'* 45
55
+ ns= case ActiveOrient::Model.namespace
56
+ when Object
57
+ "No Prefix, just ClassName#CamelCase"
58
+ else
59
+ ActiveOrient::Model.namespace.to_s + "{ClassName.camelcase}"
60
+ end
61
+ puts "Namespace for model-classes : #{ns}"
62
+ puts "Present Classes (Hierarchy) "
63
+
64
+ puts ::ORD.class_hierarchy.to_yaml
65
+ puts ActiveOrient::show_classes
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,150 @@
1
+ module RestChange
2
+
3
+ ############### DATABASE ####################
4
+
5
+ # Changes the working-database to {name}
6
+
7
+ def change_database name
8
+ @classes = []
9
+ @database = name
10
+ ActiveOrient.database = name
11
+ end
12
+
13
+ ############# OBJECTS #################
14
+
15
+ =begin
16
+ Convient update of the dataset by calling sql-patch
17
+
18
+ The argument record can be specified as ActiveOrient::Model-instance or as rid-string( #0:0 )
19
+
20
+ called from ModelRecord#update
21
+
22
+ if the update was successful, the updated data are returned as Hash.
23
+
24
+ First the cached object is retrieved
25
+ Its modified by the parameters provided
26
+ and then patched
27
+
28
+ better would be: update the cached object and patch that.
29
+ =end
30
+
31
+ def update record, attributes , version=0
32
+ r = ActiveOrient::Model.autoload_object record.rid
33
+ return(false) unless r.is_a?(ActiveOrient::Model)
34
+ raise "Cannot update the record, version-information missing. please reload ! " if version.nil?
35
+ version = r.version if version.zero?
36
+ result = patch_record(r.rid) do
37
+ attributes.merge({'@version' => version, '@class' => r.class.ref_name })
38
+ end
39
+ # returns a new instance of ActiveOrient::Model and updates any reference on rid
40
+ # It's assumed, that the version
41
+ # if the patch is not successfull no string is returned and thus no record is fetched
42
+ # JSON.parse(result) if result.is_a?(String)
43
+ if result.is_a?(String)
44
+ JSON.parse(result) # return value
45
+ else
46
+ logger.error{ "REST::Update was not successfull" }
47
+ nil # returnvalue
48
+ end
49
+ end
50
+
51
+
52
+ =begin
53
+ Example:
54
+ ORD.update_documents classname, set: {:symbol => 'TWR'}, where: {con_id: 340}
55
+
56
+ Replaces the symbol to TWR in each record where the con_id is 340
57
+
58
+ Both set and where take multiple attributes
59
+
60
+ Returns the JSON-Response.
61
+
62
+ # todo: clear the rid-cache
63
+ =end
64
+
65
+ def update_records o_class, set:{}, where: {}, remove: nil
66
+ logger.progname = 'RestChange#UpdateRecords'
67
+ count = execute do
68
+ if set.present?
69
+ "UPDATE #{classname(o_class)} SET #{generate_sql_list(set)} #{compose_where(where)}"
70
+ elsif remove.present?
71
+ "UPDATE #{classname(o_class)} remove #{remove} #{compose_where(where)}"
72
+ end
73
+ end &.first
74
+ rescue Exception => e
75
+ logger.error{e.message}
76
+ nil
77
+
78
+
79
+ end
80
+
81
+ # Lazy Updating of the given Record.
82
+ # internal using while updating records
83
+ def patch_record rid # :nodoc: (used by #update )
84
+ logger.progname = 'RestChange#PatchRecord'
85
+ content = yield
86
+ if content.is_a? Hash
87
+ begin
88
+ ActiveOrient.db_pool.checkout do | conn |
89
+ conn["/document/#{ActiveOrient.database}/#{rid}"].patch content.to_orient.to_json
90
+ end
91
+ rescue RestClient::Conflict => e # (409)
92
+ # most probably the server is busy. we wait for a second print an Error-Message and retry
93
+ sleep(1)
94
+ logger.error{ "RestClient::Error(409): Server is signaling a conflict ... retrying" }
95
+ retry
96
+ rescue RestClient::InternalServerError => e
97
+ sentence= JSON.parse( e.response)['errors'].last['content']
98
+ logger.error{sentence}
99
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
100
+ logger.error{e.message.to_s}
101
+ end
102
+ else
103
+ logger.error{"PATCH FAILED: The Block must provide an Hash with properties to be updated"}
104
+ end
105
+ end
106
+ alias patch_document patch_record
107
+
108
+
109
+ #### EXPERIMENTAL ##########
110
+
111
+ =begin
112
+ Used to add restriction or other properties to the Property of a Class.
113
+ See http://orientdb.com/docs/2.1/SQL-Alter-Property.html
114
+ =end
115
+
116
+ def alter_property o_class, property:, attribute: "DEFAULT", alteration: # :nodoc: because untested
117
+ logger.progname = 'RestChange#AlterProperty'
118
+ begin
119
+ attribute.to_s! unless attribute.is_a? String
120
+ attribute.capitalize_first_letter
121
+ case attribute
122
+ when "LINKEDCLASS", "LINKEDTYPE", "NAME", "REGEX", "TYPE", "REGEX", "COLLATE", "CUSTOM"
123
+ unless alteration.is_a? String
124
+ logger.error{"#{alteration} should be a String."}
125
+ return 0
126
+ end
127
+ when "MIN", "MAX"
128
+ unless alteration.is_a? Integer
129
+ logger.error{"#{alteration} should be an Integer."}
130
+ return 0
131
+ end
132
+ when "MANDATORY", "NOTNULL", "READONLY"
133
+ unless alteration.is_a? TrueClass or alteration.is_a? FalseClass
134
+ logger.error{"#{alteration} should be an Integer."}
135
+ return 0
136
+ end
137
+ when "DEFAULT"
138
+ else
139
+ logger.error{"Wrong attribute."}
140
+ return 0
141
+ end
142
+
143
+ execute { "ALTER PROPERTY #{class_name(o_class)}.#{property} #{attribute} #{alteration}"}
144
+ rescue Exception => e
145
+ logger.error{e.message}
146
+ end
147
+ end
148
+
149
+
150
+ end
@@ -0,0 +1,287 @@
1
+ module RestCreate
2
+
3
+ ######### DATABASE ##########
4
+
5
+ =begin
6
+ Creates a database with the given name and switches to this database as working-database. Types are either 'plocal' or 'memory'
7
+
8
+ Returns the name of the working-database
9
+ =end
10
+
11
+ def create_database type: 'plocal', database:
12
+ logger.progname = 'RestCreate#CreateDatabase'
13
+ old_d = ActiveOrient.database
14
+ ActiveOrient.database_classes = {}
15
+ ActiveOrient.database = database
16
+ begin
17
+ response = ActiveOrient.db_pool.checkout do | conn |
18
+ conn["database/#{ActiveOrient.database}/#{type}"].post ""
19
+ end
20
+ if response.code == 200
21
+ logger.info{"Database #{ActiveOrient.database} successfully created and stored as working database"}
22
+ else
23
+ logger.error{"Database #{ActiveOrient.database} was NOT created. Working Database is still #{ActiveOrient.database}"}
24
+ ActiveOrient.database = old_d
25
+ end
26
+ rescue RestClient::InternalServerError => e
27
+ logger.error{"Database #{ActiveOrient.database} was NOT created. Working Database is still #{ActiveOrient.database}"}
28
+ ActiveOrient.database = old_d
29
+ end
30
+ ActiveOrient.database # return_value
31
+ end
32
+
33
+
34
+
35
+ =begin
36
+
37
+ This is the historic method CreateClasses. It's just performing the database-stuff and now private
38
+
39
+ it takes superclass and abstract (a hash) as block
40
+
41
+ usually, only one classname is provided, however the method takes a row of classnames as argument
42
+
43
+ #todo reintegrate the ability to create abstract classes
44
+ =end
45
+ private
46
+ def create_this_class *db_classname #nodoc#
47
+ if block_given?
48
+ additional_args = yield
49
+ superclass = additional_args[ :superclass ]
50
+ abstract = additional_args[ :abstract ].presence || nil
51
+ else
52
+ superclass = nil
53
+ abstract = nil
54
+ end
55
+ #
56
+ db_classname.map do | database_class |
57
+ c = if superclass.present?
58
+ "CREATE CLASS #{database_class} EXTENDS #{superclass}"
59
+ else
60
+ "CREATE CLASS #{database_class} "
61
+ end
62
+ c << " ABSTRACT" if abstract.present?
63
+ execute( tolerated_error_code: /already exists/ ){ c }
64
+ end
65
+ # execute anything as batch, don't roll back in case of an error
66
+
67
+ # execute transaction: false, tolerated_error_code: /already exists/ do
68
+ # command
69
+ # end
70
+
71
+ rescue ArgumentError => e
72
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
73
+ end
74
+
75
+
76
+ public
77
+
78
+ ############## OBJECT #############
79
+
80
+
81
+
82
+ =begin
83
+ Creates a Record in the Database and returns this as ActiveOrient::Model-Instance
84
+
85
+ Creates a Record with the attributes provided in the attributes-hash e.g.
86
+ create_record @classname, attributes: {con_id: 343, symbol: 'EWTZ'}
87
+
88
+ Puts the database-response into the cache by default
89
+
90
+ Argument: silence
91
+ if silence is specified, no Error-Messages are raised. Instead
92
+
93
+ * if a record failes to be created, because an index-error occured, it is replaced by that specified in the database response
94
+
95
+ =end
96
+
97
+ def create_record o_class, attributes: {}, cache: true, silence: true # use Model#create instead
98
+ logger.progname = 'RestCreate#CreateRecord'
99
+ attributes = yield if attributes.empty? && block_given?
100
+ # @class must not quoted! Quote only attributes(strings)
101
+ post_argument = {'@class' => classname(o_class)}.merge(attributes.to_orient)
102
+ response = nil
103
+ begin
104
+ response = ActiveOrient.db_pool.checkout do | conn |
105
+ conn["/document/#{ActiveOrient.database}"].post post_argument.to_json
106
+ end
107
+ data = JSON.parse(response.body)
108
+ the_object = ActiveOrient::Model.orientdb_class(name: data['@class']).new data ## return_value
109
+ if cache
110
+ ActiveOrient::Base.store_rid( the_object )
111
+ else
112
+ the_object
113
+ end
114
+ rescue RestClient::InternalServerError => e
115
+ sentence= JSON.parse( e.response)['errors'].last['content']
116
+ if sentence =~ /found duplicated key/
117
+ raise "Duplicate Key" unless silence
118
+ rid = sentence.split("#").last
119
+ logger.info{ "found duplicated Key --> loaded #{rid} instead of creating "}
120
+ ## reading database content -- maybe update attributes?
121
+ get_record rid
122
+ else
123
+ response = JSON.parse(e.response)['errors'].pop
124
+ logger.error{response['content'].split(':')[1..-1].join(':')}
125
+ logger.error{"No Object allocated"}
126
+ nil # return_value
127
+ end
128
+ rescue Errno::EADDRNOTAVAIL => e
129
+ sleep(2)
130
+ retry
131
+ end
132
+ end
133
+ alias create_document create_record
134
+
135
+ # UPDATE <class>|CLUSTER:<cluster>|<recordID>
136
+ # [SET|INCREMENT|ADD|REMOVE|PUT <field-name> = <field-value>[,]*]|[CONTENT|MERGE <JSON>]
137
+ # [UPSERT]
138
+ # [RETURN <returning> [<returning-expression>]]
139
+ # [WHERE <conditions>]
140
+ # [LOCK default|record]
141
+ # [LIMIT <max-records>] [TIMEOUT <timeout>]
142
+
143
+ =begin
144
+ update or insert one record is implemented as upsert.
145
+ The where-condition is merged into the set-attributes if its a hash.
146
+ Otherwise it's taken unmodified.
147
+
148
+ The method returns the included or the updated dataset
149
+
150
+ ## to do
151
+ # yield works for updated and for inserted datasets
152
+ # upsert ( ) do | what, record |
153
+ # if what == :insert
154
+ # do stuff with insert
155
+ # if what == :update
156
+ # do stuff with update
157
+ # end
158
+ # returns nil if no insert and no update was made, ie. the dataset is identical to the given attributes
159
+ =end
160
+ def upsert o_class, set: , where: # use Model#Upsert instead
161
+ logger.progname = 'RestCreate#Upsert'
162
+ specify_return_value = "return after @rid"
163
+ # set.merge! where if where.is_a?( Hash ) # copy where attributes to set
164
+ command = "Update #{classname(o_class)} set #{generate_sql_list( set ){','}} upsert #{specify_return_value} #{compose_where where}"
165
+ result = execute( tolerated_error_code: /found duplicated key/){ command }
166
+ # puts "result #{result.inspect}"
167
+ result =result.pop if result.is_a? Array
168
+ end
169
+ ############### PROPERTIES #############
170
+
171
+ =begin
172
+ Creates properties
173
+
174
+ and (if defined in the provided block) associates an index
175
+ create_properties(classname or class, properties as hash){index}
176
+
177
+ The default-case
178
+ create_properties(:my_high_sophisticated_database_class,
179
+ con_id: {type: :integer},
180
+ details: {type: :link, linked_class: 'Contracts'}) do
181
+ contract_idx: :notunique
182
+ end
183
+
184
+ A composite index
185
+ create_properties(:my_high_sophisticated_database_class,
186
+ con_id: {type: :integer},
187
+ symbol: {type: :string}) do
188
+ {name: 'indexname',
189
+ on: [:con_id, :details] # default: all specified properties
190
+ type: :notunique # default: :unique
191
+ }
192
+ end
193
+ =end
194
+
195
+ def create_properties o_class, all_properties, &b
196
+ logger.progname = 'RestCreate#CreateProperties'
197
+ all_properties_in_a_hash = Hash.new #WithIndifferentAccess.new
198
+ all_properties.each{|field, args| all_properties_in_a_hash.merge! translate_property_hash(field, args)}
199
+ count, response = 0, nil
200
+ # puts "all_properties_in_a_hash #{all_properties_in_a_hash.to_json}"
201
+ if all_properties_in_a_hash.is_a?(Hash)
202
+ begin
203
+ response = ActiveOrient.db_pool.checkout do | conn |
204
+ conn["/property/#{ActiveOrient.database}/#{classname(o_class)}"].post all_properties_in_a_hash.to_json
205
+ end
206
+ # puts response.inspect
207
+ # response.body.to_i returns response.code, only to_f.to_i returns the correct value
208
+ count= response.body.to_f.to_i if response.code == 201
209
+ rescue RestClient::InternalServerError => e
210
+ logger.progname = 'RestCreate#CreateProperties'
211
+ response = JSON.parse(e.response)['errors'].pop
212
+ error_message = response['content'].split(':').last
213
+ logger.error{"Properties in #{classname(o_class)} were NOT created"}
214
+ logger.error{"The Error was: #{response['content'].split(':').last}"}
215
+ nil
216
+ end
217
+ end
218
+ ### index
219
+ if block_given?# && count == all_properties_in_a_hash.size
220
+ index = yield
221
+ if index.is_a?(Hash)
222
+ if index.size == 1
223
+ create_index o_class, name: index.keys.first, on: all_properties_in_a_hash.keys, type: index.values.first
224
+ else
225
+ index_hash = {type: :unique, on: all_properties_in_a_hash.keys}.merge index
226
+ create_index o_class, name: index_hash[:name], on: index_hash[:on], type: index_hash[:type]
227
+ end
228
+ end
229
+ end
230
+ count # return_value
231
+ end
232
+
233
+ =begin
234
+ Create a single property.
235
+
236
+ Supported types: https://orientdb.com/docs/last/SQL-Create-Property.html
237
+
238
+ If an index is to be specified, it's defined in the optional block
239
+
240
+ create_property(class, field){:unique | :notunique}
241
+ --> creates an automatic-Index on the given field
242
+
243
+ create_property(class, field){{»name« => :unique | :notunique | :full_text}}
244
+ --> creates a manual index
245
+ =end
246
+
247
+ def create_property o_class, field, index: nil, **args, &b
248
+ logger.progname = 'RestCreate#CreateProperty'
249
+ args= { type: :string} if args.blank? # the default case
250
+ c = create_properties o_class, {field => args}
251
+ if index.nil? && block_given?
252
+ index = yield
253
+ end
254
+ if index.present?
255
+ if index.is_a?(String) || index.is_a?(Symbol)
256
+ create_index o_class, name: field, type: index
257
+ elsif index.is_a? Hash
258
+ bez = index.keys.first
259
+ create_index o_class, name: bez, type: index[bez], on: [field]
260
+ end
261
+ end
262
+ end
263
+
264
+ ################# INDEX ###################
265
+
266
+ # Used to create an index
267
+
268
+ def create_index o_class, name:, on: :automatic, type: :unique
269
+ logger.progname = 'RestCreate#CreateIndex'
270
+ c = classname o_class
271
+ if execute( transaction: false, tolerated_error_code: /found duplicated key/) do
272
+ if on == :automatic
273
+ "CREATE INDEX #{c}.#{name} #{type.to_s.upcase}"
274
+ elsif on.is_a? Array
275
+ "CREATE INDEX #{name} ON #{c}(#{on.join(', ')}) #{type.to_s.upcase}"
276
+ else
277
+ "CREATE INDEX #{name} ON #{c}(#{on.to_s}) #{type.to_s.upcase}"
278
+ end
279
+ end
280
+
281
+ logger.info{"Index on #{c} based on #{name} created."}
282
+ else
283
+ logger.error {"index #{name}.#{type} on #{c} NOT created"}
284
+ end
285
+ end
286
+
287
+ end