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