arcadedb 0.5.0 → 0.5.4
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/.gitignore +3 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +64 -58
- data/RAG_SCHEMA.md +239 -0
- data/README.md +131 -236
- data/arcade.yml +5 -5
- data/bin/console +1 -0
- data/examples/rag_example.rb +72 -0
- data/examples/rag_usecase_demo.rb +345 -0
- data/lib/arcade/api/operations.rb +33 -24
- data/lib/arcade/api/primitives.rb +52 -2
- data/lib/arcade/base.rb +35 -14
- data/lib/arcade/database.rb +16 -7
- data/lib/arcade/init.rb +5 -2
- data/lib/arcade/match.rb +85 -37
- data/lib/arcade/query.rb +10 -15
- data/lib/arcade/rag_schema.rb +131 -0
- data/lib/arcade/support/model.rb +20 -13
- data/lib/arcade/support/sql.rb +1 -1
- data/lib/arcade/support/string.rb +11 -14
- data/lib/arcade/timeseries.rb +211 -0
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +7 -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/timeseries.rb +6 -0
- data/lib/model/vertex.rb +52 -1
- data/lib/models.rb +3 -0
- data/lib/types.rb +35 -0
- metadata +12 -6
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'
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Arcade
|
|
2
|
+
##
|
|
3
|
+
# Ragschema - A generic class to implement a RAG (Retrieval-Augmented Generation)
|
|
4
|
+
# compatible database schema for ArcadeDB.
|
|
5
|
+
#
|
|
6
|
+
# This module provides a default-index configuration optimized for:
|
|
7
|
+
# - Vector similarity search (semantic embeddings)
|
|
8
|
+
# - Metadata filtering
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# class MyDocument < Arcade::Vertex
|
|
12
|
+
# include Arcade::Ragschema
|
|
13
|
+
# attribute :content, Types::String
|
|
14
|
+
# attribute :metadata, Types::Hash.optional
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# MyDocument.create_type
|
|
18
|
+
#
|
|
19
|
+
module Ragschema
|
|
20
|
+
def self.included(base)
|
|
21
|
+
base.extend(ClassMethods)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
##
|
|
26
|
+
# Returns the default RAG schema setup commands
|
|
27
|
+
# to be executed after type creation
|
|
28
|
+
#
|
|
29
|
+
# @param vector_dimension [Integer] Dimension of vector embeddings (default: 1536 for OpenAI)
|
|
30
|
+
# @return [String] SQL commands for index creation
|
|
31
|
+
def rag_schema(vector_dimension: 1536)
|
|
32
|
+
type_name = database_name
|
|
33
|
+
commands = []
|
|
34
|
+
|
|
35
|
+
# Vector index for semantic similarity search
|
|
36
|
+
commands << "CREATE INDEX ON #{type_name} (embedding) LSM_VECTOR METADATA {dimensions: #{vector_dimension}, similarity: 'COSINE'}"
|
|
37
|
+
|
|
38
|
+
# Metadata index for filtering
|
|
39
|
+
commands << "CREATE INDEX IF NOT EXISTS ON #{type_name} (metadata) NOTUNIQUE"
|
|
40
|
+
|
|
41
|
+
commands.join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Default db_init method for RAG-compatible models
|
|
46
|
+
# Override this in your model class to customize
|
|
47
|
+
#
|
|
48
|
+
# @return [String] SQL commands to be executed on database
|
|
49
|
+
def rag_db_init(vector_dimension: 1536)
|
|
50
|
+
<<~SQL
|
|
51
|
+
CREATE PROPERTY #{database_name}.embedding ARRAY_OF_FLOATS
|
|
52
|
+
CREATE PROPERTY #{database_name}.content STRING
|
|
53
|
+
CREATE PROPERTY #{database_name}.metadata MAP
|
|
54
|
+
CREATE PROPERTY #{database_name}.source_url STRING
|
|
55
|
+
CREATE PROPERTY #{database_name}.created_at DATETIME
|
|
56
|
+
#{rag_schema(vector_dimension: vector_dimension)}
|
|
57
|
+
SQL
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Perform vector similarity search across all documents
|
|
62
|
+
# Returns metadata only (not embedding vectors) for efficiency
|
|
63
|
+
#
|
|
64
|
+
# @param query_embedding [Array<Float>] The embedding vector to search for
|
|
65
|
+
# @param limit [Integer] Maximum number of results (default: 10)
|
|
66
|
+
# @param threshold [Float] Minimum similarity score (default: 0.7)
|
|
67
|
+
# @return [Array<Hash>] Matching records with metadata and similarity score
|
|
68
|
+
def vector_search(query_embedding, limit: 10, threshold: 0.7)
|
|
69
|
+
raise "vector_search must be called on a model class" unless respond_to?(:database_name)
|
|
70
|
+
|
|
71
|
+
type_name = database_name
|
|
72
|
+
# Convert array to ArcadeDB array literal format: [1.0,2.0,3.0]
|
|
73
|
+
embedding_array = "[#{query_embedding.join(',')}]"
|
|
74
|
+
|
|
75
|
+
# Select metadata fields only (not embedding) for efficiency
|
|
76
|
+
# Include @rid for reference and all other fields except embedding
|
|
77
|
+
query = <<~SQL
|
|
78
|
+
SELECT @rid, title, content, category, tags, author, source_url, created_at, updated_at, distance AS similarity
|
|
79
|
+
FROM (
|
|
80
|
+
SELECT expand(vectorNeighbors('#{type_name}[embedding]', #{embedding_array}, #{limit}))
|
|
81
|
+
)
|
|
82
|
+
WHERE distance <= (1.0 - #{threshold})
|
|
83
|
+
ORDER BY distance ASC
|
|
84
|
+
SQL
|
|
85
|
+
|
|
86
|
+
db.query(query)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Perform hybrid search combining vector similarity with SQL LIKE filtering
|
|
91
|
+
# Returns metadata only (not embedding vectors) for efficiency
|
|
92
|
+
#
|
|
93
|
+
# @param query_embedding [Array<Float>] The embedding vector
|
|
94
|
+
# @param text_query [String] SQL LIKE search query
|
|
95
|
+
# @param limit [Integer] Maximum number of results (default: 10)
|
|
96
|
+
# @param vector_threshold [Float] Minimum vector similarity (default: 0.7)
|
|
97
|
+
# @return [Array<Hash>] Matching records with metadata and similarity score
|
|
98
|
+
def hybrid_search(query_embedding, text_query, limit: 10, vector_threshold: 0.7)
|
|
99
|
+
raise "hybrid_search must be called on a model class" unless respond_to?(:database_name)
|
|
100
|
+
|
|
101
|
+
type_name = database_name
|
|
102
|
+
# Convert array to ArcadeDB array literal format: [1.0,2.0,3.0]
|
|
103
|
+
embedding_array = "[#{query_embedding.join(',')}]"
|
|
104
|
+
|
|
105
|
+
# Select metadata fields only (not embedding) for efficiency
|
|
106
|
+
query = <<~SQL
|
|
107
|
+
SELECT @rid, title, content, category, tags, author, source_url, created_at, updated_at, distance AS similarity
|
|
108
|
+
FROM (
|
|
109
|
+
SELECT expand(vectorNeighbors('#{type_name}[embedding]', #{embedding_array}, #{limit}))
|
|
110
|
+
)
|
|
111
|
+
WHERE distance <= (1.0 - #{vector_threshold})
|
|
112
|
+
AND content LIKE '%#{text_query}%'
|
|
113
|
+
ORDER BY distance ASC
|
|
114
|
+
SQL
|
|
115
|
+
|
|
116
|
+
db.query(query)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Instance method to perform vector similarity search
|
|
122
|
+
#
|
|
123
|
+
# @param query_embedding [Array<Float>] The embedding vector to search for
|
|
124
|
+
# @param limit [Integer] Maximum number of results (default: 10)
|
|
125
|
+
# @param threshold [Float] Minimum similarity score (default: 0.7)
|
|
126
|
+
# @return [Array<Base>] Matching records
|
|
127
|
+
def vector_search(query_embedding, limit: 10, threshold: 0.7)
|
|
128
|
+
self.class.vector_search(query_embedding, limit: limit, threshold: threshold)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
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
|
|
@@ -64,7 +60,18 @@ module Arcade
|
|
|
64
60
|
end
|
|
65
61
|
end
|
|
66
62
|
# choose the appropriate class
|
|
67
|
-
|
|
63
|
+
# First, try to find an existing model class
|
|
64
|
+
klass = nil
|
|
65
|
+
begin
|
|
66
|
+
klass = namespace.const_get(type_name) if namespace && namespace.const_defined?(type_name)
|
|
67
|
+
rescue NameError
|
|
68
|
+
# Class not found, will create dynamically
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# If no existing class found, create a new one
|
|
72
|
+
unless klass
|
|
73
|
+
klass = Dry::Core::ClassBuilder.new( name: type_name, parent: nil, namespace: namespace ).call
|
|
74
|
+
end
|
|
68
75
|
#
|
|
69
76
|
begin
|
|
70
77
|
# create a new object of that class with the appropriate attributes
|
|
@@ -82,7 +89,7 @@ module Arcade
|
|
|
82
89
|
elsif response.rid?
|
|
83
90
|
# Autoload rid's
|
|
84
91
|
response.load_rid
|
|
85
|
-
|
|
92
|
+
# auto ? response.load_rid : response
|
|
86
93
|
else
|
|
87
94
|
response
|
|
88
95
|
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
|