rails_age 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []