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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45fb3b7f94084761a6a3d6a26c9e4d86f3b790768ba52d5a5735713bb978d37b
4
- data.tar.gz: af3866f393670857680d9334280058a0542155f3b76cb2cc64648fa0875e9f92
3
+ metadata.gz: 349925c4b715d64987c69a6a20b23e67c9076d91152477a976ade763805fdbf7
4
+ data.tar.gz: 4c7f1e430fe94ac3cb59eac8e6cb94bc03edab9532074c0df75be233b1210973
5
5
  SHA512:
6
- metadata.gz: 774867c2584780ca6bbae3edbdf82fd592ebb3b3cb65c8f97400ceba5b37db650ce7c0ec9da1b45b30c2cf789e1d2e2ca776b97cbfc8ac431b3af234b4ba9efe
7
- data.tar.gz: 05c03b0e12a5c77a53463cdfae58cce43b1f0cddfb811cad4a81e9ff137d49930434f53cbe0f6858e933eca15e9ce7e98b4d7cf57cb24d690a3a0d2b58cfc314
6
+ metadata.gz: af26f1a10253235703203804e508aa0d0dfbe7e44cc92097f70111587663c9af75e6cfd730fcb0986dc6093bb9bb16c847ab66ad7d3249057d3fe8adf443c78e
7
+ data.tar.gz: f98980a1937efeef70c1e548b2f62f5b2a7e2f383f707a92e8392a8f45b1bc9ac882a1f478fff0f24f1fcb3cb145e0f037202356ea4a2e76737ee81f4c70b459
data/CHANGELOG.md CHANGED
@@ -1,10 +1,37 @@
1
1
  # Change Log
2
2
 
3
+ ## VERSION 0.3.0 - 2024-xx-xx
4
+
5
+ - **cypher**
6
+ * query support
7
+ * paths support
8
+ * select attributes support
9
+ - **Paths**
10
+ * ?
11
+
12
+ ## VERSION 0.2.0 - 2024-05-26
13
+
14
+ - **Edges**
15
+ * add class methods to find_edge(with {properties, end_id, start_id})
16
+ * add missing methods to use in rails controllers
17
+ * validate edge start- & end-nodes are valid
18
+ * add unique edge validations
19
+ - **Nodes**
20
+ * add missing methods to use in rails controllers
21
+ * add unique node validations
22
+
3
23
  ## VERSION 0.1.0 - 2024-05-21
4
24
 
5
25
  Initial release has the following features:
6
26
 
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.
27
+ - **Nodes:**
28
+ * Create, Read, Update, Delete & .find(by id), .find_by(age_property), .all
29
+ * verified with usage in a controller and views
30
+ - **Edges:**
31
+ * Create, Read, Update, Delete & .find(by id), .find_by(age_property), .all
32
+ * verified with usage in a controller and views
33
+ - **Entities:**
34
+ * find (by id), find_by(age_property), all; when class/label and/or edge/node is unknown)
35
+
36
+ These can be used within Rails applications using a Rails APIs including within controllers and views.
37
+ See the [README](README.md) for more information.
data/README.md CHANGED
@@ -4,6 +4,8 @@ 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
@@ -22,6 +24,74 @@ Or install it yourself as:
22
24
  $ gem install rails_age
23
25
  ```
24
26
 
27
+ finally (tempoarily you need to copy and run the migration)
28
+ https://github.com/marpori/rails_age/blob/main/db/migrate/20240521062349_configure_apache_age.rb
29
+
30
+ ```bash
31
+ # db/migrate/20240521062349_configure_apache_age.rb
32
+ class ConfigureApacheAge < ActiveRecord::Migration[7.1]
33
+ def up
34
+ # Allow age extension
35
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
36
+
37
+ # Load the age code
38
+ execute("LOAD 'age';")
39
+
40
+ # Load the ag_catalog into the search path
41
+ execute('SET search_path = ag_catalog, "$user", public;')
42
+
43
+ # Create age_schema graph if it doesn't exist
44
+ execute("SELECT create_graph('age_schema');")
45
+ end
46
+
47
+ def down
48
+ execute <<-SQL
49
+ DO $$
50
+ BEGIN
51
+ IF EXISTS (
52
+ SELECT 1
53
+ FROM pg_constraint
54
+ WHERE conname = 'fk_graph_oid'
55
+ ) THEN
56
+ ALTER TABLE ag_catalog.ag_label
57
+ DROP CONSTRAINT fk_graph_oid;
58
+ END IF;
59
+ END $$;
60
+ SQL
61
+
62
+ execute("SELECT drop_graph('age_schema', true);")
63
+ execute('DROP SCHEMA IF EXISTS ag_catalog CASCADE;')
64
+ execute('DROP EXTENSION IF EXISTS age;')
65
+ end
66
+ end
67
+ ```
68
+
69
+ and fix the TOP of `schema.rb` file to match the following (note: the version number should be the same as the LARGEST version number in your `db/migrations` folder)
70
+ https://github.com/marpori/rails_age/blob/main/db/schema.rb
71
+
72
+ ```ruby
73
+ # db/schema.rb
74
+ ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
75
+ # These are extensions that must be enabled in order to support this database
76
+ enable_extension "plpgsql"
77
+
78
+ # Allow age extension
79
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
80
+
81
+ # Load the age code
82
+ execute("LOAD 'age';")
83
+
84
+ # Load the ag_catalog into the search path
85
+ execute('SET search_path = ag_catalog, "$user", public;')
86
+
87
+ # Create age_schema graph if it doesn't exist
88
+ execute("SELECT create_graph('age_schema');")
89
+
90
+ # other migrations
91
+ # ...
92
+ end
93
+ ```
94
+
25
95
  ## Contributing
26
96
 
27
97
  Create an MR and tests and I will review it.
@@ -34,7 +104,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
34
104
 
35
105
  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
106
 
37
- A full sample app can be found [here](https://github.com/btihen-dev/rails_graphdb_age_app) the summary usage is described below.
107
+ A full sample app can be found [here](https://github.com/marpori/rails_age_demo_app) the summary usage is described below.
38
108
 
39
109
  ### Nodes
40
110
 
@@ -42,7 +112,7 @@ A full sample app can be found [here](https://github.com/btihen-dev/rails_graphd
42
112
  # app/graphs/nodes/company.rb
43
113
  module Nodes
44
114
  class Company
45
- include ApacheAge::Vertex
115
+ include ApacheAge::Entities::Vertex
46
116
 
47
117
  attribute :company_name, :string
48
118
  validates :company_name, presence: true
@@ -54,7 +124,7 @@ end
54
124
  # app/graphs/nodes/person.rb
55
125
  module Nodes
56
126
  class Person
57
- include ApacheAge::Vertex
127
+ include ApacheAge::Entities::Vertex
58
128
 
59
129
  attribute :first_name, :string, default: nil
60
130
  attribute :last_name, :string, default: nil
@@ -80,10 +150,12 @@ end
80
150
  ```ruby
81
151
  # app/graphs/edges/works_at.rb
82
152
  module Edges
83
- class WorksAt
84
- include ApacheAge::Edge
153
+ class HasJob
154
+ include ApacheAge::Entities::Edge
85
155
 
86
156
  attribute :employee_role, :string
157
+ attribute :start_node, :person # if using optional age types
158
+ # attribute :end_node, :person # if using optional age types
87
159
  validates :employee_role, presence: true
88
160
  end
89
161
  end
@@ -98,10 +170,46 @@ fred.to_h
98
170
  quarry = Nodes::Company.create(company_name: 'Bedrock Quarry')
99
171
  quarry.to_h
100
172
 
101
- job = Edges::WorksAt.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
173
+ job = Edges::HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
102
174
  job.to_h
103
175
  ```
104
176
 
177
+ ### Update Routes
178
+
179
+ ```ruby
180
+ Rails.application.routes.draw do
181
+ # mount is not needed with the engine
182
+ # mount RailsAge::Engine => "/rails_age"
183
+
184
+ # defines the route for the people controller
185
+ resources :people
186
+
187
+ # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
188
+ # Can be used by load balancers and uptime monitors to verify that the app is live.
189
+ get 'up' => 'rails/health#show', as: :rails_health_check
190
+
191
+ # Defines the root path route ("/")
192
+ root 'people#index'
193
+ end
194
+ ```
195
+
196
+ ### Types (Optional)
197
+
198
+ ```ruby
199
+ # spec/dummy/config/initializers/types.rb
200
+ require 'apache_age/types/age_type_generator'
201
+
202
+ Rails.application.config.to_prepare do
203
+ # Ensure the files are loaded
204
+ require_dependency 'nodes/company'
205
+ require_dependency 'nodes/person'
206
+
207
+ # Register the custom types
208
+ ActiveModel::Type.register(:company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company))
209
+ ActiveModel::Type.register(:person, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Person))
210
+ end
211
+ ```
212
+
105
213
  ### Controller Usage
106
214
 
107
215
  ```ruby
@@ -176,3 +284,9 @@ class PeopleController < ApplicationController
176
284
  end
177
285
  end
178
286
  ```
287
+
288
+ ### Views
289
+
290
+ ```erb
291
+
292
+ ```
@@ -0,0 +1,112 @@
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_edge(attributes)
12
+ where_attribs =
13
+ attributes
14
+ .compact
15
+ .except(:end_id, :start_id, :end_node, :start_node)
16
+ .map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
17
+ where_attribs = where_attribs.empty? ? nil : where_attribs
18
+
19
+ end_id = attributes[:end_id] || attributes[:end_node]&.id
20
+ start_id = attributes[:start_id] || attributes[:start_node]&.id
21
+ where_end_id = end_id ? "id(end_node) = #{end_id}" : nil
22
+ where_start_id = start_id ? "id(start_node) = #{start_id}" : nil
23
+
24
+ where_clause = [where_attribs, where_start_id, where_end_id].compact.join(' AND ')
25
+ return nil if where_clause.empty?
26
+
27
+ cypher_sql = find_edge_sql(where_clause)
28
+ execute_find(cypher_sql)
29
+ end
30
+
31
+ def find_by(attributes)
32
+ where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
33
+ cypher_sql = find_sql(where_clause)
34
+ execute_find(cypher_sql)
35
+ end
36
+
37
+ def find(id)
38
+ where_clause = "id(find) = #{id}"
39
+ cypher_sql = find_sql(where_clause)
40
+ execute_find(cypher_sql)
41
+ end
42
+
43
+ def all
44
+ age_results = ActiveRecord::Base.connection.execute(all_sql)
45
+ return [] if age_results.values.count.zero?
46
+
47
+ age_results.values.map do |result|
48
+ json_string = result.first.split('::').first
49
+ hash = JSON.parse(json_string)
50
+ attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
51
+
52
+ new(**attribs)
53
+ end
54
+ end
55
+
56
+ # Private stuff
57
+
58
+ def age_graph = 'age_schema'
59
+ def age_label = name.gsub('::', '__')
60
+ def age_type = name.constantize.new.age_type
61
+
62
+ def match_clause
63
+ age_type == 'vertex' ? "(find:#{age_label})" : "(start_node)-[find:#{age_label}]->(end_node)"
64
+ end
65
+
66
+ def execute_find(cypher_sql)
67
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
68
+ return nil if age_result.values.count.zero?
69
+
70
+ age_type = age_result.values.first.first.split('::').last
71
+ json_data = age_result.values.first.first.split('::').first
72
+
73
+ hash = JSON.parse(json_data)
74
+ attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
75
+
76
+ new(**attribs)
77
+ end
78
+
79
+ def all_sql
80
+ <<-SQL
81
+ SELECT *
82
+ FROM cypher('#{age_graph}', $$
83
+ MATCH #{match_clause}
84
+ RETURN find
85
+ $$) as (#{age_label} agtype);
86
+ SQL
87
+ end
88
+
89
+ def find_sql(where_clause)
90
+ <<-SQL
91
+ SELECT *
92
+ FROM cypher('#{age_graph}', $$
93
+ MATCH #{match_clause}
94
+ WHERE #{where_clause}
95
+ RETURN find
96
+ $$) as (#{age_label} agtype);
97
+ SQL
98
+ end
99
+
100
+ def find_edge_sql(where_clause)
101
+ <<-SQL
102
+ SELECT *
103
+ FROM cypher('#{age_graph}', $$
104
+ MATCH #{match_clause}
105
+ WHERE #{where_clause}
106
+ RETURN find
107
+ $$) as (#{age_label} agtype);
108
+ SQL
109
+ end
110
+ end
111
+ end
112
+ 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
@@ -0,0 +1,69 @@
1
+ module ApacheAge
2
+ module Entities
3
+ class Entity
4
+ class << self
5
+ def find_by(attributes)
6
+ where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
7
+ handle_find(where_clause)
8
+ end
9
+
10
+ def find(id)
11
+ where_clause = "id(find) = #{id}"
12
+ handle_find(where_clause)
13
+ end
14
+
15
+ private
16
+
17
+ def age_graph = 'age_schema'
18
+
19
+ def handle_find(where_clause)
20
+ # try to find a vertex
21
+ match_node = '(find)'
22
+ cypher_sql = find_sql(match_node, where_clause)
23
+ age_response = execute_find(cypher_sql)
24
+
25
+ if age_response.nil?
26
+ # if not a vertex try to find an edge
27
+ match_edge = '()-[find]->()'
28
+ cypher_sql = find_sql(match_edge, where_clause)
29
+ age_response = execute_find(cypher_sql)
30
+ return nil if age_response.nil?
31
+ end
32
+
33
+ instantiate_result(age_response)
34
+ end
35
+
36
+ def execute_find(cypher_sql)
37
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
38
+ return nil if age_result.values.first.nil?
39
+
40
+ age_result
41
+ end
42
+
43
+ def instantiate_result(age_response)
44
+ age_type = age_response.values.first.first.split('::').last
45
+ json_string = age_response.values.first.first.split('::').first
46
+ json_data = JSON.parse(json_string)
47
+
48
+ age_label = json_data['label']
49
+ attribs = json_data.except('label', 'properties')
50
+ .merge(json_data['properties'])
51
+ .symbolize_keys
52
+
53
+ "#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
54
+ end
55
+
56
+ def find_sql(match_clause, where_clause)
57
+ <<-SQL
58
+ SELECT *
59
+ FROM cypher('#{age_graph}', $$
60
+ MATCH #{match_clause}
61
+ WHERE #{where_clause}
62
+ RETURN find
63
+ $$) as (found agtype);
64
+ SQL
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module Vertex
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
+
13
+ extend ApacheAge::Entities::ClassMethods
14
+ include ApacheAge::Entities::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
53
+ end