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