rails_age 0.1.0 → 0.2.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 +31 -4
- data/README.md +120 -6
- data/lib/apache_age/entities/class_methods.rb +112 -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 +91 -0
- metadata +30 -18
- 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
@@ -0,0 +1,46 @@
|
|
1
|
+
# lib/apache_age/types/age_type_generator.rb
|
2
|
+
# Automatically generates ActiveModel::Type classes
|
3
|
+
# Dynamically builds this (as a concrete example):
|
4
|
+
# module ApacheAge
|
5
|
+
# module Types
|
6
|
+
# class CompanyType < ActiveModel::Type::Value
|
7
|
+
# def cast(value)
|
8
|
+
# case value
|
9
|
+
# when Nodes::Company
|
10
|
+
# value
|
11
|
+
# when Hash
|
12
|
+
# Nodes::Company.new(value)
|
13
|
+
# else
|
14
|
+
# nil
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# def serialize(value)
|
18
|
+
# value.is_a?(Nodes::Company) ? value.attributes : nil
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
module ApacheAge
|
24
|
+
module Types
|
25
|
+
class AgeTypeGenerator
|
26
|
+
def self.create_type_for(klass)
|
27
|
+
Class.new(ActiveModel::Type::Value) do
|
28
|
+
define_method(:cast) do |value|
|
29
|
+
case value
|
30
|
+
when klass
|
31
|
+
value
|
32
|
+
when Hash
|
33
|
+
klass.new(value)
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
define_method(:serialize) do |value|
|
40
|
+
value.is_a?(klass) ? value.attributes : nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Usage (within an Age Model)
|
2
|
+
# validates_with UniqueEdgeValidator, attributes: [:employee_role, :start_node, :end_node]
|
3
|
+
# validates_with UniqueEdgeValidator, attributes: [:start_id, :employee_role, :end_id]
|
4
|
+
#
|
5
|
+
module ApacheAge
|
6
|
+
module Validators
|
7
|
+
class UniqueEdgeValidator < ActiveModel::Validator
|
8
|
+
def validate(record)
|
9
|
+
allowed_keys = record.age_properties.keys
|
10
|
+
attributes = options[:attributes] || []
|
11
|
+
|
12
|
+
edge_attribs =
|
13
|
+
attributes
|
14
|
+
.map { |attr| [attr, record.send(attr)] }.to_h
|
15
|
+
.symbolize_keys
|
16
|
+
.slice(*allowed_keys)
|
17
|
+
|
18
|
+
possible_end_keys = [:end_id, 'end_id', :end_node, 'end_node']
|
19
|
+
end_query =
|
20
|
+
if possible_end_keys.any? { |key| attributes.include?(key) }
|
21
|
+
end_query = query_node(record.end_node)
|
22
|
+
edge_attribs[:end_id] = end_query&.id
|
23
|
+
end_query
|
24
|
+
end
|
25
|
+
|
26
|
+
possible_start_keys = [:start_id, 'start_id', :start_node, 'start_node']
|
27
|
+
start_query =
|
28
|
+
if possible_start_keys.any? { |key| attributes.include?(key) }
|
29
|
+
start_query = query_node(record.start_node)
|
30
|
+
edge_attribs[:start_id] = start_query&.id
|
31
|
+
start_query
|
32
|
+
end
|
33
|
+
return if attributes.blank? && (end_query.blank? || start_query.blank?)
|
34
|
+
|
35
|
+
query = record.class.find_edge(edge_attribs.compact)
|
36
|
+
return if query.blank? || (query.id == record.id)
|
37
|
+
|
38
|
+
record.errors.add(:base, 'attribute combination not unique')
|
39
|
+
record.errors.add(:end_node, 'attribute combination not unique')
|
40
|
+
record.errors.add(:start_node, 'attribute combination not unique')
|
41
|
+
attributes.each { record.errors.add(_1, 'attribute combination not unique') }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def query_node(node)
|
47
|
+
return nil if node.blank?
|
48
|
+
|
49
|
+
node.persisted? ? node.class.find(node.id) : node.class.find_by(node.age_properties)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Usage (within an Age Model)
|
2
|
+
# validates_with UniqueVertexValidator, attributes: [:first_name, :last_name, :gender]
|
3
|
+
|
4
|
+
# lib/apache_age/validators/unique_vertex_validator.rb
|
5
|
+
module ApacheAge
|
6
|
+
module Validators
|
7
|
+
class UniqueVertexValidator < ActiveModel::Validator
|
8
|
+
def validate(record)
|
9
|
+
allowed_keys = record.age_properties.keys
|
10
|
+
attributes = options[:attributes]
|
11
|
+
return if attributes.blank?
|
12
|
+
|
13
|
+
record_attribs =
|
14
|
+
attributes
|
15
|
+
.map { |attr| [attr, record.send(attr)] }
|
16
|
+
.to_h.symbolize_keys
|
17
|
+
.slice(*allowed_keys)
|
18
|
+
query = record.class.find_by(record_attribs)
|
19
|
+
|
20
|
+
# if no match is found or if it finds itself, it's valid
|
21
|
+
return if query.blank? || (query.id == record.id)
|
22
|
+
|
23
|
+
record.errors.add(:base, 'attribute combination not unique')
|
24
|
+
attributes.each { record.errors.add(_1, 'attribute combination not unique') }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ApacheAge
|
2
|
+
module VertexTypeValidator
|
3
|
+
def vertex_attribute(attribute_name, type_symbol, klass)
|
4
|
+
attribute attribute_name, type_symbol
|
5
|
+
|
6
|
+
validate do
|
7
|
+
value = send(attribute_name)
|
8
|
+
unless value.is_a?(klass)
|
9
|
+
errors.add(attribute_name, "must be a #{klass.name}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/rails_age/version.rb
CHANGED
data/lib/rails_age.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'rails_age/version'
|
2
|
+
require 'rails_age/engine'
|
3
3
|
|
4
4
|
module RailsAge
|
5
5
|
# Your code goes here...
|
6
6
|
end
|
7
7
|
|
8
8
|
module ApacheAge
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
9
|
+
require 'apache_age/entities/class_methods'
|
10
|
+
require 'apache_age/entities/common_methods'
|
11
|
+
require 'apache_age/entities/edge'
|
12
|
+
require 'apache_age/entities/entity'
|
13
|
+
require 'apache_age/entities/vertex'
|
14
|
+
require 'apache_age/types/age_type_generator'
|
15
|
+
require 'apache_age/validators/unique_edge_validator'
|
16
|
+
require 'apache_age/validators/unique_vertex_validator'
|
14
17
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage: `rake rails_age:install`
|
3
|
+
#
|
4
|
+
namespace :rails_age do
|
5
|
+
desc "Copy migrations from rails_age to application and update schema"
|
6
|
+
task :install => :environment do
|
7
|
+
source = File.expand_path('../../../db/migrate', __FILE__)
|
8
|
+
destination = File.expand_path('../../../../db/migrate', __FILE__)
|
9
|
+
|
10
|
+
FileUtils.mkdir_p(destination) unless File.exists?(destination)
|
11
|
+
|
12
|
+
Dir.glob("#{source}/*.rb").each do |file|
|
13
|
+
filename = File.basename(file)
|
14
|
+
destination_file = File.join(destination, filename)
|
15
|
+
|
16
|
+
if File.exists?(destination_file)
|
17
|
+
puts "Skipping #{filename}, it already exists"
|
18
|
+
else
|
19
|
+
FileUtils.cp(file, destination_file)
|
20
|
+
puts "Copied #{filename} to #{destination}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Update the schema.rb file
|
25
|
+
schema_file = File.expand_path('../../../../db/schema.rb', __FILE__)
|
26
|
+
if File.exists?(schema_file)
|
27
|
+
content = File.read(schema_file)
|
28
|
+
|
29
|
+
# Add the necessary extensions and configurations at the top of the schema
|
30
|
+
insert_statements = <<-RUBY
|
31
|
+
|
32
|
+
# These are extensions that must be enabled in order to support this database
|
33
|
+
enable_extension "plpgsql"
|
34
|
+
|
35
|
+
# Allow age extension
|
36
|
+
execute('CREATE EXTENSION IF NOT EXISTS age;')
|
37
|
+
|
38
|
+
# Load the age code
|
39
|
+
execute("LOAD 'age';")
|
40
|
+
|
41
|
+
# Load the ag_catalog into the search path
|
42
|
+
execute('SET search_path = ag_catalog, "$user", public;')
|
43
|
+
|
44
|
+
# Create age_schema graph if it doesn't exist
|
45
|
+
execute("SELECT create_graph('age_schema');")
|
46
|
+
|
47
|
+
RUBY
|
48
|
+
|
49
|
+
unless content.include?(insert_statements.strip)
|
50
|
+
content.sub!(/^# These are extensions that must be enabled in order to support this database.*?\n\n/m, insert_statements)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Remove unwanted schema statements
|
54
|
+
content.gsub!(/^.*?create_schema "ag_catalog".*?\n\n/m, '')
|
55
|
+
content.gsub!(/^.*?create_schema "age_schema".*?\n\n/m, '')
|
56
|
+
content.gsub!(/^.*?enable_extension "age".*?\n\n/m, '')
|
57
|
+
content.gsub!(/^.*?# Could not dump table "_ag_label_edge" because of following StandardError.*?\n\n/m, '')
|
58
|
+
content.gsub!(/^.*?# Could not dump table "_ag_label_vertex" because of following StandardError.*?\n\n/m, '')
|
59
|
+
content.gsub!(/^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n\n/m, '')
|
60
|
+
content.gsub!(/^.*?# Could not dump table "ag_label" because of following StandardError.*?\n\n/m, '')
|
61
|
+
content.gsub!(/^.*?add_foreign_key "ag_label", "ag_graph".*?\n\n/m, '')
|
62
|
+
|
63
|
+
File.write(schema_file, content)
|
64
|
+
puts "Updated #{schema_file} with necessary extensions and configurations."
|
65
|
+
else
|
66
|
+
puts "schema.rb file not found. Please ensure migrations have been run."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# namespace :rails_age do
|
72
|
+
# desc "Copy migrations from rails_age to application"
|
73
|
+
# task :install => :environment do
|
74
|
+
# source = File.expand_path('../../../db/migrate', __FILE__)
|
75
|
+
# destination = File.expand_path('../../../../db/migrate', __FILE__)
|
76
|
+
|
77
|
+
# FileUtils.mkdir_p(destination) unless File.exists?(destination)
|
78
|
+
|
79
|
+
# Dir.glob("#{source}/*.rb").each do |file|
|
80
|
+
# filename = File.basename(file)
|
81
|
+
# destination_file = File.join(destination, filename)
|
82
|
+
|
83
|
+
# if File.exists?(destination_file)
|
84
|
+
# puts "Skipping #{filename}, it already exists"
|
85
|
+
# else
|
86
|
+
# FileUtils.cp(file, destination_file)
|
87
|
+
# puts "Copied #{filename} to #{destination}"
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
# end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_age
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bill Tihen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
11
|
+
date: 2024-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,29 +16,36 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.
|
19
|
+
version: '7.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '9.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 7.
|
29
|
+
version: '7.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '9.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: rspec-rails
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - "~>"
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
39
|
+
version: '6.0'
|
34
40
|
type: :development
|
35
41
|
prerelease: false
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
37
43
|
requirements:
|
38
|
-
- - "
|
44
|
+
- - "~>"
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
description: Apache AGE
|
46
|
+
version: '6.0'
|
47
|
+
description: This plugin integrates Apache AGE with Rails 7.x, providing tools and
|
48
|
+
helpers for working with graph databases within a Rails application.
|
42
49
|
email:
|
43
50
|
- btihen@gmail.com
|
44
51
|
executables: []
|
@@ -60,21 +67,26 @@ files:
|
|
60
67
|
- config/routes.rb
|
61
68
|
- db/migrate/20240521062349_configure_apache_age.rb
|
62
69
|
- db/schema.rb
|
63
|
-
- lib/apache_age/class_methods.rb
|
64
|
-
- lib/apache_age/common_methods.rb
|
65
|
-
- lib/apache_age/edge.rb
|
66
|
-
- lib/apache_age/entity.rb
|
67
|
-
- lib/apache_age/vertex.rb
|
70
|
+
- lib/apache_age/entities/class_methods.rb
|
71
|
+
- lib/apache_age/entities/common_methods.rb
|
72
|
+
- lib/apache_age/entities/edge.rb
|
73
|
+
- lib/apache_age/entities/entity.rb
|
74
|
+
- lib/apache_age/entities/vertex.rb
|
75
|
+
- lib/apache_age/types/age_type_generator.rb
|
76
|
+
- lib/apache_age/validators/unique_edge_validator.rb
|
77
|
+
- lib/apache_age/validators/unique_vertex_validator.rb
|
78
|
+
- lib/apache_age/validators/vertex_type_validator.rb
|
68
79
|
- lib/rails_age.rb
|
69
80
|
- lib/rails_age/engine.rb
|
70
81
|
- lib/rails_age/version.rb
|
82
|
+
- lib/tasks/install.rake
|
71
83
|
- lib/tasks/rails_age_tasks.rake
|
72
84
|
homepage: https://github.com/marpori/rails_age
|
73
85
|
licenses:
|
74
86
|
- MIT
|
75
87
|
metadata:
|
76
88
|
homepage_uri: https://github.com/marpori/rails_age
|
77
|
-
source_code_uri: https://github.com/marpori/rails_age
|
89
|
+
source_code_uri: https://github.com/marpori/rails_age/blob/main
|
78
90
|
changelog_uri: https://github.com/marpori/rails_age/blob/main/CHANGELOG.md
|
79
91
|
post_install_message:
|
80
92
|
rdoc_options: []
|
@@ -84,15 +96,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
96
|
requirements:
|
85
97
|
- - ">="
|
86
98
|
- !ruby/object:Gem::Version
|
87
|
-
version: '0'
|
99
|
+
version: '3.0'
|
88
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
101
|
requirements:
|
90
102
|
- - ">="
|
91
103
|
- !ruby/object:Gem::Version
|
92
104
|
version: '0'
|
93
105
|
requirements: []
|
94
|
-
rubygems_version: 3.5.
|
106
|
+
rubygems_version: 3.5.10
|
95
107
|
signing_key:
|
96
108
|
specification_version: 4
|
97
|
-
summary: Apache AGE plugin for Rails 7.
|
109
|
+
summary: Apache AGE plugin for Rails 7.x
|
98
110
|
test_files: []
|
@@ -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
|