rails_age 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45fb3b7f94084761a6a3d6a26c9e4d86f3b790768ba52d5a5735713bb978d37b
4
+ data.tar.gz: af3866f393670857680d9334280058a0542155f3b76cb2cc64648fa0875e9f92
5
+ SHA512:
6
+ metadata.gz: 774867c2584780ca6bbae3edbdf82fd592ebb3b3cb65c8f97400ceba5b37db650ce7c0ec9da1b45b30c2cf789e1d2e2ca776b97cbfc8ac431b3af234b4ba9efe
7
+ data.tar.gz: 05c03b0e12a5c77a53463cdfae58cce43b1f0cddfb811cad4a81e9ff137d49930434f53cbe0f6858e933eca15e9ce7e98b4d7cf57cb24d690a3a0d2b58cfc314
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Change Log
2
+
3
+ ## VERSION 0.1.0 - 2024-05-21
4
+
5
+ Initial release has the following features:
6
+
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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Bill Tihen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # RailsAge
2
+
3
+ Simplify Apache Age usage within a Rails application.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "rails_age"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install rails_age
23
+ ```
24
+
25
+ ## Contributing
26
+
27
+ Create an MR and tests and I will review it.
28
+
29
+ ## License
30
+
31
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
32
+
33
+ ## Usage
34
+
35
+ 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
+
37
+ A full sample app can be found [here](https://github.com/btihen-dev/rails_graphdb_age_app) the summary usage is described below.
38
+
39
+ ### Nodes
40
+
41
+ ```ruby
42
+ # app/graphs/nodes/company.rb
43
+ module Nodes
44
+ class Company
45
+ include ApacheAge::Vertex
46
+
47
+ attribute :company_name, :string
48
+ validates :company_name, presence: true
49
+ end
50
+ end
51
+ ```
52
+
53
+ ```ruby
54
+ # app/graphs/nodes/person.rb
55
+ module Nodes
56
+ class Person
57
+ include ApacheAge::Vertex
58
+
59
+ attribute :first_name, :string, default: nil
60
+ attribute :last_name, :string, default: nil
61
+ attribute :given_name, :string, default: nil
62
+ attribute :nick_name, :string, default: nil
63
+ attribute :gender, :string, default: nil
64
+
65
+ validates :gender, :first_name, :last_name, :given_name, :nick_name,
66
+ presence: true
67
+
68
+ def initialize(**attributes)
69
+ super
70
+ # use unless present? since attributes when empty sets to "" by default
71
+ self.nick_name = first_name unless nick_name.present?
72
+ self.given_name = last_name unless given_name.present?
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Edges
79
+
80
+ ```ruby
81
+ # app/graphs/edges/works_at.rb
82
+ module Edges
83
+ class WorksAt
84
+ include ApacheAge::Edge
85
+
86
+ attribute :employee_role, :string
87
+ validates :employee_role, presence: true
88
+ end
89
+ end
90
+ ```
91
+
92
+ ### Rails Console Usage
93
+
94
+ ```ruby
95
+ fred = Nodes::Person.create(first_name: 'Fredrick Jay', nick_name: 'Fred', last_name: 'Flintstone', gender: 'male')
96
+ fred.to_h
97
+
98
+ quarry = Nodes::Company.create(company_name: 'Bedrock Quarry')
99
+ quarry.to_h
100
+
101
+ job = Edges::WorksAt.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
102
+ job.to_h
103
+ ```
104
+
105
+ ### Controller Usage
106
+
107
+ ```ruby
108
+ # app/controllers/people_controller.rb
109
+ class PeopleController < ApplicationController
110
+ before_action :set_person, only: %i[show edit update destroy]
111
+
112
+ # GET /people or /people.json
113
+ def index
114
+ @people = Nodes::Person.all
115
+ end
116
+
117
+ # GET /people/1 or /people/1.json
118
+ def show; end
119
+
120
+ # GET /people/new
121
+ def new
122
+ @person = Nodes::Person.new
123
+ end
124
+
125
+ # GET /people/1/edit
126
+ def edit; end
127
+
128
+ # POST /people or /people.json
129
+ def create
130
+ @person = Nodes::Person.new(**person_params)
131
+ respond_to do |format|
132
+ if @person.save
133
+ format.html { redirect_to person_url(@person), notice: 'Person was successfully created.' }
134
+ format.json { render :show, status: :created, location: @person }
135
+ else
136
+ format.html { render :new, status: :unprocessable_entity }
137
+ format.json { render json: @person.errors, status: :unprocessable_entity }
138
+ end
139
+ end
140
+ end
141
+
142
+ # PATCH/PUT /people/1 or /people/1.json
143
+ def update
144
+ respond_to do |format|
145
+ if @person.update(**person_params)
146
+ format.html { redirect_to person_url(@person), notice: 'Person was successfully updated.' }
147
+ format.json { render :show, status: :ok, location: @person }
148
+ else
149
+ format.html { render :edit, status: :unprocessable_entity }
150
+ format.json { render json: @person.errors, status: :unprocessable_entity }
151
+ end
152
+ end
153
+ end
154
+
155
+ # DELETE /people/1 or /people/1.json
156
+ def destroy
157
+ @person.destroy!
158
+
159
+ respond_to do |format|
160
+ format.html { redirect_to people_url, notice: 'Person was successfully destroyed.' }
161
+ format.json { head :no_content }
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ # Use callbacks to share common setup or constraints between actions.
168
+ def set_person
169
+ @person = Nodes::Person.find(params[:id])
170
+ end
171
+
172
+ # Only allow a list of trusted parameters through.
173
+ def person_params
174
+ # params.fetch(:person, {})
175
+ params.require(:nodes_person).permit(:first_name, :last_name, :nick_name, :given_name, :gender)
176
+ end
177
+ end
178
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/rails_age .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module RailsAge
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RailsAge
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RailsAge
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module RailsAge
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module RailsAge
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rails age</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "rails_age/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ RailsAge::Engine.routes.draw do
2
+ end
@@ -0,0 +1,35 @@
1
+ class ConfigureApacheAge < ActiveRecord::Migration[7.1]
2
+ def up
3
+ # Allow age extension
4
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
5
+
6
+ # Load the age code
7
+ execute("LOAD 'age';")
8
+
9
+ # Load the ag_catalog into the search path
10
+ execute('SET search_path = ag_catalog, "$user", public;')
11
+
12
+ # Create age_schema graph if it doesn't exist
13
+ execute("SELECT create_graph('age_schema');")
14
+ end
15
+
16
+ def down
17
+ execute <<-SQL
18
+ DO $$
19
+ BEGIN
20
+ IF EXISTS (
21
+ SELECT 1
22
+ FROM pg_constraint
23
+ WHERE conname = 'fk_graph_oid'
24
+ ) THEN
25
+ ALTER TABLE ag_catalog.ag_label
26
+ DROP CONSTRAINT fk_graph_oid;
27
+ END IF;
28
+ END $$;
29
+ SQL
30
+
31
+ execute("SELECT drop_graph('age_schema', true);")
32
+ execute('DROP SCHEMA IF EXISTS ag_catalog CASCADE;')
33
+ execute('DROP EXTENSION IF EXISTS age;')
34
+ end
35
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,16 @@
1
+ ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
2
+ # These are extensions that must be enabled in order to support this database
3
+ enable_extension "plpgsql"
4
+
5
+ # Allow age extension
6
+ execute('CREATE EXTENSION IF NOT EXISTS age;')
7
+
8
+ # Load the age code
9
+ execute("LOAD 'age';")
10
+
11
+ # Load the ag_catalog into the search path
12
+ execute('SET search_path = ag_catalog, "$user", public;')
13
+
14
+ # Create age_schema graph if it doesn't exist
15
+ execute("SELECT create_graph('age_schema');")
16
+ end
@@ -0,0 +1,75 @@
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
@@ -0,0 +1,126 @@
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
@@ -0,0 +1,64 @@
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
@@ -0,0 +1,67 @@
1
+ module ApacheAge
2
+ class Entity
3
+ class << self
4
+ def find_by(attributes)
5
+ where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
6
+ handle_find(where_clause)
7
+ end
8
+
9
+ def find(id)
10
+ where_clause = "id(find) = #{id}"
11
+ handle_find(where_clause)
12
+ end
13
+
14
+ private
15
+
16
+ def age_graph = 'age_schema'
17
+
18
+ def handle_find(where_clause)
19
+ # try to find a vertex
20
+ match_node = '(find)'
21
+ cypher_sql = find_sql(match_node, where_clause)
22
+ age_response = execute_find(cypher_sql)
23
+
24
+ if age_response.nil?
25
+ # if not a vertex try to find an edge
26
+ match_edge = '()-[find]->()'
27
+ cypher_sql = find_sql(match_edge, where_clause)
28
+ age_response = execute_find(cypher_sql)
29
+ return nil if age_response.nil?
30
+ end
31
+
32
+ instantiate_result(age_response)
33
+ end
34
+
35
+ def execute_find(cypher_sql)
36
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
37
+ return nil if age_result.values.first.nil?
38
+
39
+ age_result
40
+ end
41
+
42
+ def instantiate_result(age_response)
43
+ age_type = age_response.values.first.first.split('::').last
44
+ json_string = age_response.values.first.first.split('::').first
45
+ json_data = JSON.parse(json_string)
46
+
47
+ age_label = json_data['label']
48
+ attribs = json_data.except('label', 'properties')
49
+ .merge(json_data['properties'])
50
+ .symbolize_keys
51
+
52
+ "#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
53
+ end
54
+
55
+ def find_sql(match_clause, where_clause)
56
+ <<-SQL
57
+ SELECT *
58
+ FROM cypher('#{age_graph}', $$
59
+ MATCH #{match_clause}
60
+ WHERE #{where_clause}
61
+ RETURN find
62
+ $$) as (found agtype);
63
+ SQL
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ module ApacheAge
2
+ module Vertex
3
+ extend ActiveSupport::Concern
4
+ # include ApacheAge::Entity
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::ClassMethods
14
+ include ApacheAge::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
@@ -0,0 +1,5 @@
1
+ module RailsAge
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RailsAge
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module RailsAge
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rails_age.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "rails_age/version"
2
+ require "rails_age/engine"
3
+
4
+ module RailsAge
5
+ # Your code goes here...
6
+ end
7
+
8
+ module ApacheAge
9
+ require "apache_age/class_methods"
10
+ require "apache_age/common_methods"
11
+ require "apache_age/edge"
12
+ require "apache_age/entity"
13
+ require "apache_age/vertex"
14
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails_age do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_age
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bill Tihen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.1.3.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Apache AGE plugin for Rails 7.1
42
+ email:
43
+ - btihen@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - MIT-LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - app/assets/config/rails_age_manifest.js
53
+ - app/assets/stylesheets/rails_age/application.css
54
+ - app/controllers/rails_age/application_controller.rb
55
+ - app/helpers/rails_age/application_helper.rb
56
+ - app/jobs/rails_age/application_job.rb
57
+ - app/mailers/rails_age/application_mailer.rb
58
+ - app/models/rails_age/application_record.rb
59
+ - app/views/layouts/rails_age/application.html.erb
60
+ - config/routes.rb
61
+ - db/migrate/20240521062349_configure_apache_age.rb
62
+ - 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
68
+ - lib/rails_age.rb
69
+ - lib/rails_age/engine.rb
70
+ - lib/rails_age/version.rb
71
+ - lib/tasks/rails_age_tasks.rake
72
+ homepage: https://github.com/marpori/rails_age
73
+ licenses:
74
+ - MIT
75
+ metadata:
76
+ homepage_uri: https://github.com/marpori/rails_age
77
+ source_code_uri: https://github.com/marpori/rails_age
78
+ changelog_uri: https://github.com/marpori/rails_age/blob/main/CHANGELOG.md
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.5.9
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Apache AGE plugin for Rails 7.1
98
+ test_files: []