rails_age 0.1.0 → 0.3.0
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 +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
|