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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45fb3b7f94084761a6a3d6a26c9e4d86f3b790768ba52d5a5735713bb978d37b
4
- data.tar.gz: af3866f393670857680d9334280058a0542155f3b76cb2cc64648fa0875e9f92
3
+ metadata.gz: aa956147280cd1ef2125eb39f3dd7867eadafde15806d95b90dab30d1290b135
4
+ data.tar.gz: a5b3a61d2fcf3026f9503b1bb4a9c6e60c6317f92266501f2890c2a02af1a01d
5
5
  SHA512:
6
- metadata.gz: 774867c2584780ca6bbae3edbdf82fd592ebb3b3cb65c8f97400ceba5b37db650ce7c0ec9da1b45b30c2cf789e1d2e2ca776b97cbfc8ac431b3af234b4ba9efe
7
- data.tar.gz: 05c03b0e12a5c77a53463cdfae58cce43b1f0cddfb811cad4a81e9ff137d49930434f53cbe0f6858e933eca15e9ce7e98b4d7cf57cb24d690a3a0d2b58cfc314
6
+ metadata.gz: a2dbb2a72e8f64056bf6531fab29da715bdc89d1748dce2fe92a44de11b5a8c74dbc354c18a903472c996c90e01f7947c921819d3cded09b4ffbfe1cbd0da33c
7
+ data.tar.gz: d5265daf4c4a1928bdd8f7f08a1efd884f8c4ce5da38d56321f58f6f507905a24d7587d2f92bacfe5a4141e500f7f81060726a2723368b443d8892bf2e66a9b3
data/CHANGELOG.md CHANGED
@@ -1,10 +1,57 @@
1
1
  # Change Log
2
2
 
3
+ ## VERSION 0.4.0 - 2024-xx-xx
4
+
5
+ - **Edges**
6
+ * `find_edge` is deprecated - use `find_by` with :start_node, :end_node to find an edge with specific nodes
7
+ - **cypher**
8
+ * query support
9
+ * paths support
10
+ * select attributes support
11
+ - **Paths**
12
+ * ?
13
+
14
+ ## VERSION 0.3.1 - 2024-xx-xx
15
+
16
+ - **Genetator**
17
+ * add `rails generate apache_age:node` to create a node model (with its type in initializer)
18
+ * add `rails generate apache_age:edge` to create an edge model (with its type in initializer)
19
+ - **Installer**
20
+ * refactored into multiple independent tasks?
21
+
22
+ ## VERSION 0.3.0 - 2024-05-28
23
+
24
+ - **Installer** (`rails generate apache_age:install`)
25
+ * copy Age PG Extenstion migration to `db/migrate`
26
+ * run the AGE PG Migration
27
+ * repair `db/schema.rb` (rails mangles schema after running pg extension)
28
+ * update `database.yml` with schema search paths
29
+
30
+ NOTE: the `rails generate apache_age:install` can be run at any time to repair the schema (or other config) file if needed.
31
+
32
+ ## VERSION 0.2.0 - 2024-05-26
33
+
34
+ - **Edges**
35
+ * add class methods to `find_edge` (with {properties, end_id, start_id})
36
+ * add missing methods to use in rails controllers
37
+ * validate edge start- & end-nodes are valid
38
+ * add unique edge validations
39
+ - **Nodes**
40
+ * add missing methods to use in rails controllers
41
+ * add unique node validations
42
+
3
43
  ## VERSION 0.1.0 - 2024-05-21
4
44
 
5
45
  Initial release has the following features:
6
46
 
7
- - Nodes
8
- - Edges
9
- That can be used within Rails applications using a Rails API.
10
- See the [README](README.md) for more information.
47
+ - **Nodes:**
48
+ * `.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
49
+ * verified with usage in a controller and views
50
+ - **Edges:**
51
+ *`.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
52
+ * verified with usage in a controller and views
53
+ - **Entities:**
54
+ * `.all`, `.find(id)`, `.find_by(age_property)` use these when class, label, edge, node
55
+
56
+ These can be used within Rails applications using a Rails APIs including within controllers and views.
57
+ See the [README](README.md) for more information.
data/README.md CHANGED
@@ -4,24 +4,127 @@ Simplify Apache Age usage within a Rails application.
4
4
 
5
5
  ## Installation
6
6
 
7
+ **NOTE:** you must be using Postgres as your database! Apache Age requires it.
8
+
7
9
  Add this line to your application's Gemfile:
8
10
 
9
11
  ```ruby
10
12
  gem "rails_age"
11
13
  ```
12
14
 
13
- And then execute:
15
+ ### Quick Install
14
16
 
15
17
  ```bash
16
18
  $ bundle
19
+ $ bin/rails apache_age:install
20
+ $ git add .
21
+ $ git commit -m "Add Apache Age to Rails"
17
22
  ```
18
23
 
19
- Or install it yourself as:
24
+ NOTE: it is important to add the db/schema.rb to your git repository because `rails db:migrate` will inappropriately modify the schema file. However, you can run `bin/rails apache_age:install` at any time to repair the schema file if needed.
25
+
26
+ ### Manual Install
27
+
28
+ create a migration to add the Apache Age extension to your database
29
+ ```bash
30
+ $ bin/rails g migration AddApacheAge
31
+ ```
32
+ copy the contents of https://github.com/marpori/rails_age/blob/main/db/migrate/20240521062349_add_apache_age.rb
33
+ ```ruby
34
+ class AddApacheAge < ActiveRecord::Migration[7.1]
35
+ def up
36
+ # Allow age extension
37
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
38
+
39
+ # Load the age code
40
+ execute("LOAD 'age';")
41
+
42
+ # Load the ag_catalog into the search path
43
+ execute('SET search_path = ag_catalog, "$user", public;')
44
+
45
+ # Create age_schema graph if it doesn't exist
46
+ execute("SELECT create_graph('age_schema');")
47
+ end
48
+
49
+ def down
50
+ execute <<-SQL
51
+ DO $$
52
+ BEGIN
53
+ IF EXISTS (
54
+ SELECT 1
55
+ FROM pg_constraint
56
+ WHERE conname = 'fk_graph_oid'
57
+ ) THEN
58
+ ALTER TABLE ag_catalog.ag_label
59
+ DROP CONSTRAINT fk_graph_oid;
60
+ END IF;
61
+ END $$;
62
+ SQL
63
+
64
+ execute("SELECT drop_graph('age_schema', true);")
65
+ execute('DROP SCHEMA IF EXISTS ag_catalog CASCADE;')
66
+ execute('DROP EXTENSION IF EXISTS age;')
67
+ end
68
+ end
69
+ ```
70
+ into your new migration file
20
71
 
72
+ then run the migration
21
73
  ```bash
22
- $ gem install rails_age
74
+ $ bin/rails db:migrate
23
75
  ```
24
76
 
77
+ Rails migrate will mangle the schema `db/schema.rb` file. You need to remove the lines that look like:
78
+ ```ruby
79
+ create_schema "ag_catalog"
80
+ create_schema "age_schema"
81
+
82
+ # These are extensions that must be enabled in order to support this database
83
+ enable_extension "age"
84
+ enable_extension "plpgsql"
85
+
86
+ # Could not dump table "_ag_label_edge" because of following StandardError
87
+ # Unknown type 'graphid' for column 'id'
88
+
89
+ # Could not dump table "_ag_label_vertex" because of following StandardError
90
+ # Unknown type 'graphid' for column 'id'
91
+
92
+ # Could not dump table "ag_graph" because of following StandardError
93
+ # Unknown type 'regnamespace' for column 'namespace'
94
+
95
+ # Could not dump table "ag_label" because of following StandardError
96
+ # Unknown type 'regclass' for column 'relation'
97
+
98
+ add_foreign_key "ag_label", "ag_graph", column: "graph", primary_key: "graphid", name: "fk_graph_oid"
99
+
100
+ # other migrations
101
+ # ...
102
+ ```
103
+
104
+ and replace them with the following lines:
105
+ ```ruby
106
+ # These are extensions that must be enabled in order to support this database
107
+ enable_extension "plpgsql"
108
+
109
+ # Allow age extension
110
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
111
+
112
+ # Load the age code
113
+ execute("LOAD 'age';")
114
+
115
+ # Load the ag_catalog into the search path
116
+ execute('SET search_path = ag_catalog, "$user", public;')
117
+
118
+ # Create age_schema graph if it doesn't exist
119
+ execute("SELECT create_graph('age_schema');")
120
+
121
+ # other migrations
122
+ # ...
123
+ ```
124
+
125
+ NOTE: I like to add the schema.rb to git so that it is easy to revert the unwanted changes and keep the desired changes.
126
+ ALSO note that running: `bin/rails apache_age:install` will check and non-destructively repair any config files at any time (however a git commit before hand as a backup is a good idea incase something goes wrong!)
127
+
25
128
  ## Contributing
26
129
 
27
130
  Create an MR and tests and I will review it.
@@ -34,7 +137,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
34
137
 
35
138
  I suggest you creat a folder within app called `graphs` and under that create a folder called `nodes` and `edges`. This will help you keep your code organized.
36
139
 
37
- A full sample app can be found [here](https://github.com/btihen-dev/rails_graphdb_age_app) the summary usage is described below.
140
+ A full sample app can be found [here](https://github.com/marpori/rails_age_demo_app) the summary usage is described below.
38
141
 
39
142
  ### Nodes
40
143
 
@@ -42,10 +145,15 @@ A full sample app can be found [here](https://github.com/btihen-dev/rails_graphd
42
145
  # app/graphs/nodes/company.rb
43
146
  module Nodes
44
147
  class Company
45
- include ApacheAge::Vertex
148
+ include ApacheAge::Entities::Vertex
46
149
 
47
150
  attribute :company_name, :string
151
+
48
152
  validates :company_name, presence: true
153
+ validates_with(
154
+ ApacheAge::Validators::UniqueVertexValidator,
155
+ attributes: [:company_name]
156
+ )
49
157
  end
50
158
  end
51
159
  ```
@@ -54,7 +162,7 @@ end
54
162
  # app/graphs/nodes/person.rb
55
163
  module Nodes
56
164
  class Person
57
- include ApacheAge::Vertex
165
+ include ApacheAge::Entities::Vertex
58
166
 
59
167
  attribute :first_name, :string, default: nil
60
168
  attribute :last_name, :string, default: nil
@@ -78,13 +186,30 @@ end
78
186
  ### Edges
79
187
 
80
188
  ```ruby
81
- # app/graphs/edges/works_at.rb
189
+ # app/graphs/edges/has_job.rb
82
190
  module Edges
83
- class WorksAt
84
- include ApacheAge::Edge
191
+ class HasJob
192
+ include ApacheAge::Entities::Edge
85
193
 
86
194
  attribute :employee_role, :string
195
+ attribute :start_node, :person
196
+ attribute :end_node, :company
197
+
87
198
  validates :employee_role, presence: true
199
+ validate :validate_unique
200
+ # or with a one-liner
201
+ # validates_with(
202
+ # ApacheAge::Validators::UniqueEdgeValidator,
203
+ # attributes: %i[employee_role start_node end_node]
204
+ # )
205
+
206
+ private
207
+
208
+ def validate_unique
209
+ ApacheAge::Validators::UniqueEdgeValidator
210
+ .new(attributes: %i[employee_role start_node end_node])
211
+ .validate(self)
212
+ end
88
213
  end
89
214
  end
90
215
  ```
@@ -98,10 +223,46 @@ fred.to_h
98
223
  quarry = Nodes::Company.create(company_name: 'Bedrock Quarry')
99
224
  quarry.to_h
100
225
 
101
- job = Edges::WorksAt.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
226
+ job = Edges::HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
102
227
  job.to_h
103
228
  ```
104
229
 
230
+ ### Update Routes
231
+
232
+ ```ruby
233
+ Rails.application.routes.draw do
234
+ # mount is not needed with the engine
235
+ # mount RailsAge::Engine => "/rails_age"
236
+
237
+ # defines the route for the people controller
238
+ resources :people
239
+
240
+ # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
241
+ # Can be used by load balancers and uptime monitors to verify that the app is live.
242
+ get 'up' => 'rails/health#show', as: :rails_health_check
243
+
244
+ # Defines the root path route ("/")
245
+ root 'people#index'
246
+ end
247
+ ```
248
+
249
+ ### Types (Optional)
250
+
251
+ ```ruby
252
+ # spec/dummy/config/initializers/types.rb
253
+ require 'apache_age/types/age_type_generator'
254
+
255
+ Rails.application.config.to_prepare do
256
+ # Ensure the files are loaded
257
+ require_dependency 'nodes/company'
258
+ require_dependency 'nodes/person'
259
+
260
+ # Register the custom types
261
+ ActiveModel::Type.register(:company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company))
262
+ ActiveModel::Type.register(:person, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Person))
263
+ end
264
+ ```
265
+
105
266
  ### Controller Usage
106
267
 
107
268
  ```ruby
@@ -176,3 +337,9 @@ class PeopleController < ApplicationController
176
337
  end
177
338
  end
178
339
  ```
340
+
341
+ ### Views
342
+
343
+ ```erb
344
+
345
+ ```
@@ -1,4 +1,4 @@
1
- class ConfigureApacheAge < ActiveRecord::Migration[7.1]
1
+ class AddApacheAge < ActiveRecord::Migration[7.1]
2
2
  def up
3
3
  # Allow age extension
4
4
  execute('CREATE EXTENSION IF NOT EXISTS age;')
@@ -0,0 +1,119 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module ClassMethods
4
+ # for now we only allow one predertimed graph
5
+ def create(attributes)
6
+ instance = new(**attributes)
7
+ instance.save
8
+ instance
9
+ end
10
+
11
+ def find_by(attributes)
12
+ return nil if attributes.reject{ |k,v| v.blank? }.empty?
13
+
14
+ edge_keys = [:start_id, :start_node, :end_id, :end_node]
15
+ return find_edge(attributes) if edge_keys.any? { |key| attributes.include?(key) }
16
+
17
+ where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
18
+ cypher_sql = find_sql(where_clause)
19
+
20
+ execute_find(cypher_sql)
21
+ end
22
+
23
+ def find(id)
24
+ where_clause = "id(find) = #{id}"
25
+ cypher_sql = find_sql(where_clause)
26
+ execute_find(cypher_sql)
27
+ end
28
+
29
+ def all
30
+ age_results = ActiveRecord::Base.connection.execute(all_sql)
31
+ return [] if age_results.values.count.zero?
32
+
33
+ age_results.values.map do |result|
34
+ json_string = result.first.split('::').first
35
+ hash = JSON.parse(json_string)
36
+ attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
37
+
38
+ new(**attribs)
39
+ end
40
+ end
41
+
42
+ # Private stuff
43
+
44
+ def find_edge(attributes)
45
+ where_attribs =
46
+ attributes
47
+ .compact
48
+ .except(:end_id, :start_id, :end_node, :start_node)
49
+ .map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
50
+ where_attribs = where_attribs.empty? ? nil : where_attribs
51
+
52
+ end_id = attributes[:end_id] || attributes[:end_node]&.id
53
+ start_id = attributes[:start_id] || attributes[:start_node]&.id
54
+ where_end_id = end_id ? "id(end_node) = #{end_id}" : nil
55
+ where_start_id = start_id ? "id(start_node) = #{start_id}" : nil
56
+
57
+ where_clause = [where_attribs, where_start_id, where_end_id].compact.join(' AND ')
58
+ return nil if where_clause.empty?
59
+
60
+ cypher_sql = find_edge_sql(where_clause)
61
+
62
+ execute_find(cypher_sql)
63
+ end
64
+
65
+ def age_graph = 'age_schema'
66
+ def age_label = name.gsub('::', '__')
67
+ def age_type = name.constantize.new.age_type
68
+
69
+ def match_clause
70
+ age_type == 'vertex' ? "(find:#{age_label})" : "(start_node)-[find:#{age_label}]->(end_node)"
71
+ end
72
+
73
+ def execute_find(cypher_sql)
74
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
75
+ return nil if age_result.values.count.zero?
76
+
77
+ age_type = age_result.values.first.first.split('::').last
78
+ json_data = age_result.values.first.first.split('::').first
79
+
80
+ hash = JSON.parse(json_data)
81
+ attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
82
+
83
+ new(**attribs)
84
+ end
85
+
86
+ def all_sql
87
+ <<-SQL
88
+ SELECT *
89
+ FROM cypher('#{age_graph}', $$
90
+ MATCH #{match_clause}
91
+ RETURN find
92
+ $$) as (#{age_label} agtype);
93
+ SQL
94
+ end
95
+
96
+ def find_sql(where_clause)
97
+ <<-SQL
98
+ SELECT *
99
+ FROM cypher('#{age_graph}', $$
100
+ MATCH #{match_clause}
101
+ WHERE #{where_clause}
102
+ RETURN find
103
+ $$) as (#{age_label} agtype);
104
+ SQL
105
+ end
106
+
107
+ def find_edge_sql(where_clause)
108
+ <<-SQL
109
+ SELECT *
110
+ FROM cypher('#{age_graph}', $$
111
+ MATCH #{match_clause}
112
+ WHERE #{where_clause}
113
+ RETURN find
114
+ $$) as (#{age_label} agtype);
115
+ SQL
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,118 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module CommonMethods
4
+ # for now we just can just use one schema
5
+ def age_graph = 'age_schema'
6
+ def age_label = self.class.name.gsub('::', '__')
7
+ def persisted? = id.present?
8
+ def to_s = ":#{age_label} #{properties_to_s}"
9
+
10
+ def to_h
11
+ base_h = attributes.to_hash
12
+ if age_type == 'edge'
13
+ # remove the nodes (in attribute form and re-add in hash form)
14
+ base_h = base_h.except('start_node', 'end_node')
15
+ base_h[:end_node] = end_node.to_h if end_node
16
+ base_h[:start_node] = start_node.to_h if start_node
17
+ end
18
+ base_h.symbolize_keys
19
+ end
20
+
21
+ def update_attributes(attribs)
22
+ attribs.except(id:).each do |key, value|
23
+ send("#{key}=", value) if respond_to?("#{key}=")
24
+ end
25
+ end
26
+
27
+ def update(attribs)
28
+ update_attributes(attribs)
29
+ save
30
+ end
31
+
32
+ def save
33
+ return false unless valid?
34
+
35
+ cypher_sql = (persisted? ? update_sql : create_sql)
36
+ response_hash = execute_sql(cypher_sql)
37
+
38
+ self.id = response_hash['id']
39
+
40
+ if age_type == 'edge'
41
+ self.end_id = response_hash['end_id']
42
+ self.start_id = response_hash['start_id']
43
+ # reload the nodes? (can we change the nodes?)
44
+ # self.end_node = ApacheAge::Entity.find(end_id)
45
+ # self.start_node = ApacheAge::Entity.find(start_id)
46
+ end
47
+
48
+ self
49
+ end
50
+
51
+ def destroy
52
+ match_clause = (age_type == 'vertex' ? "(done:#{age_label})" : "()-[done:#{age_label}]->()")
53
+ delete_clause = (age_type == 'vertex' ? 'DETACH DELETE done' : 'DELETE done')
54
+ cypher_sql =
55
+ <<-SQL
56
+ SELECT *
57
+ FROM cypher('#{age_graph}', $$
58
+ MATCH #{match_clause}
59
+ WHERE id(done) = #{id}
60
+ #{delete_clause}
61
+ return done
62
+ $$) as (deleted agtype);
63
+ SQL
64
+
65
+ hash = execute_sql(cypher_sql)
66
+ return nil if hash.blank?
67
+
68
+ self.id = nil
69
+ self
70
+ end
71
+ alias destroy! destroy
72
+ alias delete destroy
73
+
74
+ # private
75
+
76
+ def age_properties
77
+ attrs = attributes.except('id')
78
+ attrs = attrs.except('end_node', 'start_node', 'end_id', 'start_id') if age_type == 'edge'
79
+ attrs.symbolize_keys
80
+ end
81
+
82
+ def age_hash
83
+ hash =
84
+ {
85
+ id:,
86
+ label: age_label,
87
+ properties: age_properties
88
+ }
89
+ hash.merge!(end_id:, start_id:) if age_type == 'edge'
90
+ hash.transform_keys(&:to_s)
91
+ end
92
+
93
+ def properties_to_s
94
+ string_values =
95
+ age_properties.each_with_object([]) do |(key, val), array|
96
+ array << "#{key}: '#{val}'"
97
+ end
98
+ "{#{string_values.join(', ')}}"
99
+ end
100
+
101
+ def age_alias
102
+ return nil if id.blank?
103
+
104
+ # we start the alias with a since we can't start with a number
105
+ 'a' + Digest::SHA256.hexdigest(id.to_s).to_i(16).to_s(36)[0..9]
106
+ end
107
+
108
+ def execute_sql(cypher_sql)
109
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
110
+ age_type = age_result.values.first.first.split('::').last
111
+ json_data = age_result.values.first.first.split('::').first
112
+ # json_data = age_result.to_a.first.values.first.split("::#{age_type}").first
113
+
114
+ JSON.parse(json_data)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,98 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module Edge
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Model
8
+ include ActiveModel::Dirty
9
+ include ActiveModel::Attributes
10
+
11
+ attribute :id, :integer
12
+ attribute :end_id, :integer
13
+ attribute :start_id, :integer
14
+ # allow user to optionally specify the class type (or not) thus not adding: `:vertex`
15
+ attribute :end_node
16
+ attribute :start_node
17
+
18
+ validates :end_node, :start_node, presence: true
19
+ validate :validate_nodes
20
+
21
+ extend ApacheAge::Entities::ClassMethods
22
+ include ApacheAge::Entities::CommonMethods
23
+ end
24
+
25
+ def initialize(**attributes)
26
+ super
27
+ self.end_id ||= end_node.id if end_node
28
+ self.start_id ||= start_node.id if start_node
29
+ self.end_node ||= Entity.find(end_id) if end_id
30
+ self.start_node ||= Entity.find(start_id) if start_id
31
+ end
32
+
33
+ def age_type = 'edge'
34
+ def end_class = end_node.class
35
+ def start_class = start_node.class
36
+ def end_node_class = end_node.class
37
+ def start_node_class = start_node.class
38
+
39
+ # Private methods
40
+
41
+ # Custom validation method to validate start_node and end_node
42
+ def validate_nodes
43
+ errors.add(:start_node, 'invalid') if start_node && !start_node.valid?
44
+ errors.add(:end_node, 'invalid') if end_node && !end_node.valid?
45
+ end
46
+
47
+ # Discover attribute class
48
+ # name_type = model.class.attribute_types['name']
49
+ # age_type = model.class.attribute_types['age']
50
+ # company_type = model.class.attribute_types['company']
51
+ # # Determine the class from the attribute type (for custom types)
52
+ # name_class = name_type.class # This will generally be ActiveModel::Type::String
53
+ # age_class = age_type.class # This will generally be ActiveModel::Type::Integer
54
+ # # For custom types, you may need to look deeper
55
+ # company_class = company_type.cast_type.class
56
+
57
+ # AgeSchema::Edges::HasJob.create(
58
+ # start_node: fred, end_node: quarry, employee_role: 'Crane Operator'
59
+ # )
60
+ # SELECT *
61
+ # FROM cypher('age_schema', $$
62
+ # MATCH (start_vertex:Person), (end_vertex:Company)
63
+ # WHERE id(start_vertex) = 1125899906842634 and id(end_vertex) = 844424930131976
64
+ # CREATE (start_vertex)-[edge:HasJob {employee_role: 'Crane Operator'}]->(end_vertex)
65
+ # RETURN edge
66
+ # $$) as (edge agtype);
67
+ def create_sql
68
+ self.start_node = start_node.save unless start_node.persisted?
69
+ self.end_node = end_node.save unless end_node.persisted?
70
+ <<-SQL
71
+ SELECT *
72
+ FROM cypher('#{age_graph}', $$
73
+ MATCH (from_node:#{start_node.age_label}), (to_node:#{end_node.age_label})
74
+ WHERE id(from_node) = #{start_node.id} and id(to_node) = #{end_node.id}
75
+ CREATE (from_node)-[edge#{self}]->(to_node)
76
+ RETURN edge
77
+ $$) as (edge agtype);
78
+ SQL
79
+ end
80
+
81
+ # So far just properties of string type with '' around them
82
+ def update_sql
83
+ alias_name = age_alias || age_label.downcase
84
+ set_caluse =
85
+ age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
86
+ <<-SQL
87
+ SELECT *
88
+ FROM cypher('#{age_graph}', $$
89
+ MATCH ()-[#{alias_name}:#{age_label}]->()
90
+ WHERE id(#{alias_name}) = #{id}
91
+ SET #{set_caluse}
92
+ RETURN #{alias_name}
93
+ $$) as (#{age_label} agtype);
94
+ SQL
95
+ end
96
+ end
97
+ end
98
+ end