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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +98 -23
- data/arcade.yml +5 -5
- data/bin/console +1 -0
- data/lib/arcade/api/operations.rb +33 -24
- data/lib/arcade/api/primitives.rb +9 -2
- data/lib/arcade/base.rb +34 -13
- data/lib/arcade/database.rb +6 -5
- data/lib/arcade/init.rb +5 -2
- data/lib/arcade/match.rb +85 -37
- data/lib/arcade/query.rb +10 -15
- data/lib/arcade/support/model.rb +8 -12
- data/lib/arcade/support/sql.rb +1 -1
- data/lib/arcade/support/string.rb +11 -14
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +5 -4
- data/lib/model/document.rb +1 -1
- data/lib/model/edge.rb +17 -0
- data/lib/model/revision.rb +41 -0
- data/lib/model/revision_record.rb +29 -0
- data/lib/model/vertex.rb +52 -1
- data/lib/models.rb +2 -0
- data/lib/types.rb +35 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f030932c1444832b3ea224120f71cc655837ca732717b896ed8587a8cefc0dd4
|
4
|
+
data.tar.gz: 96f05185b21af4820ebc667d796635e4b0c6c3ac42b59dc360212df7b287934e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
101
|
-
f = Person.
|
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,
|
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
|
-
|
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,
|
134
|
+
is still valid, but the class has changed to `Arcade::Query`.
|
135
135
|
|
136
|
-
|
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)
|
139
|
-
necessary.
|
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
|
-
|
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
|
-
|
213
|
+
db = Arcade::Init.db
|
162
214
|
|
163
|
-
$
|
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
|
-
$
|
166
|
-
$
|
167
|
-
$
|
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
|
-
$
|
170
|
-
$
|
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 `
|
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
|
-
$
|
232
|
+
$ db.query "select from my_names limit 1"
|
181
233
|
# which is identical to
|
182
|
-
$ My::Names.query( limit: 1).
|
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).
|
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:
|
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
@@ -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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
298
|
-
#
|
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(
|
305
|
-
|
306
|
-
|
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
|
-
|
452
|
-
|
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
|
458
|
-
db.
|
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
|
-
|
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 } ) " }
|
data/lib/arcade/database.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
-
# ------------------------------
|
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.
|
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
|
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
|
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
|
-
|
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 =
|
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
|
-
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
93
|
-
"{ #{ assigned_parameters } }"
|
94
|
-
|
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
|
-
|
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
|
-
|
130
|
-
|
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? ?
|
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'
|
data/lib/arcade/support/model.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
81
|
+
# auto ? response.load_rid : response
|
86
82
|
else
|
87
83
|
response
|
88
84
|
end
|
data/lib/arcade/support/sql.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/arcade/version.rb
CHANGED
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
|
-
|
13
|
-
|
14
|
-
|
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'
|
data/lib/model/document.rb
CHANGED
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 = "{#{
|
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.
|
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:
|
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.
|
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: []
|