arcadedb 0.5.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d7f1f29472b0fb31734654c9456ec5bdfb31cd42fd46b658139ac840691c6f6
4
- data.tar.gz: 4c1c95707c8e2c528654f2a7509b1b6943ef9615fe203c81cd421d52636dcac7
3
+ metadata.gz: f030932c1444832b3ea224120f71cc655837ca732717b896ed8587a8cefc0dd4
4
+ data.tar.gz: 96f05185b21af4820ebc667d796635e4b0c6c3ac42b59dc360212df7b287934e
5
5
  SHA512:
6
- metadata.gz: f54746f04760ebfbc8c1588e66fb0bb52e820eef83df564e3563e37219835ff86c93e5b3d2e2a7617452218938c30a5aabff099361d0fd829890e2ac83411fc8
7
- data.tar.gz: 0f8bf3623302c5204ca56552fb41694bfd506c493dc72463b8000e66c82aacddb7f42188f41508c84e9fc5bf93fa78e42e4235ed904982b97d38a4a5c8d4ff40
6
+ metadata.gz: 92ab840638f4a8e0cf56a5138d870bbb282223b9cc0c4b8f5bab39184712ce6742862ea0683a73b917e0b5a94acbd1bccc12ec6a52863caeba9a28b2aacf3132
7
+ data.tar.gz: 6fd195fb51f62fbcea2945f0688626084f8991936b745d6c939bae35bce4912414a80376535a379c3ca1166c4b62ffb2089c041d3951d87fe78ab5dcb421eae0
data/CHANGELOG.md CHANGED
@@ -31,3 +31,20 @@ All notable changes to this project will be documented in this file.
31
31
  - Arcade::Match simple match-statement generator
32
32
  - return to Arcade::QueryError messages instead of HTTPX::HTTPError
33
33
  - The ProjectRoot Const is used to read configuration files (Changed from Arcade::ProjectRoot)
34
+
35
+ ## 0.5.1 - 2024-1-16
36
+ - Included Arcade::RevisionRecord and Arcade::Revision model classes
37
+ - Both provide basic support for audit proofed bookings
38
+ ## 0.5.2 - 2025-02-11
39
+ - Include method Object#descendants (part of active-support)
40
+ - Separate Dry::Type definitions to /lib/types.rb
41
+ - added Vertex#coe (count of edeges) as service method for to_human
42
+ - updated Match#inE, #outE methods to facilitate queries on properties on edges
43
+ ## - 2025-03-15
44
+ - support of Match.new vertex: {a prefetched Arcade::Vertex} and match.node( vertex: ...)
45
+ ## - 2025-03-25
46
+ - support of Vertex.match( where conditions [, as: :return_element] )
47
+ - added Vertex#match to build 'MATCH type: {class} rid: {rid}'
48
+
49
+
50
+
data/README.md CHANGED
@@ -7,7 +7,7 @@ The server's response is then mapped to an ORM (Object-Relational Mapping) based
7
7
  Each database type is represented by a dedicated Model Class, where complex queries are encapsulated.
8
8
  The program also includes a Query-Preprocessor and Match-statment generator for constructing custom queries in ruby fashion.
9
9
 
10
- ***ArcadeDB internally uses `Arcade` as primary namespace****
10
+ ***ArcadeDB internally uses `Arcade` as primary namespace***
11
11
 
12
12
  ## Prerequisites
13
13
 
@@ -76,9 +76,9 @@ __END__
76
76
 
77
77
  Only the `name` attribute is declared.
78
78
 
79
- `Demo::User.create_type` creates the type and executes provided database-commands after __END__.
79
+ `Demo::User.create_type` creates the type and executes provided database-commands after `__END__`.
80
80
 
81
- Other properties are schemaless, but have to be declared in the model-file.
81
+ Other properties are schemaless. They are present in the database and are mapped to the `value`-attribute on the ruby side.
82
82
 
83
83
  ```ruby
84
84
  Person.insert name: "Hubert", age: 35
@@ -97,8 +97,8 @@ Person.delete all: true || where: age: 56 , ...
97
97
 
98
98
  Suppose
99
99
  ```ruby
100
- m = Person.create name: 'Hubert', age: '25'
101
- f = Person.create name: 'Pauline', age: '28'
100
+ m = Person.insert name: 'Hubert', age: 25
101
+ f = Person.insert name: 'Pauline', age: 28
102
102
  m.assign via: IsMarriedTo, vertex: f , divorced: false
103
103
 
104
104
  ```
@@ -116,7 +116,7 @@ pauline = hubert.nodes( :out, via: IsMarriedTo ).first
116
116
  pauline = hubert.out.first
117
117
 
118
118
  ```
119
- Conditions may be set, to.
119
+ Conditions may be set, too.
120
120
  ```ruby
121
121
  hubert.nodes( :out, via: IsMarriedTo, where: "age < 30" )
122
122
  ```
@@ -128,15 +128,15 @@ Person.nodes( :outE, via: IsMarriedTo, where: { divorced: false } )
128
128
  ```
129
129
  gets all wives where the divorced-condition, which is set on the edge, is false.
130
130
 
131
- ## Query
131
+ ### Query
132
132
 
133
133
  A **Query Preprocessor** is implemented. Its adapted from ActiveOrient. The [documentation](https://github.com/topofocus/active-orient/wiki/OrientQuery)
134
- is still valid, however the class has changed to `Arcade::Query`.
134
+ is still valid, but the class has changed to `Arcade::Query`.
135
135
 
136
- ## Match
136
+ ### Match
137
137
 
138
- A simple **Match Statement Generator** is provided for convenience. As the [declarative syntax](https://github.com/ArcadeData/arcadedb-docs/blob/main/src/main/asciidoc/sql/SQL-Match.adoc) seems to be very intuitive, a preprocessor should not be
139
- necessary. With the help of some ruby magic, the creation of match-statements is simply an no-brainer.
138
+ A simple **Match Statement Generator** is provided for convenience. As the [declarative syntax](https://github.com/ArcadeData/arcadedb-docs/blob/main/src/main/asciidoc/sql/SQL-Match.adoc) is very intuitive, a preprocessor should not be
139
+ necessary. However, with the help of some ruby magic, the creation of match-statements is simply an no-brainer.
140
140
 
141
141
  ```ruby
142
142
  a = Arcade::Match.new( type: Person, as: :persons)
@@ -148,9 +148,61 @@ a.to_s
148
148
  a.execute.select_results # sends the statement to the database and returns Arcade Objects.
149
149
  ```
150
150
 
151
- Arcade::Match-objects can be used as `from:` argument to Arcade::Query-Statements, thus hybrid queries are
151
+ To query properties on Edges, `inE`, and `outE` are present.
152
+ ```ruby
153
+ a = Arcade::Match.new( type: Person, as: :persons) -> Match { type: person, as: persons}
154
+ a.outE via: IsMarriedTo, where: { year: 1990..2000 } -> .outE('is_married_to'){ where: year between 1990 and 2000}
155
+ -> .outV('is_maried_to') ...
156
+
157
+ a.outE via: [IsMarriedTo, IsMarriedIn], where: { year: 1990..2000 }, as: :bride
158
+ -> .outV('is_married_in') ...
159
+ ```
160
+
161
+ Match statements can also refer to a vertex:
162
+ ```ruby
163
+ p = Person.find name: 'tux'
164
+ c = Person.find name: 'hilde'
165
+ a = Arcade::Match.new( vertex: p ) -> Match { type: person, rid: 2:0 }
166
+ .out( IsMarriedTo ) -> .out('is_married_to')
167
+ .node( as: :wife ) -> { as: wife }
168
+ .out( HasChild ) -> .out('has_child')
169
+ .node( vertex: c ) -> { type: person, rid: 2:1 }
170
+ -> Return wife
171
+ ```
172
+ fetches the vertex between two known vertices. This is the fastest lookup mechanism if the vertices are prefetched.
173
+
174
+ Arcade::Match-objects can be used as `from:` argument to Arcade::Query-Statements; hybrid queries are
152
175
  easily constructed without extensive string-manipulations.
153
176
 
177
+ #### Vertex based Match statements
178
+
179
+ A `match`-method is implemented for Vertex-Classes and vertices.
180
+
181
+ The class-method takes any argument (except `as:`) as part of a `where`-condition.
182
+
183
+ ```ruby
184
+
185
+ m = Watchlist.match( symbol: 'iBit', as: :ibit )
186
+ .out( HasUnderlying)
187
+ .node( as: :u )
188
+
189
+ m.to_s
190
+ => "MATCH { type: watchlist, where: ( symbol='iBit' ), as: ibit }
191
+ .out('has_underlying'){ as: u }
192
+ RETURN ibit,u "
193
+ ```
194
+
195
+ The instance-method is extremly handy in web-applications. If the `rid` is part of the `params`-hash,
196
+ associated nodes are easily selected
197
+
198
+ ```ruby
199
+ params[:rid] => "#49:0"
200
+
201
+ m= params[:rid].load_rid.match.out( HasUnterlying ).node( as: :u )
202
+ m.to_s
203
+ => "MATCH { type: strategie, rid: #49:0 }.out('has_underlying'){ as: u } RETURN u "
204
+ ```
205
+ ## Low Level Database Requests
154
206
  The **second Layer** handles Database-Requests.
155
207
  In its actual implementation, these requests are delegated to the HTTP/JSON-API.
156
208
 
@@ -158,35 +210,36 @@ In its actual implementation, these requests are delegated to the HTTP/JSON-API.
158
210
  The database-handle is always present through `Arcade::Init.db`
159
211
 
160
212
  ```ruby
161
- DB = Arcade::Init.db
213
+ db = Arcade::Init.db
162
214
 
163
- $ DB.get nn, mm # returns a Aracde:Base object
215
+ $ db.get nn, mm # nn=2; mm=0 # returns a Aracde:Base object
164
216
  # rid is either "#11:10:" or two numbers
165
- $ DB.query querystring # returns either an Array of results (as Hash)
166
- $ DB.execute { querystring } # execute a non idempotent query within a (nested) transaction
167
- $ DB.create <name>, attribute: value .... # Creates a new <Document | Vertex> and returns the rid
217
+ $ db.query querystring # returns either an Array of results (as Hash)
218
+ $ db.execute { querystring } # execute a non idempotent query within a (nested) transaction
219
+ $ db.create <name>, attribute: value .... # Creates a new <Document | Vertex> and returns the rid
168
220
  # Operation is performed as Database-Transaction and is rolled back on error
169
- $ DB.insert <name>, attribute: value .... # Inserts a new <Document | Vertex> and returns the new object
170
- $ DB.create_edge <name>, from: <rid> or [rid, rid, ..] , to: <rid> or [rid, rid, ..]
221
+ $ db.insert <name>, attribute: value .... # Inserts a new <Document | Vertex> and returns the new object
222
+ $ db.create_edge <name>, from: <rid> or [rid, rid, ..] , to: <rid> or [rid, rid, ..]
171
223
 
172
224
  ```
173
225
 
174
226
  **Convert database input to Arcade::Base Models**
175
227
 
176
- Either `DB.query` or `DB.execute` return the raw JSON-input from the database. It can always converted to model-objects by chaining
228
+ Either `db.query` or `db.execute` return the raw JSON-input from the database. It can always converted to model-objects by chaining
177
229
  `allocate_model` or `select_result`.
178
230
 
179
231
  ```ruby
180
- $ DB.query "select from my_names limit 1"
232
+ $ db.query "select from my_names limit 1"
181
233
  # which is identical to
182
- $ My::Names.query( limit: 1).query
234
+ $ My::Names.query( limit: 1).execute
183
235
  => [{:@out=>0, :@rid=>"#225:6", :@in=>0, :@type=>"my_names", :@cat=>"v", :name=>"Zaber"}]
184
236
  # then
185
- $ My::Names.query( limit: 1).query.allocate_model.to_human
237
+ $ My::Names.query( limit: 1).execute.allocate_model.to_human
186
238
  => ["<my_names[#225:6]: name: Zaber>" ]
187
239
  # replaces the hash with a My::Names Object
188
240
  ```
189
241
 
242
+
190
243
  The **Base Layer** implements a low level access to the database API.
191
244
 
192
245
  ```ruby
@@ -306,7 +359,29 @@ db.commit
306
359
  db.rollback
307
360
 
308
361
  ```
362
+ ### Audit proofed Bookings
363
+
364
+ Although ArcadeDB is a mutable database, the ruby-Interface supports basic audit proofed transactions.
365
+ Two specialized Database-Types are included:
366
+
367
+ * Arcade::RevisionRecord – A document class, intended to be embedded in Revision-Records
368
+ * Arcade::Revision – A vertex-class with specialized `insert-` and `update-` methods
309
369
 
370
+ If a vertex is based on `Arcade::Revision`, a `protocol`-property is included. Its initialised with a single `Arcade::RevisionRecord` Object. Each `update` adds an `Arcade::RevisionRecord` to the protocol stack.
371
+
372
+ ```ruby
373
+ m= Member.insert( name: "Hubert",
374
+ surname: "Hugo",
375
+ birth: Date.new( 1976,3,15 ) ) { 'record initiated' }
376
+
377
+ m.update( birth: Date.new( 1978,3,15 ) ) { "member provided correct date via mail" }
378
+
379
+ m.protocol => Date | User | Action | old
380
+ --------------------------------------------------------------------------------
381
+ => 1.1.2024 | root | record initiated |
382
+ => 1.2.2024 | root | member provided correct date via mail | birth: "1976-3-15"
383
+
384
+ ```
310
385
 
311
386
  ## Contributing
312
387
 
data/arcade.yml CHANGED
@@ -3,20 +3,20 @@
3
3
  :test:
4
4
  dbname: playground
5
5
  user: root
6
- pass: ***
6
+ pass: topo1focus
7
7
  :development:
8
8
  dbname: devel
9
9
  user: root
10
- pass: ***
10
+ pass: topo1focus
11
11
  :production:
12
12
  dbname: production
13
13
  user: root
14
- pass: ***
14
+ pass: topo1focus
15
15
  :admin:
16
- :host: 10.247.8.109
16
+ :host: localhost
17
17
  :port: 2480
18
18
  :user: root
19
- :pass: ***
19
+ :pass: topo1focus
20
20
  :logger: stdout # 'file' or 'stdout'
21
21
  :namespace: Arcade # Default Namespace
22
22
  :autoload: true # load model if a link is detected in a record
data/bin/console CHANGED
@@ -9,6 +9,7 @@
9
9
  require 'bundler/setup'
10
10
  #require 'terminal-table'
11
11
  require 'zeitwerk'
12
+ ProjectRoot = Pathname.new( Dir.pwd ).parent
12
13
  require 'pastel'
13
14
  require 'arcade'
14
15
  #require 'pry'
@@ -32,6 +32,13 @@ 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
@@ -54,23 +61,6 @@ module Arcade
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, session_id: nil, **attributes
67
- payload = { "@type" => type }.merge( attributes )
68
- if session_id.nil?
69
- post_data "document/#{database}", payload
70
- else
71
- post_transaction "document/#{database}", payload, session_id: session_id
72
- end
73
- end
74
64
 
75
65
  # ------------------------------ execute ------------------------------------------------- #
76
66
  # executes a sql-query in the specified database
@@ -94,13 +84,28 @@ module Arcade
94
84
  # ------------------------------ query ------------------------------------------------- #
95
85
  # same for idempotent queries
96
86
  def self.query database, query, session_id: nil
97
- if session_id.nil?
98
- post_data "query/#{database}" , provide_payload(query)
99
- else
100
- post_transaction "query/#{database}" , provide_payload(query), session_id: session_id
101
- end
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
102
92
  end
103
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
+
104
109
  # ------------------------------ get_record ------------------------------------------------- #
105
110
  # fetches a record by providing database and rid
106
111
  # and returns the result as hash
@@ -114,7 +119,7 @@ module Arcade
114
119
  rid = rid.join(':')
115
120
  rid = rid[1..-1] if rid[0]=="#"
116
121
  if rid.rid?
117
- get_data "document/#{database}/#{rid}"
122
+ get_data( "query/#{database}/sql/" + URI.encode_uri_component("select from #{rid}")) &.first
118
123
  else
119
124
  raise Error "Get requires a rid input"
120
125
  end
@@ -181,7 +186,11 @@ module Arcade
181
186
  the_yield.map do | key, value |
182
187
  case key
183
188
  when :query
184
- action == :post ? [ :command, value ] : [ :query, value ]
189
+ if action == :post
190
+ [ :command, value ]
191
+ else
192
+ [ :query, URI.encode_uri_component(value )]
193
+ end
185
194
  when :limit
186
195
  [ :limit , value ]
187
196
  when :params
@@ -9,6 +9,12 @@ module Arcade
9
9
 
10
10
  def http
11
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)
@@ -49,7 +55,7 @@ module Arcade
49
55
 
50
56
  # ------------------------------ post data -------------------------------------------------------- #
51
57
  def post_data command, payload
52
- case response = http.post( Config.base_uri + command, json: payload )
58
+ case response = http.post( Config.base_uri + command, json: payload )
53
59
  in {status: 200..299}
54
60
  # success
55
61
  JSON.parse( response.body, symbolize_names: true )[:result]
@@ -64,7 +70,7 @@ module Arcade
64
70
  sleep 1
65
71
  post_data command, payload
66
72
  else
67
- raise Arcade::QueryError.new **response.json( symbolize_names: true )
73
+ raise Arcade::QueryError.new **response.json( symbolize_names: true )
68
74
  end
69
75
  else
70
76
  # # http error
@@ -87,6 +93,7 @@ module Arcade
87
93
  # ------------------------------ post transaction ------------------------------------------------- #
88
94
  def post_transaction command, params, session_id:
89
95
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
96
+ puts "params #{params.inspect}"
90
97
  case response = http_a.post( Config.base_uri + command, json: params )
91
98
  in {status: 200..299}
92
99
  # success
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
@@ -294,16 +298,19 @@ module Arcade
294
298
  Query.new( **{ from: self }.merge(args) )
295
299
  end
296
300
 
297
- # immutable support
298
- # to make a database type immutable add
301
+ # ## Immutable Support
302
+ #
303
+ # To make a database type immutable add
299
304
  # `not_permitted :update, :upsert, :delete`
300
305
  # to the model-specification
301
306
  #
307
+ # Even after applying `not_permitted` the database-type can be modified via class-methods.
308
+ #
302
309
  def not_permitted *m
303
310
  m.each do | def_m |
304
- define_method( def_m ) do | v = nil |
305
- raise ArcadeImmutableError "operation not permitted", caller
306
- end
311
+ define_method( def_m ) do | v = nil |
312
+ raise Arcade::ImmutableError.new( "operation #{def_m} not permitted" )
313
+ end
307
314
  end
308
315
  end
309
316
 
@@ -366,8 +373,6 @@ module Arcade
366
373
  end
367
374
 
368
375
  def to_human
369
-
370
-
371
376
  "<#{ self.class.to_s.snake_case }" + rid? ? "[#{ rid }]: " : " " + invariant_attributes.map do |attr, value|
372
377
  v= case value
373
378
  when Base
@@ -428,7 +433,7 @@ module Arcade
428
433
 
429
434
  def update **args
430
435
  Query.new( from: rid , kind: :update, set: args).execute
431
- refresh
436
+ refresh # return the updated record (the object itself is untouched!)
432
437
  end
433
438
 
434
439
  # inserts or updates a embedded document
@@ -448,21 +453,37 @@ module Arcade
448
453
  db.transmit { " update #{rid} set `#{embedded}`.`#{embedded_property}` = #{value.to_or}" }
449
454
  end
450
455
 
451
- def update_list list, value
452
- 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
453
469
  value.to_json
454
470
  else
455
471
  value.to_or
456
472
  end
457
- if send( list ).nil? || send( list ).empty?
458
- db.transmit { "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 }]" }
459
479
  else
460
480
  db.transmit { "update #{ rid } set #{ list } += #{ value }" }
461
481
  end
462
- refresh
482
+ # refresh
463
483
  end
464
484
 
465
485
  # updates a map property , actually adds the key-value pair to the property
486
+ ## does not work on reduced model records
466
487
  def update_map m, key, value
467
488
  if send( m ).nil?
468
489
  db.transmit { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) " }
@@ -157,7 +157,7 @@ module Arcade
157
157
 
158
158
 
159
159
 
160
- # ------------ create -----------
160
+ # ------------ create ----------- ## not supported anymore by the api
161
161
  # returns an rid of the successfully created vertex or document
162
162
  #
163
163
  # Parameter: name of the vertex or document type
@@ -192,13 +192,14 @@ module Arcade
192
192
  elsif content_params.empty?
193
193
  logger.error "Nothing to Insert"
194
194
  else
195
- content = "CONTENT #{ content_params.to_json }"
195
+ content = "CONTENT #{ content_params.to_json }"
196
196
  target = target_params.map{|y,z| y==:type ? z : "#{y.to_s} #{ z } "}.join
197
- result = Api.execute( database, session_id: session ){ "INSERT INTO #{target} #{content} "}
197
+ result = Api.execute( database, session_id: session ){ "INSERT INTO #{target} #{content} "}
198
198
  result &.first.allocate_model(false)
199
199
  end
200
200
  end
201
201
 
202
+
202
203
  # ------------------------------ get ------------------------------------------------------ #
203
204
  # Get fetches the record associated with the rid given as parameter.
204
205
  #
@@ -224,7 +225,7 @@ module Arcade
224
225
  end
225
226
  end
226
227
 
227
- # ------------------------------ get ------------------------------------------------------ #
228
+ # ------------------------------ delete ------------------------------------------------------ #
228
229
  #
229
230
  # Delete the specified rid
230
231
  #
@@ -272,7 +273,7 @@ module Arcade
272
273
  end
273
274
  rescue Dry::Struct::Error, Arcade::QueryError => e
274
275
  Api.rollback database, session_id: s, log: false
275
- logger.info "Execution FAILED --> Status #{e.status}"
276
+ logger.fatal "Execution FAILED --> Status #{e}"
276
277
  [] # return empty result
277
278
  end
278
279
 
data/lib/arcade/init.rb CHANGED
@@ -14,7 +14,8 @@ module Arcade
14
14
  #
15
15
  class Init
16
16
  extend Dry::Core::ClassAttributes
17
- defines :db # database handle
17
+ defines :db # database handle
18
+ defines :models # static array of database name for arcade-model classes
18
19
 
19
20
  def self.connect e= :development
20
21
 
@@ -25,10 +26,12 @@ module Arcade
25
26
  else
26
27
  :development
27
28
  end
28
- # set the class attribute
29
29
 
30
+ # set the class attributes
31
+ models Base.descendants.map{|x| x.to_s.split("Arcade::").last }
30
32
  db Database.new(env)
31
33
  end
34
+
32
35
  end
33
36
 
34
37
  # Provides method `db` to every Model class
data/lib/arcade/match.rb CHANGED
@@ -4,22 +4,64 @@ module Arcade
4
4
  include Arcade::Support::Sql
5
5
 
6
6
  =begin
7
- This is a very simple wrapper for the match statement
7
+ This is a wrapper for the match statement
8
8
 
9
9
  Initialize: a= Arcade::Match.new type: Arcade::DatabaseType, where: { property: 'value' }, as: :alias
10
10
  Complete: b = a.out( Arcade::EdgeType ).node( while: true, as: item )[ .in.node ... ]
11
11
  Inspect b.to_s
12
12
  Query DB b.execute [.allocate_model]
13
13
  [.analyse_result]
14
+ Customize the return values:
15
+ b.to_s{ "customized return statement" }
16
+ b.execute{ "customized return statment" }
17
+
18
+ Conditions on edges:
19
+ m = Match.new type: Arcade::DatabaseType
20
+ m.inE via: Arcade::Edge || via: [Arcade::Edge1, Arcade::Edge2], where: { var: 3..6 }
21
+ ---> inE('edge(1)'){ where: (var between 3 and 6 )}.outV('edge(2)')
22
+
23
+ Address a single database record:
24
+ m = Match.new( vertex: { Arcade::Vertex-Instance } )
25
+ .....
26
+ .node( vertex: { Arcade::Vertex-Instance } ) # instead of where statement
27
+
28
+ Example
29
+
30
+ symbol_or_title = symbol.present? ? { :symbol => symbol } : { :title => title }
31
+ where = { :right => right }
32
+
33
+ a= Arcade::Match.new( type: self.class, where: symbol_or_title )
34
+ .out( Arcade::HasContract )
35
+ .node( as: :c, where: where )
36
+ a.execute do "c.last_trading_day as expiry, # c is returned form the query [as: :c]
37
+ count(c) as contracts,
38
+ min(c.strike) as s_min,
39
+ max(c.strike) as s_max
40
+ group by c.last_trading_day order by c.last_trading_day"
41
+ end
14
42
 
15
- =end
16
43
 
17
- def initialize type: , **args
44
+ =end
18
45
 
46
+ def initialize **args
47
+ type = args.delete :type
48
+ vertex = args.delete :vertex
49
+ rid = args.delete :rid
50
+ raise "MATCH#new:: parameter rid is not supported. Use `vertex: Arcade::Vertex.instance` instead." if rid.present?
19
51
  @args = args
20
52
  @as = []
21
53
 
22
- @stack = [ "MATCH { type: #{type.database_name}, #{ assigned_parameters } }" ]
54
+ @stack = if vertex.is_a?( Arcade::Vertex ) && args.empty?
55
+ [ "MATCH { type: #{vertex.class.database_name }, rid: #{vertex.rid} }" ]
56
+ elsif vertex.is_a?( Arcade::Vertex )
57
+ [ "MATCH { type: #{vertex.class.database_name }, rid: #{vertex.rid}, #{ assigned_parameters } }" ]
58
+ elsif type.is_a?( Class) && type.ancestors.include?(Arcade::Vertex) && args.empty?
59
+ [ "MATCH { type: #{type.database_name} }" ]
60
+ elsif type.is_a?( Class) && type.ancestors.include?(Arcade::Vertex)
61
+ [ "MATCH { type: #{type.database_name}, #{ assigned_parameters } }" ]
62
+ else
63
+ raise "Match:: Either type (Arcade::Vertex-Class) or vertex (Arcade::Vertex-Object) is required as parameter"
64
+ end
23
65
 
24
66
  return self
25
67
  end
@@ -49,49 +91,55 @@ module Arcade
49
91
  end
50
92
 
51
93
 
52
- # todo : metaprogramming!
53
- def out edge=""
54
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
55
- @stack << ".out(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
56
- return self
57
- end
58
- def in edge=""
59
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
60
- @stack << ".in(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
61
- return self
62
- end
63
- def both edge=""
64
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
65
- @stack << ".both(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
66
- return self
94
+
95
+ def self.define_base_edge *direction
96
+ direction.each do | e_d |
97
+ define_method e_d do | edge = "" |
98
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
99
+ @stack << ".#{ e_d }(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
100
+ self
101
+ end
102
+ end
67
103
  end
68
104
 
105
+
69
106
  # add conditions on edges to the match statement
70
- def inE edge="", **args
71
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
72
- @stack << ".inE(#{edge.is_a?(Class) ? edge.database_name.to_or : ''}) #{assigned_parameters}.outV()"
73
- return self
74
- end
75
- def outE edge="", **args
76
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
77
- @stack << ".outE(#{edge.is_a?(Class) ? edge.database_name.to_or : ''}) #{assigned_parameters}.inV()"
78
- return self
79
- end
80
- def bothE edge="", **args
81
- raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
82
- @stack << ".bothE(#{edge.is_a?(Class) ? edge.database_name : ''}) #{assigned_parameters}.bothV()"
83
- return self
107
+ def self.define_inner_edge start_dir, final_dir
108
+ define_method start_dir do | edge = "", **a |
109
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
110
+
111
+ print_edges = -> (e){ e.is_a?(Class) ? e.database_name.to_or : ''}
112
+ edges = [ a.delete(:via) ].flatten
113
+ edges << edge unless edge == ''
114
+ n = if a.empty?
115
+ ""
116
+ else
117
+ @args = a
118
+ "{ #{ assigned_parameters } }"
119
+ end
120
+ @stack << ".#{ start_dir }(#{print_edges.call(edges.first)})#{ n }.#{ final_dir }(#{print_edges.call(edges.last)})"
121
+ return self
122
+ end
123
+
84
124
  end
85
125
 
126
+ define_base_edge :out, :in, :both
127
+ define_inner_edge :inE, :outV
128
+ define_inner_edge :outE, :inV
129
+
130
+
86
131
 
87
132
  # general declation of a node (ie. vertex)
88
133
  def node **args
134
+ vertex = args.delete :vertex
135
+ rid = args.delete :rid
136
+ raise "MATCH#node:: parameter rid is not supported. Use `vertex: Arcade::Vertex.instance` instead." if rid.present?
89
137
  @args = args
90
138
  @stack << if args.empty?
91
- "{}"
92
- else
93
- "{ #{ assigned_parameters } }"
94
- end
139
+ vertex.present? ? "{ type: #{vertex.class.database_name }, rid: #{vertex.rid} }" : "{}"
140
+ else
141
+ vertex.present? ? "{ type: #{vertex.class.database_name }, rid: #{vertex.rid}, #{assigned_parameters} }" : "{ #{ assigned_parameters } }"
142
+ end
95
143
  return self
96
144
  end
97
145
 
data/lib/arcade/query.rb CHANGED
@@ -27,7 +27,8 @@ module Arcade
27
27
  [], # aliases
28
28
  '', # database
29
29
  [], #set,
30
- [] # remove
30
+ [], # remove
31
+ [] # group by
31
32
  args.each{|k,v| send k, v}
32
33
  @fill = block_given? ? yield : 'and'
33
34
  end
@@ -62,12 +63,10 @@ module Arcade
62
63
  @q[:kind]
63
64
  end
64
65
  end
65
- =begin
66
- Output the compiled query
67
- Parameter: destination (rest, batch )
68
- If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
69
- =end
70
-
66
+ # ------------------ compose---------------------------------------
67
+ # Output the compiled query
68
+ # Parameter: destination (rest, batch )
69
+ # If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
71
70
  def compose(destination: :batch)
72
71
  if kind.to_sym == :update
73
72
  return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
@@ -126,9 +125,8 @@ module Arcade
126
125
  end
127
126
  end
128
127
 
129
- =begin
130
- from can either be a Databaseclass to operate on or a Subquery providing data to query further
131
- =end
128
+ # -------------------- from ---------------------------------------
129
+ # arg can either be a Database class to operate on or a Subquery providing data to query further
132
130
  def from arg = nil
133
131
  if arg.present?
134
132
  @q[:database] = arg
@@ -315,11 +313,8 @@ end # class << self
315
313
  self
316
314
  end
317
315
 
318
- # # connects by adding {in_or_out}('edgeClass')
319
- # def connect_with in_or_out, via: nil
320
- # argument = " #{in_or_out}(#{via.to_or if via.present?})"
321
- # end
322
316
 
317
+ # ------------------ nodes ---------------------------------------
323
318
  # adds a connection
324
319
  # in_or_out: :out ---> out('edgeClass')[where-condition]
325
320
  # :in ---> in('edgeClass')[where-condition]
@@ -335,7 +330,7 @@ end # class << self
335
330
 
336
331
  def nodes in_or_out = :out, via: nil, where: nil, expand: false
337
332
 
338
- condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
333
+ condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
339
334
  via = resolve_edge_name(via) unless via.nil?
340
335
 
341
336
  argument = if in_or_out.to_s[-1] == 'E'
@@ -15,35 +15,31 @@ module Arcade
15
15
  end.join(',')
16
16
  end
17
17
 
18
-
19
18
  # used by array#allocate_model
20
19
  def _allocate_model response=nil, auto = Config.autoload
21
20
 
22
-
23
21
  if response.is_a? Hash
24
22
  # save rid to a safe place
25
- temp_rid = response.delete :"@rid"
26
- type = response.delete :"@type"
27
- cat = response.delete :"@cat"
23
+ temp_rid = response.delete( :"@rid" ) || response.delete( :rid ) || "#0:0"
24
+ type = response.delete( :"@type" ) || response.delete( :type ) || nil
25
+ cat = response.delete( :"@cat" ) || response.delete( :cat ) || "d"
28
26
 
29
27
  return response if type.nil?
30
- temp_rid = "#0:0" if temp_rid.nil?
31
- type = "d" if type.nil?
32
28
  # extract type infos and convert to database-name
33
29
  namespace, type_name = type.camelcase_and_namespace
34
30
  namespace = self.namespace if namespace.nil?
35
31
  # autoconvert rid's in attributes to model-records (exclude edges!)
36
32
  if auto && !(cat.to_s =='e')
37
- response.transform_values! do |x|
33
+ response.transform_values do |x|
38
34
  case x
39
35
  when String
40
36
  x.rid? ? x.load_rid : x # follow links
41
37
  when Array
42
38
  a =[]
43
- x.map do| y |
44
- # Thread.new do ## thread or fiber decrease the performance significantly.
39
+ x.map do | y |
40
+ # Thread.new do ## using thread or fiber decreases the performance significantly.
45
41
  if y.is_a?(Hash) && y.include?(:@type) # if embedded documents are present, load them
46
- y.allocate_model(false)
42
+ _allocate_model(y,false)
47
43
  elsif y.rid? # if links are present, load the object
48
44
  y.load_rid(false) # do not autoload further records, prevents from recursive locking
49
45
  else
@@ -82,7 +78,7 @@ module Arcade
82
78
  elsif response.rid?
83
79
  # Autoload rid's
84
80
  response.load_rid
85
- # auto ? response.load_rid : response
81
+ # auto ? response.load_rid : response
86
82
  else
87
83
  response
88
84
  end
@@ -44,7 +44,7 @@ If »NULL« should be addressed, { key: nil } is translated to "key = NULL" (us
44
44
  attributes.map do |key, value|
45
45
  case value
46
46
  when nil
47
- "#{key}=NULL"
47
+ "#{key} is NULL"
48
48
  when ::Array
49
49
  if value == [nil]
50
50
  "#{key} is NULL"
@@ -11,9 +11,12 @@ module Arcade
11
11
  delimiters = Regexp.union(['-', '_'])
12
12
  n,c= self.split(delimiters).then { |first, *rest| [first.tap {|s| s[0] = s[0].upcase}, rest.map(&:capitalize).join] }
13
13
  ## if base is the first part of the type-name, probably Arcade::Base is choosen a namespace. thats wrong
14
- namespace_present = unless n == 'Base'
14
+ # Arcade::Base descendants are prefetched in Init.models
15
+ # They have to be fetched to load both `Arcade::Ge` and `Arcade::GeSomething`
16
+ # (otherwise: Arcade::Ge and Arcade::Ge::Something)
17
+ namespace_present = unless n == 'Base' || Arcade::Init.models.include?(n)
15
18
  Object.const_get(n) rescue false # Database.namespace
16
- else
19
+ else
17
20
  false
18
21
  end
19
22
  # if no namespace is found, leave it empty and return the capitalized string as class name
@@ -22,6 +25,10 @@ module Arcade
22
25
  [ Database.namespace, self.capitalize ]
23
26
  end
24
27
  end
28
+ def camelcase
29
+ # Split the string into words, capitalize each word, and join them together
30
+ self.split('_').map(&:capitalize).join
31
+ end
25
32
 
26
33
  def snake_case
27
34
  n= if split('::').first == Database.namespace.to_s
@@ -51,7 +58,7 @@ module Arcade
51
58
  end
52
59
  # return a valid rid (format: "nn:mm") or nil
53
60
  def rid
54
- self["#"].nil? ? "#"+ self : self if rid?
61
+ self["#"].nil? ? "#"+ self : self if rid?
55
62
  end
56
63
 
57
64
  def where **args
@@ -80,7 +87,7 @@ module Arcade
80
87
  ## Load the database object if the string is a rid
81
88
  # (Default: No Autoloading of rid-links)
82
89
  def load_rid autocomplete = false
83
- db.get( self ){ autocomplete } if rid? rescue nil
90
+ db.get( self.strip ){ autocomplete } if rid? rescue nil
84
91
  end
85
92
 
86
93
  # updates the record ### retired in favour of Arcade::Base.update
@@ -104,13 +111,3 @@ module Arcade
104
111
  end
105
112
 
106
113
  String.include Arcade::Support::String
107
-
108
- module Types
109
- include Dry.Types()
110
-
111
- # include in attribute definitions
112
- Rid = String.constrained( format: /\A[#]{1}[0-9]{1,}:[0-9]{1,}\z/ )
113
- Blockchain = String.constrained( format: /^(algo|eth|btc)$/ ) # add other blockchain symbols here
114
- Email = String.constrained( format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i )
115
-
116
- end
@@ -1,3 +1,3 @@
1
1
  module Arcade
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
data/lib/arcade.rb CHANGED
@@ -9,13 +9,13 @@ require "dry/core/class_builder"
9
9
  require "dry/core/class_attributes"
10
10
  require 'json'
11
11
 
12
- module Types
13
- include Dry.Types()
14
- end
12
+ require_relative '../lib/types'
13
+ #module Types
14
+ # include Dry.Types()
15
+ #end
15
16
  require 'yaml'
16
17
  require 'securerandom'
17
18
  require 'httpx'
18
- require 'arcade/errors'
19
19
  require_relative '../lib/arcade/support/object'
20
20
  require_relative '../lib/arcade/support/string'
21
21
  require_relative '../lib/arcade/support/class'
@@ -24,6 +24,7 @@ require_relative '../lib/arcade/support/model'
24
24
  require_relative '../lib/arcade/support/conversions'
25
25
  require_relative '../lib/arcade/api/primitives'
26
26
  require_relative '../lib/arcade/api/operations'
27
+ require_relative '../lib/arcade/errors'
27
28
  require_relative '../lib/arcade/base'
28
29
  require_relative '../lib/arcade/logging'
29
30
  require_relative '../lib/arcade/config'
@@ -11,7 +11,7 @@ module Arcade
11
11
  end
12
12
 
13
13
  =begin
14
- Document.delete fires a "delete vertex" command to the database.
14
+ Document.delete fires a "delete" command to the database.
15
15
  To remove all records use »all: true« as argument
16
16
 
17
17
  The "where" parameter is optional
data/lib/model/edge.rb CHANGED
@@ -27,6 +27,23 @@ module Arcade
27
27
 
28
28
  end
29
29
 
30
+
31
+ ## class-method: Delete Edge between two vertices
32
+ ##
33
+ ## returns the count of deleted edges
34
+ ##
35
+ ## todo: add conditions
36
+ def self.delete from:, to:
37
+ return 0 if from.nil? || to.nil?
38
+ raise "parameter ( from: ) must be a String or a Vertex" unless from.is_a?(String) || from.is_a?( Arcade::Vertex)
39
+ raise "parameter ( to: ) must be a String or a Vertex" unless to.is_a?(String) || to.is_a?( Arcade::Vertex)
40
+ raise "parameters (from: + to:) must respond to `.rid`" unless from.respond_to?( :rid) && to.respond_to?(:rid)
41
+
42
+ db.execute{ "delete edge from #{from.rid} to #{to.rid} " } &.select_result &.first
43
+ end
44
+
45
+
46
+ ## instance method: Delete specified edge
30
47
  def delete
31
48
  db.execute{ "delete edge #{ rid }" }.select_result
32
49
  end
@@ -0,0 +1,41 @@
1
+ module Arcade
2
+ class Revision < Arcade::Vertex
3
+ attribute :protocol?, Types::Nominal::Array
4
+
5
+ @@u='root' # default user
6
+ @@i = 'Record initiated'
7
+
8
+ def self.set_user u
9
+ @@u = u
10
+ end
11
+
12
+ def self.set_initial_message m
13
+ @@i = m
14
+ end
15
+
16
+ def self.insert **attr
17
+
18
+ action = block_given? ? yield : @@i
19
+ i= super **attr
20
+ i.update_list :protocol,
21
+ Arcade::RevisionRecord.create( user: @@u, action: action ),
22
+ modus: :first
23
+ i.refresh
24
+
25
+ end
26
+
27
+
28
+ def update **attr
29
+ hist_state = attr.keys.map do |k|
30
+ [ k, send( k ) ]
31
+ end.to_h
32
+ super **attr
33
+ revision = if block_given?
34
+ Arcade::RevisionRecord.create( user: @@u, action: yield, fields: hist_state )
35
+ else
36
+ Arcade::RevisionRecord.create( fields: hist_state )
37
+ end
38
+ update_list :protocol, revision, modus: :append
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ module Arcade
2
+ class RevisionRecord < Document
3
+ attribute :user, Types::Nominal::String.default( 'root'.freeze )
4
+ attribute :action, Types::Nominal::String.default( "Property changed".freeze )
5
+ attribute :date, Types::Nominal::Date.default( Date.today.freeze )
6
+ attribute :fields?, Types::Nominal::Hash
7
+
8
+ # Revision Records are always embedded.
9
+ # create and insert methods return an new ruby record
10
+ def self.create **attr
11
+ new **( { rid: '#0:0', date: Date.today}.merge attr )
12
+ end
13
+ def self.insert **attr
14
+ new **( { rid: '#0:0', date: Date.today}.merge **attr )
15
+ end
16
+
17
+ =begin
18
+ Its not allowed to delete records.
19
+ =end
20
+ def self.delete where: {} , **args
21
+ raise ArcadeQueryError, "Its not possible to delete revision records"
22
+ end
23
+
24
+ def delete
25
+ raise ArcadeQueryError, "Its not possible to delete revision records"
26
+ end
27
+
28
+ end
29
+ end
data/lib/model/vertex.rb CHANGED
@@ -213,6 +213,50 @@ or
213
213
  nil
214
214
  end
215
215
 
216
+ =begin
217
+ ----------------------------------- Match ------------------------------------------
218
+ (ClassMethod)
219
+
220
+ Creates a Match-Statement based on the vertex type
221
+
222
+ ```
223
+ m = Watchlist.match( symbol: 'iBit', as: :ibit )
224
+ .out( HasUnderlying)
225
+ .node( as: :u )
226
+
227
+ m.to_s
228
+ => "MATCH { type: watchlist, where: ( symbol='iBit' ), as: ibit }
229
+ .out('has_underlying'){ as: u }
230
+ RETURN ibit,u "
231
+ ```
232
+ =end
233
+ def self.match **args
234
+ as = args.delete( :as) || :a
235
+ args = args.delete( :where ) if args.key?( :where )
236
+ Arcade::Match.new type: self, where: args, as: as
237
+ end
238
+
239
+ =begin
240
+ ----------------------------------- Match ------------------------------------------
241
+ (InstanceMethod)
242
+
243
+ Creates a Match-Statement based on the current vertex ( i.e as response to a website-request )
244
+
245
+ ```
246
+ params[:rid] => "#49:0"
247
+
248
+ m= params[:rid].load_rid.match.out( HasUnterlying ).node( as: :u )
249
+ m.to_s
250
+ => "MATCH { type: strategie, rid: #49:0 }.out('has_underlying'){ as: u } RETURN u "
251
+ ```
252
+ =end
253
+ def match as: nil
254
+ if as.nil?
255
+ Arcade::Match.new vertex: self
256
+ else
257
+ Arcade::Match.new vertex: self, as: as
258
+ end
259
+ end
216
260
 
217
261
  def remove
218
262
  db.execute{ "delete vertex #{rid}" }
@@ -224,7 +268,7 @@ Format: < Classname: Edges, Attributes >
224
268
  =end
225
269
  def to_human
226
270
 
227
- in_and_out = "{#{inE.count}->}{->#{outE.count }}, "
271
+ in_and_out = coe.then{| i,o| "{#{i}->}{->#{o}}, "}
228
272
 
229
273
  #Default presentation of Arcade::Base::Model-Objects
230
274
 
@@ -244,6 +288,13 @@ Format: < Classname: Edges, Attributes >
244
288
  end
245
289
 
246
290
 
291
+ # Count Of Edges (coe)
292
+ # returns an Array: [ count-of-in-edges, count-of-out-edges ]
293
+ def coe
294
+ db.query( "select in().size() as ie, out().size() as oe from #{rid}" ) &.first.values
295
+ end
296
+
297
+
247
298
  def refresh
248
299
  # force reloading of edges and nodes
249
300
  # edges are not cached (now)
data/lib/models.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require_relative "../lib/model/document.rb"
2
+ require_relative "../lib/model/revision_record.rb"
2
3
  require_relative "../lib/model/vertex.rb"
3
4
  require_relative "../lib/model/edge.rb"
5
+ require_relative "../lib/model/revision.rb"
4
6
  require_relative "../lib/model/basicvertex.rb"
5
7
  require_relative "../lib/model/basicdocument.rb"
6
8
  require_relative "../lib/model/basicedge.rb"
data/lib/types.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Types
2
+ include Dry.Types()
3
+
4
+ # include in attribute definitions
5
+ Rid = String.constrained( format: /\A[#]{1}[0-9]{1,}:[0-9]{1,}\z/ )
6
+ Blockchain = String.constrained( format: /^(algo|eth|btc)$/ ) # add other blockchain symbols here
7
+ Email = String.constrained( format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i )
8
+
9
+
10
+ # Define a type for your DateTime with timezone. Crucially, use
11
+ # the `.constructor` to handle the string parsing and potential errors. (source: Gemini)
12
+ Types::DateTime = Types::Any.constructor do |value|
13
+ begin
14
+ case value
15
+ when String
16
+ ::DateTime.parse(value)
17
+ when Integer
18
+ ::Time.at(value).to_datetime
19
+ else
20
+ ::DateTime.now
21
+ end
22
+ rescue ArgumentError
23
+ # Handle invalid date strings. Options:
24
+ # 1. Raise an exception (recommended for API responses)
25
+ raise Dry::Types::CoercionError.new(
26
+ value, DateTime, "Invalid DateTime format: #{value}"
27
+ )
28
+ # 2. Return nil (less strict, but might lead to unexpected behavior)
29
+ # nil
30
+ # 3. Use a default value (be careful with this, as it might mask errors)
31
+ # DateTime.now
32
+ end
33
+ end
34
+
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arcadedb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hartmut Bischoff
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-12 00:00:00.000000000 Z
11
+ date: 2025-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -170,8 +170,11 @@ files:
170
170
  - lib/model/basicvertex.rb
171
171
  - lib/model/document.rb
172
172
  - lib/model/edge.rb
173
+ - lib/model/revision.rb
174
+ - lib/model/revision_record.rb
173
175
  - lib/model/vertex.rb
174
176
  - lib/models.rb
177
+ - lib/types.rb
175
178
  - rails.md
176
179
  - rails/arcade.rb
177
180
  - rails/config.yml
@@ -180,7 +183,7 @@ homepage: https://github.com/topofocus/arcadedb
180
183
  licenses:
181
184
  - MIT
182
185
  metadata: {}
183
- post_install_message:
186
+ post_install_message:
184
187
  rdoc_options: []
185
188
  require_paths:
186
189
  - lib
@@ -195,8 +198,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  - !ruby/object:Gem::Version
196
199
  version: '0'
197
200
  requirements: []
198
- rubygems_version: 3.4.6
199
- signing_key:
201
+ rubygems_version: 3.5.23
202
+ signing_key:
200
203
  specification_version: 4
201
204
  summary: Ruby Interface to ArcadeDB
202
205
  test_files: []