rails_age 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -10
- data/README.md +108 -57
- data/config/initializers/types.rb +20 -0
- data/lib/apache_age/edge.rb +5 -0
- data/lib/apache_age/entities/common_methods.rb +6 -0
- data/lib/apache_age/entities/edge.rb +4 -3
- data/lib/apache_age/entities/node.rb +53 -0
- data/lib/apache_age/entities/vertex.rb +47 -47
- data/lib/apache_age/node.rb +36 -0
- data/lib/apache_age/types/{age_type_generator.rb → factory.rb} +3 -3
- data/lib/apache_age/validators/expected_node_type.rb +17 -0
- data/lib/apache_age/validators/node_type_validator.rb +15 -0
- data/lib/apache_age/validators/unique_node.rb +32 -0
- data/lib/apache_age/validators/unique_vertex.rb +27 -27
- data/lib/apache_age/validators/vertex_type_validator.rb +14 -13
- data/lib/generators/apache_age/edge/USAGE +7 -7
- data/lib/generators/apache_age/edge/edge_generator.rb +1 -0
- data/lib/generators/apache_age/edge/templates/edge.rb.tt +3 -3
- data/lib/generators/apache_age/generator_entity_helpers.rb +6 -11
- data/lib/generators/apache_age/node/USAGE +4 -4
- data/lib/generators/apache_age/node/node_generator.rb +1 -1
- data/lib/generators/apache_age/node/templates/node.rb.tt +2 -2
- data/lib/generators/apache_age/scaffold_edge/USAGE +16 -0
- data/lib/generators/apache_age/scaffold_edge/scaffold_edge_generator.rb +66 -0
- data/lib/generators/apache_age/scaffold_edge/templates/controller.rb.tt +50 -0
- data/lib/generators/apache_age/{scaffold_node/templates/views/_form.html.erb copy.tt → scaffold_edge/templates/views/_form.html.erb.tt} +10 -0
- data/lib/generators/apache_age/{scaffold_node/templates/views/index.html.erb copy.tt → scaffold_edge/templates/views/index.html.erb.tt} +1 -1
- data/lib/generators/apache_age/{scaffold_node/templates/views/partial.html.erb copy.tt → scaffold_edge/templates/views/partial.html.erb.tt} +11 -2
- data/lib/generators/apache_age/{scaffold_node/templates/views/show.html.erb copy.tt → scaffold_edge/templates/views/show.html.erb.tt} +1 -1
- data/lib/generators/apache_age/scaffold_node/scaffold_node_generator.rb +20 -22
- data/lib/rails_age/version.rb +1 -1
- data/lib/rails_age.rb +7 -4
- data/lib/tasks/config_types.rake +8 -8
- metadata +48 -10
- /data/lib/generators/apache_age/{scaffold_node/templates/views/edit.html.erb copy.tt → scaffold_edge/templates/views/edit.html.erb.tt} +0 -0
- /data/lib/generators/apache_age/{scaffold_node/templates/views/new.html.erb copy.tt → scaffold_edge/templates/views/new.html.erb.tt} +0 -0
- /data/lib/generators/apache_age/scaffold_node/templates/{node_controller.rb.tt → controller.rb.tt} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6777870b945c8684ea2ef5d10b8e7411259e002001b074754840a9422b594cee
|
4
|
+
data.tar.gz: '008ab62bbe28d408d390acd5e00cecc764507e21ae3d38ca94d219e9d299435f'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a9c122ce1a8b5b173fc9ffd6ba45a4bf97965eb0681ec0d44e9779dcc1ef647da8e7e5110a5564a9f28c329ebe9e83ba832d920f4504bb5026650f41cc102d2
|
7
|
+
data.tar.gz: f9217b16e0177746a438ab5fed8a4e5c7f4bb6ff02b3e31a2a4d1db7a8bd246550e33be6494e0533c70dcbcde3a54078ed041b2c3a08718cb9b020c73353eee5
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## VERSION 0.6.
|
3
|
+
## VERSION 0.6.4 - 2024-xx-xx
|
4
4
|
|
5
5
|
- **cypher queries** (like active record queries)
|
6
6
|
* schema override
|
@@ -8,11 +8,11 @@
|
|
8
8
|
* paths support
|
9
9
|
* select attributes support
|
10
10
|
|
11
|
-
## VERSION 0.6.
|
11
|
+
## VERSION 0.6.3 - 2024-xx-xx
|
12
12
|
|
13
13
|
- **Age Path**
|
14
14
|
|
15
|
-
## VERSION 0.6.
|
15
|
+
## VERSION 0.6.2 - 2024-xx-xx
|
16
16
|
|
17
17
|
breaking change?: namespaces (by default) will use their own schema? (add to database.yml & schema.rb ?)
|
18
18
|
|
@@ -20,24 +20,44 @@ breaking change?: namespaces (by default) will use their own schema? (add to dat
|
|
20
20
|
|
21
21
|
- **multiple AGE Schema**
|
22
22
|
|
23
|
-
## VERSION 0.
|
24
|
-
|
25
|
-
- **Edge Scaffold** (generates edge, type, view and controller)
|
26
|
-
* add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
|
23
|
+
## VERSION 0.6.1 - 2024-xx-xx
|
27
24
|
|
28
|
-
|
25
|
+
- **Fix**
|
26
|
+
* show validation errors in scaffold views
|
29
27
|
|
30
28
|
- **Edge Generator**
|
31
29
|
* add start-/end-nodes types to edge generator (would make scaffold easier), ie:
|
32
30
|
`rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
|
33
31
|
with property and specified start-/end-nodes (person and pet nodes must have already been created)
|
34
32
|
|
33
|
+
- **Edge Scaffold** (generates edge, type, view and controller)
|
34
|
+
* add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
|
35
|
+
|
36
|
+
## VERSION 0.6.0 - 2024-06-xx
|
37
|
+
|
38
|
+
**breaking changes**: update naming
|
39
|
+
* renamed `Entities::Vertex` module to `Entities::Node`
|
40
|
+
* renamed `UniqueVertex` to `UniqueNode`
|
41
|
+
* rebamed `AgeTypeGenerator` to `Type::Factory`
|
42
|
+
* move `lib/generators/*` intp `lib/apache_age/generators`
|
43
|
+
|
44
|
+
here is the [commit](https://github.com/marpori/rails_age_demo_app/commit/a6f0708f2bbc165eddbafe63896068a72d803b17) to see the changes te demo app to make it work for release 0.6.0
|
45
|
+
|
46
|
+
## VERSION 0.5.3 - 2024-06-23
|
47
|
+
|
48
|
+
- **Edge Scaffold** (generates edge, type, view and controller) - without start-/end-nodes types!?
|
49
|
+
* add `rails generate apache_age:edge_scaffold HasJob employee_role`
|
50
|
+
* add system test (to dummy app after scaffold_node is run)
|
51
|
+
|
52
|
+
- **Node Scaffold** (generates node, type, view and controller)
|
53
|
+
* add system test (to dummy app after scaffold_node is run)
|
54
|
+
|
35
55
|
## VERSION 0.5.2 - 2024-06-16
|
36
56
|
|
37
57
|
- **Node Scaffold** (generates node, type, view and controller)
|
38
58
|
* add `rails generate apache_age:node_scaffold Person first_name last_name age:integer`
|
39
59
|
|
40
|
-
## VERSION 0.5.1 - 2024-06-16
|
60
|
+
## VERSION 0.5.1 - 2024-06-16 (yanked)
|
41
61
|
|
42
62
|
**yanked** (2024-06-16) - had an issue with the generator
|
43
63
|
|
@@ -50,7 +70,7 @@ breaking change?: namespaces (by default) will use their own schema? (add to dat
|
|
50
70
|
|
51
71
|
- **Edge Generator**
|
52
72
|
* add `rails generate apache_age:edge HasPet owner_role`
|
53
|
-
caveate: start_node and end_node are of type `:
|
73
|
+
caveate: start_node and end_node are of type `:node` in the generator but can be changed manually in the class file - having trouble with the generator loading the types (the generator rejects custom types - but rails still works with custom types)
|
54
74
|
|
55
75
|
## VERSION 0.4.1 - 2024-06-15
|
56
76
|
|
data/README.md
CHANGED
@@ -1,25 +1,76 @@
|
|
1
1
|
# RailsAge
|
2
2
|
|
3
|
-
|
3
|
+
Apache Age integration within a Rails application.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Quick Start - Essentials
|
6
6
|
|
7
7
|
**NOTE:** you must be using Postgres as your database! Apache Age requires it.
|
8
8
|
|
9
|
-
|
9
|
+
```bash
|
10
|
+
bundle add rails_age
|
11
|
+
bundle install
|
12
|
+
bin/rails apache_age:install
|
13
|
+
# optional: prevents `bin/rails db:migrate` from modifying the schema file,
|
14
|
+
# bin/rails apache_age:override_db_migrate
|
15
|
+
git add .
|
16
|
+
git commit -m "Add & configure Apache Age within Rails"
|
17
|
+
```
|
18
|
+
|
19
|
+
## Generators
|
20
|
+
|
21
|
+
**NODES**
|
22
|
+
|
23
|
+
```bash
|
24
|
+
rails generate apache_age:scaffold_node Company company_name
|
25
|
+
|
26
|
+
rails generate apache_age:scaffold_node Person first_name last_name
|
27
|
+
```
|
28
|
+
|
29
|
+
**EDGES**
|
10
30
|
|
31
|
+
```bash
|
32
|
+
rails generate apache_age:scaffold_edge HasJob employee_role start_date:date
|
33
|
+
```
|
34
|
+
|
35
|
+
Ideally, edit the HasJob class so that `start_node` would use a type `:person` and the `end_node` uses at type `:company` - this is not yet supported by the generator, but easy to do manually as shown below. (The problem is that I havent been able to figure out how load all the rails types in the testing environment).
|
36
|
+
|
37
|
+
ie:
|
11
38
|
```ruby
|
12
|
-
|
39
|
+
# app/edges/has_job.rb
|
40
|
+
class HasJob
|
41
|
+
include ApacheAge::Entities::Edge
|
42
|
+
|
43
|
+
attribute :employee_role, :string
|
44
|
+
attribute :start_node, :person
|
45
|
+
attribute :end_node, :company
|
46
|
+
|
47
|
+
validates :employee_role, presence: true
|
48
|
+
validate :validate_unique_edge
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validate_unique_edge
|
53
|
+
ApacheAge::Validators::UniqueEdge
|
54
|
+
.new(attributes: %i[employee_role start_node end_node])
|
55
|
+
.validate(self)
|
56
|
+
end
|
57
|
+
end
|
13
58
|
```
|
14
59
|
|
15
|
-
##
|
60
|
+
## Installation in Detail
|
16
61
|
|
17
62
|
using the installer, creates the migration to install age, runs the migration, and adjusts the schema file, and updates the `config/database.yml` file.
|
18
63
|
|
19
|
-
|
20
|
-
|
64
|
+
### Install Apache Age
|
65
|
+
|
66
|
+
see: [Apache AGE Installation](https://age.apache.org/age-manual/master/intro/setup.html#installation)
|
67
|
+
(The docker install is probably the easiest way to get started with a new application - for existing applications you may need to compile the extension from source.)
|
68
|
+
|
69
|
+
Verify your PostgreSQL AGE with the following commands:
|
70
|
+
|
21
71
|
```bash
|
22
|
-
psql -h localhost -p 5455 -U docker_username
|
72
|
+
$ psql -h localhost -p 5455 -U docker_username
|
73
|
+
|
23
74
|
> CREATE EXTENSION IF NOT EXISTS age;
|
24
75
|
> LOAD 'age';
|
25
76
|
> SET search_path = ag_catalog, "$user", public;
|
@@ -27,7 +78,12 @@ psql -h localhost -p 5455 -U docker_username
|
|
27
78
|
> \q
|
28
79
|
```
|
29
80
|
|
81
|
+
### Install and Configure Rails (if not done already)
|
82
|
+
|
83
|
+
AGE REQUIRES POSTGRESQL!
|
84
|
+
|
30
85
|
create a new Rails app (WITH POSTGRESQL!)
|
86
|
+
|
31
87
|
```bash
|
32
88
|
rails new age_demo -d postgresql
|
33
89
|
cd age_demo
|
@@ -35,6 +91,7 @@ git add .
|
|
35
91
|
git commit -m "Initial Rails App"
|
36
92
|
```
|
37
93
|
configure `config/database.yml` when using the docker version of AGE DB my config looks like:
|
94
|
+
|
38
95
|
```yaml
|
39
96
|
port: 5455
|
40
97
|
host: localhost
|
@@ -42,15 +99,19 @@ username: docker_username
|
|
42
99
|
password: dockerized_password
|
43
100
|
```
|
44
101
|
|
45
|
-
|
102
|
+
If both the Rails DB config and AGE DB are correctly configured, you should be able to run the following command without error:
|
103
|
+
|
46
104
|
```bash
|
47
105
|
rails db:create
|
48
106
|
rails db:migrate
|
49
107
|
git add .
|
50
|
-
git commit -m "
|
108
|
+
git commit -m "Basic Rails Configuration"
|
51
109
|
```
|
52
110
|
|
53
|
-
install Apache Age
|
111
|
+
### install Apache Age Plugin
|
112
|
+
|
113
|
+
NOTE: _ignore the `unknown OID` warnings_
|
114
|
+
|
54
115
|
```bash
|
55
116
|
bundle add rails_age
|
56
117
|
bundle install
|
@@ -62,62 +123,52 @@ git add .
|
|
62
123
|
git commit -m "Add & configure Apache Age within Rails"
|
63
124
|
```
|
64
125
|
|
65
|
-
|
126
|
+
### Optional Migration override (OPTIONAL)
|
127
|
+
|
128
|
+
run `bin/rails apache_age:override_db_migrate` to ensure that running `rails db:migrate` does not inappropriately modify the schema file.
|
129
|
+
|
130
|
+
However, if you are familiar with the schema file and git then you can safely ignore this step and manage the changes after a migration manually - only submitting changes directly related to the newest migration and not those related AGE.
|
131
|
+
|
132
|
+
**NOTE:**
|
133
|
+
* **You can run `bin/rails apache_age:config_schema` at any time to repair the schema file as needed.**
|
134
|
+
( **You can run `bin/rails apache_age:install` at any time to repair any AGE related config file**
|
135
|
+
|
136
|
+
If you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge).
|
137
|
+
|
138
|
+
### NODE Scaffold Generation
|
139
|
+
|
66
140
|
```bash
|
67
|
-
rails generate apache_age:
|
68
|
-
|
69
|
-
|
141
|
+
rails generate apache_age:scaffold_node Company company_name:string
|
142
|
+
|
143
|
+
# string is the default type (so it can be omitted)
|
144
|
+
rails generate apache_age:scaffold_node Person first_name last_name
|
145
|
+
|
146
|
+
# with a namespace
|
147
|
+
rails generate apache_age:scaffold_node Animals/Pet pet_name birthdate:date
|
70
148
|
```
|
71
|
-
|
149
|
+
|
150
|
+
### EDGE Scaffold Generation**
|
151
|
+
|
152
|
+
NOTE: the generator will only allow `:node` (default type) for start_node and end_node, however, it is strongly recommended to specify the start_node and end_node types manually. _Hopefully, I can find a way to get the generators to recognize and allow the usage of custom node types. Thus eventually, I hope: `rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role` will work._
|
153
|
+
|
72
154
|
```bash
|
73
|
-
# when start node and end node are not specified they are of type `:vertex`
|
74
|
-
# this is generally not recommended - exept when very generic relationships are needed
|
75
155
|
rails generate apache_age:edge HasJob employee_role begin_date:date
|
76
|
-
|
77
|
-
# # this is recommended - (but not yet working) add explicit start_node and end_node types manually
|
78
|
-
# rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role
|
79
156
|
```
|
80
157
|
|
81
|
-
|
82
|
-
|
83
|
-
For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
|
158
|
+
_edge scaffold is coming soon._
|
84
159
|
|
85
|
-
|
160
|
+
```bash
|
161
|
+
# without a namespace
|
162
|
+
rails generate apache_age:scaffold_edge HasPet caretaker_role
|
86
163
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
fred = Person.new(first_name: 'Fredrick Jay', last_name: 'Flintstone')
|
91
|
-
fred.valid?
|
92
|
-
fred.save
|
93
|
-
fred.to_h # should have an ID
|
94
|
-
|
95
|
-
# fails because of a missing required field (Property)
|
96
|
-
incomplete = Person.new(first_name: 'Fredrick Jay')
|
97
|
-
incomplete.valid?
|
98
|
-
incomplete.errors
|
99
|
-
incomplete.to_h
|
100
|
-
|
101
|
-
# fails because of uniqueness constraints
|
102
|
-
jay = Person.create(first_name: 'Fredrick Jay', last_name: 'Flintstone')
|
103
|
-
jay.to_h
|
104
|
-
=> {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
|
105
|
-
jay.valid?
|
106
|
-
=> false
|
107
|
-
jay.errors
|
108
|
-
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=record not unique, options={}>, #<ActiveModel::Error attribute=first_name, type=property combination not unique, options={}>, #<ActiveModel::Error attribute=last_name, type=property combination not unique, options={}>]>
|
109
|
-
irb(main):008> jav.to_h
|
110
|
-
=> {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
|
111
|
-
|
112
|
-
# .create is a shortcut for .new and .save
|
113
|
-
quarry = Company.create(company_name: 'Bedrock Quarry')
|
114
|
-
quarry.to_h # should have an ID
|
115
|
-
|
116
|
-
# create an edge (no generator yet)
|
117
|
-
job = HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
|
118
|
-
job.to_h # should have an ID
|
164
|
+
# with a namespace
|
165
|
+
rails generate apache_age:scaffold_edge People/HasSpouse spousal_role
|
119
166
|
```
|
120
167
|
|
168
|
+
### AGE Usage within Rails Console
|
169
|
+
|
170
|
+
see [AGE Usage within Rails Console](AGE_CONSOLE_USAGE.md)
|
171
|
+
|
121
172
|
## Manual Install, Config and Usage
|
122
173
|
|
123
174
|
see [Manuel Installation, Configuration and Usage](MANUAL_INSTALL.md)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# config/initializers/types.rb
|
2
|
+
|
3
|
+
require 'apache_age/types/factory'
|
4
|
+
# USAGE (with edges or nodes) - ie:
|
5
|
+
# require_dependency 'company'
|
6
|
+
# ActiveModel::Type.register(
|
7
|
+
# :company, ApacheAge::Types::Factory.type_for(Company)
|
8
|
+
# )
|
9
|
+
|
10
|
+
Rails.application.config.to_prepare do
|
11
|
+
# Register AGE types
|
12
|
+
require_dependency 'apache_age/node'
|
13
|
+
ActiveModel::Type.register(
|
14
|
+
:node, ApacheAge::Types::Factory.type_for(ApacheAge::Node)
|
15
|
+
)
|
16
|
+
require_dependency 'apache_age/edge'
|
17
|
+
ActiveModel::Type.register(
|
18
|
+
:edge, ApacheAge::Types::Factory.type_for(ApacheAge::Edge)
|
19
|
+
)
|
20
|
+
end
|
@@ -7,6 +7,12 @@ module ApacheAge
|
|
7
7
|
def persisted? = id.present?
|
8
8
|
def to_s = ":#{age_label} #{properties_to_s}"
|
9
9
|
|
10
|
+
# default display value
|
11
|
+
def display
|
12
|
+
info = age_properties&.values&.first
|
13
|
+
info.blank? ? "#{age_label} (#{id})" : "#{info} (#{age_label})"
|
14
|
+
end
|
15
|
+
|
10
16
|
def to_h
|
11
17
|
base_h = attributes.to_hash
|
12
18
|
if age_type == 'edge'
|
@@ -9,11 +9,12 @@ module ApacheAge
|
|
9
9
|
include ActiveModel::Attributes
|
10
10
|
|
11
11
|
attribute :id, :integer
|
12
|
+
# attribute :label, :string
|
12
13
|
attribute :end_id, :integer
|
13
14
|
attribute :start_id, :integer
|
14
|
-
#
|
15
|
-
attribute :end_node
|
16
|
-
attribute :start_node
|
15
|
+
# override with a specific node type in the defining class
|
16
|
+
attribute :end_node
|
17
|
+
attribute :start_node
|
17
18
|
|
18
19
|
validates :end_node, :start_node, presence: true
|
19
20
|
validate :validate_nodes
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ApacheAge
|
2
|
+
module Entities
|
3
|
+
module Node
|
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
|
@@ -1,53 +1,53 @@
|
|
1
|
-
module ApacheAge
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# module ApacheAge
|
2
|
+
# module Entities
|
3
|
+
# module Vertex
|
4
|
+
# extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# included do
|
7
|
+
# include ActiveModel::Model
|
8
|
+
# include ActiveModel::Dirty
|
9
|
+
# include ActiveModel::Attributes
|
10
10
|
|
11
|
-
|
11
|
+
# attribute :id, :integer
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
# extend ApacheAge::Entities::ClassMethods
|
14
|
+
# include ApacheAge::Entities::CommonMethods
|
15
|
+
# end
|
16
16
|
|
17
|
-
|
17
|
+
# def age_type = 'vertex'
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
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
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ApacheAge
|
2
|
+
class Node
|
3
|
+
include ApacheAge::Entities::Node
|
4
|
+
|
5
|
+
attribute :label, :string
|
6
|
+
attribute :properties
|
7
|
+
# attribute :properties, :hash
|
8
|
+
|
9
|
+
# def display = [label, properties&.values&.first].compact.join(' - ')
|
10
|
+
def display
|
11
|
+
info = properties&.values&.first
|
12
|
+
info.blank? ? "#{label} (#{id})" : "#{info} (#{label})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
all_nodes_sql = <<~SQL
|
17
|
+
SELECT *
|
18
|
+
FROM cypher('age_schema', $$
|
19
|
+
MATCH (node)
|
20
|
+
RETURN node
|
21
|
+
$$) as (node agtype);
|
22
|
+
SQL
|
23
|
+
age_results = ActiveRecord::Base.connection.execute(all_nodes_sql)
|
24
|
+
return [] if age_results.values.count.zero?
|
25
|
+
|
26
|
+
age_results.values.map do |result|
|
27
|
+
json_string = result.first.split('::').first
|
28
|
+
hash = JSON.parse(json_string)
|
29
|
+
attribs = hash.slice('id', 'label').symbolize_keys
|
30
|
+
attribs[:properties] = hash['properties'].symbolize_keys
|
31
|
+
|
32
|
+
new(**attribs)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# lib/apache_age/types/
|
1
|
+
# lib/apache_age/types/factory.rb
|
2
2
|
# Automatically generates ActiveModel::Type classes
|
3
3
|
# Dynamically builds this (as a concrete example):
|
4
4
|
# module ApacheAge
|
@@ -22,8 +22,8 @@
|
|
22
22
|
# end
|
23
23
|
module ApacheAge
|
24
24
|
module Types
|
25
|
-
class
|
26
|
-
def self.
|
25
|
+
class Factory
|
26
|
+
def self.type_for(klass)
|
27
27
|
Class.new(ActiveModel::Type::Value) do
|
28
28
|
define_method(:cast) do |value|
|
29
29
|
case value
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ApacheAge
|
2
|
+
module Validators
|
3
|
+
module ExpectedNodeType
|
4
|
+
def
|
5
|
+
# Register the AGE types vertex_attribute(attribute_name, type_symbol, klass)
|
6
|
+
attribute attribute_name, type_symbol
|
7
|
+
|
8
|
+
validate do
|
9
|
+
value = send(attribute_name)
|
10
|
+
unless value.is_a?(klass)
|
11
|
+
errors.add(attribute_name, "must be a #{klass.name}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ApacheAge
|
2
|
+
module NodeTypeValidator
|
3
|
+
def
|
4
|
+
# Register the AGE typesvertex_attribute(attribute_name, type_symbol, klass)
|
5
|
+
attribute attribute_name, type_symbol
|
6
|
+
|
7
|
+
validate do
|
8
|
+
value = send(attribute_name)
|
9
|
+
unless value.is_a?(klass)
|
10
|
+
errors.add(attribute_name, "must be a #{klass.name}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# lib/apache_age/validators/unique_node.rb
|
2
|
+
|
3
|
+
# Usage:
|
4
|
+
# validates_with(
|
5
|
+
# ApacheAge::Validators::UniqueNode,
|
6
|
+
# attributes: [:first_name, :last_name, :gender]
|
7
|
+
# )
|
8
|
+
|
9
|
+
module ApacheAge
|
10
|
+
module Validators
|
11
|
+
class UniqueNode < ActiveModel::Validator
|
12
|
+
def validate(record)
|
13
|
+
allowed_keys = record.age_properties.keys
|
14
|
+
attributes = options[:attributes]
|
15
|
+
return if attributes.blank?
|
16
|
+
|
17
|
+
record_attribs =
|
18
|
+
attributes
|
19
|
+
.map { |attr| [attr, record.send(attr)] }
|
20
|
+
.to_h.symbolize_keys
|
21
|
+
.slice(*allowed_keys)
|
22
|
+
query = record.class.find_by(record_attribs)
|
23
|
+
|
24
|
+
# if no match is found or if it finds itself, it's valid
|
25
|
+
return if query.blank? || (query.id == record.id)
|
26
|
+
|
27
|
+
record.errors.add(:base, 'record not unique')
|
28
|
+
attributes.each { record.errors.add(_1, 'property combination not unique') }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|