rails_age 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -4
- data/README.md +177 -10
- data/db/migrate/{20240521062349_configure_apache_age.rb → 20240521062349_add_apache_age.rb} +1 -1
- data/lib/apache_age/entities/class_methods.rb +119 -0
- data/lib/apache_age/entities/common_methods.rb +118 -0
- data/lib/apache_age/entities/edge.rb +98 -0
- data/lib/apache_age/entities/entity.rb +69 -0
- data/lib/apache_age/entities/vertex.rb +53 -0
- data/lib/apache_age/types/age_type_generator.rb +46 -0
- data/lib/apache_age/validators/unique_edge_validator.rb +53 -0
- data/lib/apache_age/validators/unique_vertex_validator.rb +28 -0
- data/lib/apache_age/validators/vertex_type_validator.rb +14 -0
- data/lib/rails_age/version.rb +1 -1
- data/lib/rails_age.rb +10 -7
- data/lib/tasks/install.rake +210 -0
- metadata +31 -19
- data/lib/apache_age/class_methods.rb +0 -75
- data/lib/apache_age/common_methods.rb +0 -126
- data/lib/apache_age/edge.rb +0 -64
- data/lib/apache_age/entity.rb +0 -67
- data/lib/apache_age/vertex.rb +0 -52
@@ -1,75 +0,0 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
module ClassMethods
|
3
|
-
# for now we only allow one predertimed graph
|
4
|
-
def create(attributes) = new(**attributes).save
|
5
|
-
|
6
|
-
def find_by(attributes)
|
7
|
-
where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
|
8
|
-
cypher_sql = find_sql(where_clause)
|
9
|
-
execute_find(cypher_sql)
|
10
|
-
end
|
11
|
-
|
12
|
-
def find(id)
|
13
|
-
where_clause = "id(find) = #{id}"
|
14
|
-
cypher_sql = find_sql(where_clause)
|
15
|
-
execute_find(cypher_sql)
|
16
|
-
end
|
17
|
-
|
18
|
-
def all
|
19
|
-
age_results = ActiveRecord::Base.connection.execute(all_sql)
|
20
|
-
return [] if age_results.values.count.zero?
|
21
|
-
|
22
|
-
age_results.values.map do |result|
|
23
|
-
json_string = result.first.split('::').first
|
24
|
-
hash = JSON.parse(json_string)
|
25
|
-
attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
|
26
|
-
|
27
|
-
new(**attribs)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Private stuff
|
32
|
-
|
33
|
-
def age_graph = 'age_schema'
|
34
|
-
def age_label = name.gsub('::', '__')
|
35
|
-
def age_type = name.constantize.new.age_type
|
36
|
-
|
37
|
-
def match_clause
|
38
|
-
age_type == 'vertex' ? "(find:#{age_label})" : "()-[find:#{age_label}]->()"
|
39
|
-
end
|
40
|
-
|
41
|
-
def execute_find(cypher_sql)
|
42
|
-
age_result = ActiveRecord::Base.connection.execute(cypher_sql)
|
43
|
-
return nil if age_result.values.count.zero?
|
44
|
-
|
45
|
-
age_type = age_result.values.first.first.split('::').last
|
46
|
-
json_data = age_result.values.first.first.split('::').first
|
47
|
-
|
48
|
-
hash = JSON.parse(json_data)
|
49
|
-
attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
|
50
|
-
|
51
|
-
new(**attribs)
|
52
|
-
end
|
53
|
-
|
54
|
-
def all_sql
|
55
|
-
<<-SQL
|
56
|
-
SELECT *
|
57
|
-
FROM cypher('#{age_graph}', $$
|
58
|
-
MATCH #{match_clause}
|
59
|
-
RETURN find
|
60
|
-
$$) as (#{age_label} agtype);
|
61
|
-
SQL
|
62
|
-
end
|
63
|
-
|
64
|
-
def find_sql(where_clause)
|
65
|
-
<<-SQL
|
66
|
-
SELECT *
|
67
|
-
FROM cypher('#{age_graph}', $$
|
68
|
-
MATCH #{match_clause}
|
69
|
-
WHERE #{where_clause}
|
70
|
-
RETURN find
|
71
|
-
$$) as (#{age_label} agtype);
|
72
|
-
SQL
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,126 +0,0 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
module CommonMethods
|
3
|
-
def initialize(**attributes)
|
4
|
-
super
|
5
|
-
return self unless age_type == 'edge'
|
6
|
-
|
7
|
-
self.end_id ||= end_node.id if end_node
|
8
|
-
self.start_id ||= start_node.id if start_node
|
9
|
-
self.end_node ||= Entity.find(end_id) if end_id
|
10
|
-
self.start_node ||= Entity.find(start_id) if start_id
|
11
|
-
end
|
12
|
-
|
13
|
-
# for now we just can just use one schema
|
14
|
-
def age_graph = 'age_schema'
|
15
|
-
def age_label = self.class.name.gsub('::', '__')
|
16
|
-
def persisted? = id.present?
|
17
|
-
def to_s = ":#{age_label} #{properties_to_s}"
|
18
|
-
|
19
|
-
def to_h
|
20
|
-
base_h = attributes.to_hash
|
21
|
-
if age_type == 'edge'
|
22
|
-
# remove the nodes (in attribute form and re-add in hash form)
|
23
|
-
base_h = base_h.except('start_node', 'end_node')
|
24
|
-
base_h[:end_node] = end_node.to_h if end_node
|
25
|
-
base_h[:start_node] = start_node.to_h if start_node
|
26
|
-
end
|
27
|
-
base_h.symbolize_keys
|
28
|
-
end
|
29
|
-
|
30
|
-
def update_attributes(attribs)
|
31
|
-
attribs.except(id:).each do |key, value|
|
32
|
-
send("#{key}=", value) if respond_to?("#{key}=")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def update(attribs)
|
37
|
-
update_attributes(attribs)
|
38
|
-
save
|
39
|
-
end
|
40
|
-
|
41
|
-
def save
|
42
|
-
return false unless valid?
|
43
|
-
|
44
|
-
cypher_sql = (persisted? ? update_sql : create_sql)
|
45
|
-
response_hash = execute_sql(cypher_sql)
|
46
|
-
|
47
|
-
self.id = response_hash['id']
|
48
|
-
|
49
|
-
if age_type == 'edge'
|
50
|
-
self.end_id = response_hash['end_id']
|
51
|
-
self.start_id = response_hash['start_id']
|
52
|
-
# reload the nodes? (can we change the nodes?)
|
53
|
-
# self.end_node = ApacheAge::Entity.find(end_id)
|
54
|
-
# self.start_node = ApacheAge::Entity.find(start_id)
|
55
|
-
end
|
56
|
-
|
57
|
-
self
|
58
|
-
end
|
59
|
-
|
60
|
-
def destroy
|
61
|
-
match_clause = (age_type == 'vertex' ? "(done:#{age_label})" : "()-[done:#{age_label}]->()")
|
62
|
-
delete_clause = (age_type == 'vertex' ? 'DETACH DELETE done' : 'DELETE done')
|
63
|
-
cypher_sql =
|
64
|
-
<<-SQL
|
65
|
-
SELECT *
|
66
|
-
FROM cypher('#{age_graph}', $$
|
67
|
-
MATCH #{match_clause}
|
68
|
-
WHERE id(done) = #{id}
|
69
|
-
#{delete_clause}
|
70
|
-
return done
|
71
|
-
$$) as (deleted agtype);
|
72
|
-
SQL
|
73
|
-
|
74
|
-
hash = execute_sql(cypher_sql)
|
75
|
-
return nil if hash.blank?
|
76
|
-
|
77
|
-
self.id = nil
|
78
|
-
self
|
79
|
-
end
|
80
|
-
alias destroy! destroy
|
81
|
-
alias delete destroy
|
82
|
-
|
83
|
-
# private
|
84
|
-
|
85
|
-
def age_properties
|
86
|
-
attrs = attributes.except('id')
|
87
|
-
attrs = attrs.except('end_node', 'start_node', 'end_id', 'start_id') if age_type == 'edge'
|
88
|
-
attrs.symbolize_keys
|
89
|
-
end
|
90
|
-
|
91
|
-
def age_hash
|
92
|
-
hash =
|
93
|
-
{
|
94
|
-
id:,
|
95
|
-
label: age_label,
|
96
|
-
properties: age_properties
|
97
|
-
}
|
98
|
-
hash.merge!(end_id:, start_id:) if age_type == 'edge'
|
99
|
-
hash.transform_keys(&:to_s)
|
100
|
-
end
|
101
|
-
|
102
|
-
def properties_to_s
|
103
|
-
string_values =
|
104
|
-
age_properties.each_with_object([]) do |(key, val), array|
|
105
|
-
array << "#{key}: '#{val}'"
|
106
|
-
end
|
107
|
-
"{#{string_values.join(', ')}}"
|
108
|
-
end
|
109
|
-
|
110
|
-
def age_alias
|
111
|
-
return nil if id.blank?
|
112
|
-
|
113
|
-
# we start the alias with a since we can't start with a number
|
114
|
-
'a' + Digest::SHA256.hexdigest(id.to_s).to_i(16).to_s(36)[0..9]
|
115
|
-
end
|
116
|
-
|
117
|
-
def execute_sql(cypher_sql)
|
118
|
-
age_result = ActiveRecord::Base.connection.execute(cypher_sql)
|
119
|
-
age_type = age_result.values.first.first.split('::').last
|
120
|
-
json_data = age_result.values.first.first.split('::').first
|
121
|
-
# json_data = age_result.to_a.first.values.first.split("::#{age_type}").first
|
122
|
-
|
123
|
-
JSON.parse(json_data)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
data/lib/apache_age/edge.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
module Edge
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
include ActiveModel::Model
|
7
|
-
include ActiveModel::Dirty
|
8
|
-
include ActiveModel::Attributes
|
9
|
-
|
10
|
-
attribute :id, :integer
|
11
|
-
attribute :end_id, :integer
|
12
|
-
attribute :start_id, :integer
|
13
|
-
attribute :end_node # :vertex
|
14
|
-
attribute :start_node # :vertex
|
15
|
-
|
16
|
-
validates :end_node, :start_node, presence: true
|
17
|
-
|
18
|
-
extend ApacheAge::ClassMethods
|
19
|
-
include ApacheAge::CommonMethods
|
20
|
-
end
|
21
|
-
|
22
|
-
def age_type = 'edge'
|
23
|
-
|
24
|
-
# AgeSchema::Edges::WorksAt.create(
|
25
|
-
# start_node: fred, end_node: quarry, employee_role: 'Crane Operator'
|
26
|
-
# )
|
27
|
-
# SELECT *
|
28
|
-
# FROM cypher('age_schema', $$
|
29
|
-
# MATCH (start_vertex:Person), (end_vertex:Company)
|
30
|
-
# WHERE id(start_vertex) = 1125899906842634 and id(end_vertex) = 844424930131976
|
31
|
-
# CREATE (start_vertex)-[edge:WorksAt {employee_role: 'Crane Operator'}]->(end_vertex)
|
32
|
-
# RETURN edge
|
33
|
-
# $$) as (edge agtype);
|
34
|
-
def create_sql
|
35
|
-
self.start_node = start_node.save unless start_node.persisted?
|
36
|
-
self.end_node = end_node.save unless end_node.persisted?
|
37
|
-
<<-SQL
|
38
|
-
SELECT *
|
39
|
-
FROM cypher('#{age_graph}', $$
|
40
|
-
MATCH (from_node:#{start_node.age_label}), (to_node:#{end_node.age_label})
|
41
|
-
WHERE id(from_node) = #{start_node.id} and id(to_node) = #{end_node.id}
|
42
|
-
CREATE (from_node)-[edge#{self}]->(to_node)
|
43
|
-
RETURN edge
|
44
|
-
$$) as (edge agtype);
|
45
|
-
SQL
|
46
|
-
end
|
47
|
-
|
48
|
-
# So far just properties of string type with '' around them
|
49
|
-
def update_sql
|
50
|
-
alias_name = age_alias || age_label.downcase
|
51
|
-
set_caluse =
|
52
|
-
age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
|
53
|
-
<<-SQL
|
54
|
-
SELECT *
|
55
|
-
FROM cypher('#{age_graph}', $$
|
56
|
-
MATCH ()-[#{alias_name}:#{age_label}]->()
|
57
|
-
WHERE id(#{alias_name}) = #{id}
|
58
|
-
SET #{set_caluse}
|
59
|
-
RETURN #{alias_name}
|
60
|
-
$$) as (#{age_label} agtype);
|
61
|
-
SQL
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
data/lib/apache_age/entity.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
class Entity
|
3
|
-
class << self
|
4
|
-
def find_by(attributes)
|
5
|
-
where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
|
6
|
-
handle_find(where_clause)
|
7
|
-
end
|
8
|
-
|
9
|
-
def find(id)
|
10
|
-
where_clause = "id(find) = #{id}"
|
11
|
-
handle_find(where_clause)
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def age_graph = 'age_schema'
|
17
|
-
|
18
|
-
def handle_find(where_clause)
|
19
|
-
# try to find a vertex
|
20
|
-
match_node = '(find)'
|
21
|
-
cypher_sql = find_sql(match_node, where_clause)
|
22
|
-
age_response = execute_find(cypher_sql)
|
23
|
-
|
24
|
-
if age_response.nil?
|
25
|
-
# if not a vertex try to find an edge
|
26
|
-
match_edge = '()-[find]->()'
|
27
|
-
cypher_sql = find_sql(match_edge, where_clause)
|
28
|
-
age_response = execute_find(cypher_sql)
|
29
|
-
return nil if age_response.nil?
|
30
|
-
end
|
31
|
-
|
32
|
-
instantiate_result(age_response)
|
33
|
-
end
|
34
|
-
|
35
|
-
def execute_find(cypher_sql)
|
36
|
-
age_result = ActiveRecord::Base.connection.execute(cypher_sql)
|
37
|
-
return nil if age_result.values.first.nil?
|
38
|
-
|
39
|
-
age_result
|
40
|
-
end
|
41
|
-
|
42
|
-
def instantiate_result(age_response)
|
43
|
-
age_type = age_response.values.first.first.split('::').last
|
44
|
-
json_string = age_response.values.first.first.split('::').first
|
45
|
-
json_data = JSON.parse(json_string)
|
46
|
-
|
47
|
-
age_label = json_data['label']
|
48
|
-
attribs = json_data.except('label', 'properties')
|
49
|
-
.merge(json_data['properties'])
|
50
|
-
.symbolize_keys
|
51
|
-
|
52
|
-
"#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
|
53
|
-
end
|
54
|
-
|
55
|
-
def find_sql(match_clause, where_clause)
|
56
|
-
<<-SQL
|
57
|
-
SELECT *
|
58
|
-
FROM cypher('#{age_graph}', $$
|
59
|
-
MATCH #{match_clause}
|
60
|
-
WHERE #{where_clause}
|
61
|
-
RETURN find
|
62
|
-
$$) as (found agtype);
|
63
|
-
SQL
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
data/lib/apache_age/vertex.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
module Vertex
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
# include ApacheAge::Entity
|
5
|
-
|
6
|
-
included do
|
7
|
-
include ActiveModel::Model
|
8
|
-
include ActiveModel::Dirty
|
9
|
-
include ActiveModel::Attributes
|
10
|
-
|
11
|
-
attribute :id, :integer
|
12
|
-
|
13
|
-
extend ApacheAge::ClassMethods
|
14
|
-
include ApacheAge::CommonMethods
|
15
|
-
end
|
16
|
-
|
17
|
-
def age_type = 'vertex'
|
18
|
-
|
19
|
-
# AgeSchema::Nodes::Company.create(company_name: 'Bedrock Quarry')
|
20
|
-
# SELECT *
|
21
|
-
# FROM cypher('age_schema', $$
|
22
|
-
# CREATE (company:Company {company_name: 'Bedrock Quarry'})
|
23
|
-
# RETURN company
|
24
|
-
# $$) as (Company agtype);
|
25
|
-
def create_sql
|
26
|
-
alias_name = age_alias || age_label.downcase
|
27
|
-
<<-SQL
|
28
|
-
SELECT *
|
29
|
-
FROM cypher('#{age_graph}', $$
|
30
|
-
CREATE (#{alias_name}#{self})
|
31
|
-
RETURN #{alias_name}
|
32
|
-
$$) as (#{age_label} agtype);
|
33
|
-
SQL
|
34
|
-
end
|
35
|
-
|
36
|
-
# So far just properties of string type with '' around them
|
37
|
-
def update_sql
|
38
|
-
alias_name = age_alias || age_label.downcase
|
39
|
-
set_caluse =
|
40
|
-
age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
|
41
|
-
<<-SQL
|
42
|
-
SELECT *
|
43
|
-
FROM cypher('#{age_graph}', $$
|
44
|
-
MATCH (#{alias_name}:#{age_label})
|
45
|
-
WHERE id(#{alias_name}) = #{id}
|
46
|
-
SET #{set_caluse}
|
47
|
-
RETURN #{alias_name}
|
48
|
-
$$) as (#{age_label} agtype);
|
49
|
-
SQL
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|