arcadedb 0.4 → 0.5.2

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.
@@ -9,7 +9,7 @@ module Arcade
9
9
  $ Arcade::Api.drop_database <a string> # returns true if successfull
10
10
 
11
11
  $ Arcade::Api.create_document <database>, <type>, attributes
12
- $ Arcade::Api.execute( <database> ) { <query> }
12
+ $ Arcade::Api.execute( <database> [, session_id: some_session_id ]) { <query> }
13
13
  $ Arcade::Api.query( <database> ) { <query> }
14
14
  $ Arcade::Api.get_record <database>, rid # returns a hash
15
15
 
@@ -32,13 +32,20 @@ module Arcade
32
32
  get_data 'databases'
33
33
  end
34
34
 
35
+ # ------------------------------ database? ------------------------------------------------- #
36
+ # returns true if the database exists #
37
+
38
+ def self.database? name
39
+ get_data "exists/#{name}"
40
+ end
41
+
35
42
  # ------------------------------ create database ------------------------------------------------- #
36
43
  # creates a database if not present #
37
44
  def self.create_database name
38
45
  return if databases.include?( name.to_s )
39
46
  payload = { "command" => "create database #{name}" }
40
47
  post_data "server", payload
41
- rescue HTTPX::HTTPError => e
48
+ rescue Arcade::QueryError => e
42
49
  logger.fatal "Create database #{name} through \"POST create/#{name}\" failed"
43
50
  logger.fatal e
44
51
  raise
@@ -50,29 +57,10 @@ module Arcade
50
57
  return unless databases.include?( name.to_s )
51
58
  payload = {"command" => "drop database #{name}" }
52
59
  post_data "server", payload
53
- rescue HTTPX::HTTPError => e
60
+ rescue Arcade::QueryError => e
54
61
  logger.fatal "Drop database #{name} through \"POST drop database/#{name}\" failed"
55
62
  raise
56
63
  end
57
- # ------------------------------ create document ------------------------------------------------- #
58
- # adds a document to the specified database table
59
- #
60
- # specify database-fields as hash-type parameters
61
- #
62
- # i.e Arcade::Api.create_document 'devel', 'documents', name: 'herta meyer', age: 56, sex: 'f'
63
- #
64
- # returns the rid of the inserted dataset
65
- #
66
- def self.create_document database, type, **attributes
67
- payload = { "@type" => type }.merge( attributes )
68
- logger.debug "C: #{payload}"
69
- options = if session.nil?
70
- payload
71
- else
72
- payload.merge headers: { "arcadedb-session-id" => session }
73
- end
74
- post_data "document/#{database}", options
75
- end
76
64
 
77
65
  # ------------------------------ execute ------------------------------------------------- #
78
66
  # executes a sql-query in the specified database
@@ -84,21 +72,40 @@ module Arcade
84
72
  # Arcade::Api.execute( "devel" ) { 'select from test ' }
85
73
  # =y [{"@rid"=>"#57:0", "@type"=>"test", "name"=>"Hugo"}, {"@rid"=>"#60:0", "@type"=>"test", "name"=>"Hubert"}]
86
74
  #
87
- def self.execute database, query=nil, session_id= nil
88
- pl = query.nil? ? provide_payload(yield) : provide_payload(query)
89
- if session_id.nil? && session.nil?
75
+ def self.execute database, session_id: nil
76
+ pl = provide_payload(yield)
77
+ if session_id.nil?
90
78
  post_data "command/#{database}" , pl
91
79
  else
92
- post_transaction "command/#{database}" , pl, session_id || session
80
+ post_transaction "command/#{database}" , pl, session_id: session_id
93
81
  end
94
82
  end
95
83
 
96
84
  # ------------------------------ query ------------------------------------------------- #
97
85
  # same for idempotent queries
98
- def self.query database, query
99
- post_data "query/#{database}" , provide_payload(query)
86
+ def self.query database, query, session_id: nil
87
+ # if session_id.nil?
88
+ get_data "query/#{database}/sql/" + provide_payload(query, action: :get)[:query]
89
+ # else
90
+ # post_transaction "query/#{database}" , provide_payload(query), session_id: session_id
91
+ # end
100
92
  end
101
93
 
94
+ # ------------------------------ create Document ------------------------------------------------- #
95
+ # adds a record ( document, vertex or edge ) to the specified database type
96
+ #
97
+ # specify database-fields as hash-type parameters
98
+ #
99
+ # i.e Arcade::Api.create_record 'devel', 'documents', name: 'herta meyer', age: 56, sex: 'f'
100
+ #
101
+ # returns the rid of the inserted dataset
102
+ #
103
+ def self.create_document database, type, session_id: nil, **attributes
104
+ content = "CONTENT #{ attributes.to_json }"
105
+ result = execute( database, session_id: session_id ){ "INSERT INTO #{type} #{content} "}
106
+ result.first[:@rid] # emulate the »old« behavior of the /post/create_document api endpoint
107
+ end
108
+
102
109
  # ------------------------------ get_record ------------------------------------------------- #
103
110
  # fetches a record by providing database and rid
104
111
  # and returns the result as hash
@@ -112,7 +119,7 @@ module Arcade
112
119
  rid = rid.join(':')
113
120
  rid = rid[1..-1] if rid[0]=="#"
114
121
  if rid.rid?
115
- get_data "document/#{database}/#{rid}"
122
+ get_data( "query/#{database}/sql/" + URI.encode_uri_component("select from #{rid}")) &.first
116
123
  else
117
124
  raise Error "Get requires a rid input"
118
125
  end
@@ -133,21 +140,17 @@ module Arcade
133
140
  #
134
141
  def self.property database, type, **args
135
142
 
136
- begin_transaction database
143
+ s= begin_transaction database
137
144
  success = args.map do | name, format |
138
- r= execute(database) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
139
- puts "R: #{r.inspect}"
140
- if r.nil?
141
- false
142
- else
143
- r[:operation] == 'create property'
144
- end
145
+ r= execute(database, session_id: s) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
146
+ r.nil? ? false : r[:operation] == 'create property'
145
147
  end.uniq
146
148
  if success == [true]
147
- commit database
149
+ commit database, session_id: s
148
150
  true
149
151
  else
150
- rollback database
152
+ rollback database log: false, session_id: s
153
+ false
151
154
  end
152
155
 
153
156
 
@@ -160,9 +163,6 @@ module Arcade
160
163
  unique_requested = "notunique" if properties.delete("notunique" )
161
164
  automatic = true if
162
165
  properties << name if properties.empty?
163
- # puts " create index #{type.to_s}[#{name.to_s}] on #{type} ( #{properties.join(',')} ) #{unique_requested}"
164
- # VV 22.10: providing an index-name raises an Error ( Encountered " "(" "( "" at line 1, column 44. Was expecting one of: <EOF> <SCHEMA> ... <NULL_STRATEGY> ... ";" ... "," ... )) )
165
- # named indices droped for now
166
166
  success = execute(database) {" create index IF NOT EXISTS on #{type} (#{properties.join(', ')}) #{unique_requested}" } &.first
167
167
  # puts "success: #{success}"
168
168
  success[:operation] == 'create index'
@@ -176,9 +176,6 @@ module Arcade
176
176
  Database.logger
177
177
  end
178
178
 
179
- def self.session
180
- @session_id
181
- end
182
179
 
183
180
  def self. provide_payload( the_yield, action: :post )
184
181
  unless the_yield.is_a? Hash
@@ -189,7 +186,11 @@ module Arcade
189
186
  the_yield.map do | key, value |
190
187
  case key
191
188
  when :query
192
- action == :post ? [ :command, value ] : [ :query, value ]
189
+ if action == :post
190
+ [ :command, value ]
191
+ else
192
+ [ :query, URI.encode_uri_component(value )]
193
+ end
193
194
  when :limit
194
195
  [ :limit , value ]
195
196
  when :params
@@ -207,7 +208,7 @@ module Arcade
207
208
  [ :serializer, value.to_sym ]
208
209
  end
209
210
  when :language
210
- if [:sql, :cypher, :gremlin, :neo4j ].include? value.to_sym
211
+ if [:sql, :cypher, :gremlin, :neo4j, :sqlscript, :graphql, :mongo ].include? value.to_sym
211
212
  [ :language, value.to_sym ]
212
213
  end
213
214
  end # case
@@ -216,42 +217,11 @@ module Arcade
216
217
 
217
218
 
218
219
 
219
- # returns the json-response ## retiered
220
- def self.analyse_result r, command
221
- if r.success?
222
- return nil if r.status == 204 # no content
223
- result = JSON.parse( r.response_body, symbolize_names: true )[:result]
224
- if result == [{}]
225
- []
226
- else
227
- result
228
- end
229
- elsif r.timed_out?
230
- raise Error "Timeout Error", caller
231
- []
232
- elsif r.response_code > 0
233
- logger.error "Execution Failure – Code: #{ r.response_code } – #{r.status_message} "
234
- error_message = JSON.parse( r.response_body, symbolize_names: true )
235
- logger.error "ErrorMessage: #{ error_message[:detail]} "
236
- if error_message[:detail] =~ /Duplicated key/
237
- raise IndexError, error_message[:detail]
238
- else
239
- # available fields: :detail, :exception, error
240
- puts error_message[:detail]
241
- #raise error_message[:detail], caller
242
- end
243
- end
244
- end
245
220
  def self.auth
246
221
  @a ||= { httpauth: :basic,
247
222
  username: Config.admin[:user],
248
223
  password: Config.admin[:pass] }
249
224
  end
250
225
 
251
- # not tested
252
- def self.delete_data command
253
- result = HTTPX.delete Config.base_uri + command, auth
254
- analyse_result(result, command)
255
- end
256
226
  end
257
227
  end
@@ -8,7 +8,13 @@ module Arcade
8
8
  # persistent http handle to the database
9
9
 
10
10
  def http
11
- break_on = -> (response) { response.status == 500 }
11
+ # break_on = -> (response) { response.status == 500 }
12
+ # Version 23.12: Persistent connection are inactive after 3 sec.
13
+ # Implemented a walk around, that renews the connection after 2 sec. of inactivity
14
+ t = Time.now
15
+ @t ||= Time.now
16
+ @http = nil if t-@t > 2
17
+ @t = t
12
18
  @http ||= HTTPX.plugin(:basic_auth).basic_auth(auth[:username], auth[:password])
13
19
  .plugin(:persistent)
14
20
  .plugin(:circuit_breaker)
@@ -17,8 +23,16 @@ module Arcade
17
23
 
18
24
  # ------------------------------ get data -------------------------------------------------------- #
19
25
  def get_data command
20
- response = http.get( Config.base_uri + command )
21
- response.raise_for_status
26
+ case response = http.get( Config.base_uri + command )
27
+ in {status: 200..299}
28
+ # success
29
+ JSON.parse( response.body, symbolize_names: true )[:result]
30
+ in {status: 400..}
31
+ raise Arcade::QueryError.new **response.json( symbolize_names: true )
32
+ else
33
+ # # http error
34
+ raise response
35
+ end
22
36
 
23
37
  JSON.parse( response.body, symbolize_names: true )[:result]
24
38
  # alternative to `raise for status `
@@ -41,56 +55,77 @@ module Arcade
41
55
 
42
56
  # ------------------------------ post data -------------------------------------------------------- #
43
57
  def post_data command, payload
44
- # http = HTTPX.plugin(:basic_auth)
45
- # .basic_auth(auth[:username], auth[:password])
46
- response = http.post( Config.base_uri + command, json: payload )
47
- response.raise_for_status
48
- JSON.parse( response.body, symbolize_names: true )[:result]
58
+ case response = http.post( Config.base_uri + command, json: payload )
59
+ in {status: 200..299}
60
+ # success
61
+ JSON.parse( response.body, symbolize_names: true )[:result]
62
+ in {status: 400..}
63
+ detail = response.json( symbolize_names: true )[:detail]
64
+ if detail =~ /Please retry the operation/
65
+ logger.error "--------------------------------"
66
+ logger.error " ----> Operation repeated <---- "
67
+ logger.error detail
68
+ logger.error "The query --> #{payload.inspect}"
69
+ logger.error "--------------------------------"
70
+ sleep 1
71
+ post_data command, payload
72
+ else
73
+ raise Arcade::QueryError.new **response.json( symbolize_names: true )
74
+ end
75
+ else
76
+ # # http error
77
+ raise response
78
+ end
79
+ # response.raise_for_status
49
80
  end
50
81
 
51
82
  # ------------------------------ transaction ------------------------------------------------- #
52
83
  #
84
+ # The payload, optional as a JSON, accepts the following parameters:
85
+ # isolationLevel: READ_COMMITTED (default) or REPEATABLE_READ. (not implemented)
86
+ #
53
87
  def begin_transaction database
54
88
  result = http.post Config.base_uri + "begin/#{database}"
55
- @session_id = result.headers["arcadedb-session-id"]
56
89
  # returns the session-id
90
+ result.headers["arcadedb-session-id"]
57
91
  end
58
92
 
59
93
  # ------------------------------ post transaction ------------------------------------------------- #
60
- def post_transaction command, params, session_id= @session_id
61
- # http = HTTPX.plugin(:basic_auth)
62
- # .basic_auth(auth[:username], auth[:password])
63
- # .with( headers: { "arcadedb-session-id"=>session }, debug_level: 1)
94
+ def post_transaction command, params, session_id:
64
95
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
65
- response = http_a.post( Config.base_uri + command, json: params )
66
- response.raise_for_status
67
- JSON.parse( response.body, symbolize_names: true )[:result]
68
-
96
+ puts "params #{params.inspect}"
97
+ case response = http_a.post( Config.base_uri + command, json: params )
98
+ in {status: 200..299}
99
+ # success
100
+ JSON.parse( response.body, symbolize_names: true )[:result]
101
+ in {status: 400..}
102
+ ## debug
103
+ # puts "Command: #{command}"
104
+ # puts "params: #{params}"
105
+ # puts response.json( symbolize_names: true )
106
+ raise Arcade::QueryError.new **response.json( symbolize_names: true )
107
+ else
108
+ # # http error
109
+ raise response
110
+ end
69
111
  end
70
112
 
71
113
  # ------------------------------ commit ------------------------------------------------- #
72
- def commit database, session_id = @session_id
114
+ def commit database, session_id:
73
115
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
74
116
  response = http_a.post( Config.base_uri + "commit/#{database}" )
75
- response.raise_for_status
76
- @session_id = nil
77
117
  response.status # returns 204 --> success
78
- # 403 --> incalid credentials
118
+ # 403 --> invalid credentials
79
119
  # 500 --> Transaction not begun
80
120
 
81
121
  end
82
122
 
83
123
  # ------------------------------ rollback ------------------------------------------------- #
84
- def rollback database, session_id = @session_id, publish_error=true
85
- # http = HTTPX.plugin(:basic_auth)
86
- # .basic_auth(auth[:username], auth[:password])
87
- # .with( headers: { "arcadedb-session-id"=>session_id }, debug_level: 1)
124
+ def rollback database, session_id: , log: true
88
125
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
89
126
  response = http_a.post( Config.base_uri + "rollback/#{database}" )
90
- response.raise_for_status
91
- @session_id = nil
92
- logger.error "A Transaction has been rolled back" # if publish_error
93
- response.status
127
+ logger.info "A Transaction has been rolled back" if log
128
+ response.status # returns 500 !
94
129
  end
95
130
  end
96
131
  end
data/lib/arcade/base.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Arcade
2
+
2
3
  class Base < Dry::Struct
3
4
 
4
5
  extend Arcade::Support::Sql
@@ -20,6 +21,9 @@ module Arcade
20
21
  ## ----------------------------------------- Class Methods------------------------------------ ##
21
22
  # #
22
23
  class << self
24
+ def descendants
25
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
26
+ end
23
27
 
24
28
  # this has to be implemented on class level
25
29
  # otherwise it interfere with attributes
@@ -27,6 +31,16 @@ module Arcade
27
31
  self.name.snake_case
28
32
  end
29
33
 
34
+ def begin_transaction
35
+ db.begin_transaction
36
+ end
37
+ def commit
38
+ db.commit
39
+ end
40
+ def rollback
41
+ db.rollback
42
+ end
43
+
30
44
  def create_type
31
45
  the_class = nil # declare as local var
32
46
  parent_present = ->(cl){ db.hierarchy.flatten.include? cl }
@@ -67,7 +81,7 @@ module Arcade
67
81
  the_command = command[0 .. -2] # remove '\n'
68
82
  next if the_command == ''
69
83
  # db.logger.info "Custom Setup:: #{the_command}"
70
- db.execute { the_command }
84
+ db.transmit { the_command }
71
85
  end unless custom_setup.nil?
72
86
 
73
87
  rescue RollbackError => e
@@ -118,9 +132,11 @@ module Arcade
118
132
  # (not supported (jet): [RETURN <expression>] [FROM <query>] )
119
133
 
120
134
  def insert **attributes
121
- db.insert type: database_name, **attributes
135
+ db.insert type: database_name, session_id: attributes.delete(:session_id), **attributes
122
136
  end
123
137
 
138
+ alias create insert
139
+
124
140
  ## ----------------------------------------- create ---------------------------------- ##
125
141
  #
126
142
  # Adds a record to the database
@@ -128,21 +144,21 @@ module Arcade
128
144
  # returns the model dataset
129
145
  # ( depreciated )
130
146
 
131
- def create **attributes
132
- s = Api.begin_transaction db.database
133
- attributes.merge!( created: DateTime.now ) if timestamps
134
- record = insert **attributes
135
- Api.commit db.database, s
136
- record
137
- rescue QueryError => e
138
- db.logger.error "Dataset NOT created"
139
- db.logger.error "Provided Attributes: #{ attributes.inspect }"
140
- # Api.rollback db.database ---> raises "transactgion not begun"
141
- rescue Dry::Struct::Error => e
142
- Api.rollback db.database
143
- db.logger.error "#{ rid } :: Validation failed, record deleted."
144
- db.logger.error e.message
145
- end
147
+ # def create **attributes
148
+ # s = Api.begin_transaction db.database
149
+ # attributes.merge!( created: DateTime.now ) if timestamps
150
+ # record = insert **attributes
151
+ # Api.commit db.database, s
152
+ # record
153
+ # rescue HTTPX::HTTPError => e
154
+ # db.logger.error "Dataset NOT created"
155
+ # db.logger.error "Provided Attributes: #{ attributes.inspect }"
156
+ # Api.rollback db.database # ---> raises "transactgion not begun"
157
+ # rescue Dry::Struct::Error => e
158
+ # Api.rollback db.database
159
+ # db.logger.error "#{ rid } :: Validation failed, record deleted."
160
+ # db.logger.error e.message
161
+ # end
146
162
 
147
163
  def count **args
148
164
  command = "count(*)"
@@ -219,9 +235,9 @@ module Arcade
219
235
  # is equivalent to
220
236
  # Strategie.all.find{|y| y.symbol == 'Still' }
221
237
  def find **args
222
- f= where(**args).first
223
- f= where( "#{ args.keys.first } like #{ args.values.first.to_or }" ).first if f.nil? || f.empty?
224
- f
238
+ where(**args).first
239
+ # f= where( "#{ args.keys.first } like #{ args.values.first.to_or }" ).first if f.nil? || f.empty?
240
+ # f
225
241
  end
226
242
  # update returns a list of updated records
227
243
  #
@@ -282,16 +298,19 @@ module Arcade
282
298
  Query.new( **{ from: self }.merge(args) )
283
299
  end
284
300
 
285
- # immutable support
286
- # to make a database type immutable add
301
+ # ## Immutable Support
302
+ #
303
+ # To make a database type immutable add
287
304
  # `not_permitted :update, :upsert, :delete`
288
305
  # to the model-specification
289
306
  #
307
+ # Even after applying `not_permitted` the database-type can be modified via class-methods.
308
+ #
290
309
  def not_permitted *m
291
310
  m.each do | def_m |
292
- define_method( def_m ) do | v = nil |
293
- raise ArcadeImmutableError "operation not permitted", caller
294
- end
311
+ define_method( def_m ) do | v = nil |
312
+ raise Arcade::ImmutableError.new( "operation #{def_m} not permitted" )
313
+ end
295
314
  end
296
315
  end
297
316
 
@@ -354,8 +373,6 @@ module Arcade
354
373
  end
355
374
 
356
375
  def to_human
357
-
358
-
359
376
  "<#{ self.class.to_s.snake_case }" + rid? ? "[#{ rid }]: " : " " + invariant_attributes.map do |attr, value|
360
377
  v= case value
361
378
  when Base
@@ -367,6 +384,9 @@ module Arcade
367
384
  end
368
385
  "%s : %s" % [ attr, v] unless v.nil?
369
386
  end.compact.sort.join(', ') + ">".gsub('"' , ' ')
387
+
388
+ rescue TypeError => e
389
+ attributes
370
390
  end
371
391
 
372
392
 
@@ -376,26 +396,44 @@ module Arcade
376
396
  to_human
377
397
  end
378
398
 
399
+
400
+ def html_attributes
401
+ invariant_attributes
402
+ end
403
+
404
+ def in_and_out_attributes
405
+ _modul, _class = self.class.to_s.split "::"
406
+ the_class = _modul == 'Arcade' ? _class : self.class.to_s
407
+ the_attributes = { :"CLASS" => the_class, :"IN" => self.in.count, :"OUT" => self.out.count, :"RID" => rid }
408
+ end
409
+
379
410
  def to_html # iruby
411
+ in_and_out = ->(r) { "[#{r}] : {#{self.in.count}->}{->#{self.out.count }}" }
412
+ the_rid = rid? && rid != "0:0" ? in_and_out[rid] : ""
380
413
  _modul, _class = self.class.to_s.split "::"
381
414
  the_class = _modul == 'Arcade' ? _class : self.class.to_s
382
- IRuby.display IRuby.html "<b style=\"color: #50953DFF\"><#{ the_class}</b>"
383
- + rid? ? "[#{ rid }]: " : " " + invariant_attributes.map do |attr, value|
384
- v= case value
385
- when Base
386
- "< #{ self.class.to_s.snake_case }: #{ value.rid } >"
387
- when Array
388
- value.map{|x| x.to_s}
389
- else
390
- value.from_db
391
- end
392
- "%s : %s" % [ attr, v] unless v.nil?
393
- end.compact.sort.join(', ') + ">".gsub('"' , ' ')
415
+ # the_attribute = ->(v) do
416
+ # case v
417
+ # when Base
418
+ # "< #{ self.class.to_s.snake_case }: #{ v.rid } >"
419
+ # when Array
420
+ # v.map{|x| x.to_s}
421
+ # else
422
+ # v.to_s
423
+ # end
424
+ # end
425
+ # last_part = invariant_attributes.map do |attr, value|
426
+ # [ attr, the_attribute[value] ].join(": ")
427
+ # end.join(', ')
428
+
429
+ # IRuby.display( [IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span><br/> ") , IRuby.table(html_attributes) ] )
430
+ IRuby.display IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span>< #{ html_attributes.map{|_,v| v }.join(', ') } >")
394
431
  end
395
432
 
433
+
396
434
  def update **args
397
435
  Query.new( from: rid , kind: :update, set: args).execute
398
- refresh
436
+ refresh # return the updated record (the object itself is untouched!)
399
437
  end
400
438
 
401
439
  # inserts or updates a embedded document
@@ -406,40 +444,56 @@ module Arcade
406
444
  obj.to_or
407
445
  end
408
446
  # if send( name ).nil? || send( name ).empty?
409
- db.execute { "update #{ rid } set #{ name } = #{ value }" }.first[:count]
447
+ db.transmit { "update #{ rid } set #{ name } = #{ value }" }.first[:count]
410
448
  # end
411
449
  end
412
450
 
413
451
  # updates a single property in an embedded document
414
452
  def update_embedded embedded, embedded_property, value
415
- db.execute{ " update #{rid} set `#{embedded}`.`#{embedded_property}` = #{value.to_or}" }
453
+ db.transmit { " update #{rid} set `#{embedded}`.`#{embedded_property}` = #{value.to_or}" }
416
454
  end
417
455
 
418
- def update_list list, value
419
- value = if value.is_a? Document
456
+ # Adds List-Elements to embedded List
457
+ #
458
+ # Arguments:
459
+ # * list: A symbol of the list property
460
+ # * value: A embedded document or a hash
461
+ # * modus: :auto, :first, :append
462
+ #
463
+ # Prefered modus operandi
464
+ # * the-element.insert (...) , #{list}:[]
465
+ # * the_element.update_list list, value: :append
466
+ #
467
+ def update_list list, value, modus: :auto
468
+ value = if value.is_a? Document # embedded mode
420
469
  value.to_json
421
470
  else
422
471
  value.to_or
423
472
  end
424
- if send( list ).nil? || send( list ).empty?
425
- db.execute { "update #{ rid } set #{ list } = [#{ value }]" }
473
+ if modus == :auto
474
+ modus = db.query( "select #{list}.size() from #{rid}" ).select_result.first.zero? ? :first : :append
475
+ end
476
+
477
+ if modus == :first
478
+ db.transmit { "update #{ rid } set #{ list } = [#{ value }]" }
426
479
  else
427
- db.execute { "update #{ rid } set #{ list } += #{ value }" }
480
+ db.transmit { "update #{ rid } set #{ list } += #{ value }" }
428
481
  end
429
- refresh
482
+ # refresh
430
483
  end
431
484
 
432
485
  # updates a map property , actually adds the key-value pair to the property
486
+ ## does not work on reduced model records
433
487
  def update_map m, key, value
434
488
  if send( m ).nil?
435
- db.execute { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) " }
489
+ db.transmit { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) " }
436
490
  else
437
- db.execute { "update #{ rid } set #{ m }.`#{ key.to_s }` = #{ value.to_or }" }
491
+ db.transmit { "update #{ rid } set #{ m }.`#{ key.to_s }` = #{ value.to_or }" }
438
492
  end
439
493
  refresh
440
494
  end
441
495
  def delete
442
- response = db.execute { "delete from #{ rid }" }
496
+ response = db.transmit { "delete from #{ rid }" }
443
497
  true if response == [{ count: 1 }]
444
498
  end
445
499
  def == arg
@@ -3,11 +3,11 @@ module Arcade
3
3
  extend Dry::Configurable
4
4
  # central place to initialize constants
5
5
  #
6
- # ProjectRoot has to be a Pathname-Object
6
+ # ProjectRoot should to be a Pathname-Object and has to be defined before `arcadedb` is required
7
7
  #
8
8
  #puts "expand: #{File.expand_path(__dir__)}"
9
- unless Arcade.const_defined?( :ProjectRoot )
10
- Arcade::ProjectRoot = if defined?( Rails.env )
9
+ unless Object.const_defined?( :ProjectRoot )
10
+ ::ProjectRoot = if defined?( Rails.env )
11
11
  Rails.root
12
12
  else
13
13
  STDERR.puts "Using default (arcadedb gem) database credentials and settings"
@@ -15,10 +15,9 @@ module Arcade
15
15
  Pathname.new( File.expand_path( "../../", __FILE__ ))
16
16
  end
17
17
  else
18
- STDERR.puts "Using provided database credentials and settings fron #{Arcade::ProjectRoot}"
18
+ STDERR.puts "Using provided database credentials and settings from #{::ProjectRoot}"
19
19
  end
20
20
 
21
-
22
21
  # initialised a hash { environment => property }
23
22
  setting :username, default: :user, reader: true,
24
23
  constructor: ->(v) { yml(:environment).map{|x,y| [x , y[v.to_s]] }.to_h }
@@ -53,7 +52,7 @@ module Arcade
53
52
  def self.config_file
54
53
 
55
54
  configdir = -> do
56
- pr = ProjectRoot.is_a?(Pathname)? ProjectRoot : Pathname.new( ProjectRoot )
55
+ pr = ::ProjectRoot.is_a?(Pathname)? ::ProjectRoot : Pathname.new( ::ProjectRoot )
57
56
  ( cd = pr + 'arcade.yml' ).exist? || ( cd = pr + 'config' + 'arcade.yml' ).exist? || ( cd = pr + 'config.yml' )
58
57
  cd
59
58
  end