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 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