rails_age 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0856a8b6ebd00d824cb655f76712731b5f1034250520ddf90008099863ce70ff'
4
- data.tar.gz: 38bfd628af67308dc59ec42eb3c7e89e6f2391c9bc4d84fd27fd24dfba89a4cd
3
+ metadata.gz: e75dd885fd2214ee3d6cbf5424406f26ba390a51a5bea08999998908e5bf24da
4
+ data.tar.gz: 9347d365deecd63c0ef5a48877c568db1e246d27d8cdfb031c693fc4c017ece2
5
5
  SHA512:
6
- metadata.gz: c7f10fc55865bda5020d850470e103b1e8172e3b6fcf1be8a9fe7ecbac2b8617326cdbd3048bf241b5f5fe5a72a414d67f1f58bc2d6d06df4faa7d426ab49e86
7
- data.tar.gz: ba30bcf0dabd7a66e68038d0314379663e1a6058f2b433947d961588cd593b3c318f3eec81ef7691d007029efbafa64d3cc2ba7fe8d92b56f9d51b31a7cf8d7b
6
+ metadata.gz: 70170301c78e81a6756f2f2d09d63c9cbf79d5c24dcbd44c732b732b667172dcc74402f038142d9ae14024c9c944ccab467988d37014ccd0ec6837b2b93a4844
7
+ data.tar.gz: 68bcd97903344fba28cfa378b0feb96663997056c01a467a0c53399e124affd37462a4e2f58a8aa2377047ef61e411ffaf0cb8f63e1da6bd720623db14de0ab9
data/CHANGELOG.md CHANGED
@@ -1,44 +1,48 @@
1
1
  # Change Log
2
2
 
3
- ## VERSION 0.9.1 - xxxx-xx-xx
3
+ ## VERSION 0.7.0 - 2025-06-01
4
4
 
5
- - **Edge Generator**
6
- * add start-/end-nodes types to edge generator (would make scaffold easier), ie:
7
- `rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
8
- with property and specified start-/end-nodes (person and pet nodes must have already been created)
5
+ **Age Path** - nodes and edges combined
9
6
 
10
- - **Edge Scaffold** with node types?
11
- * add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
7
+ - query paths (control path, length/depth and filtering using `match`)
8
+ - code: `Path.cypher(path_edge: HasChild, path_length: "1..5", path_properties: {guardian_role: 'father'}, start_node_filter: {first_name: 'Zeke'}, end_node_filter: {last_name: 'Flintstone'})`
9
+ - code.to_sql: `SELECT * FROM cypher('age_schema', $$ MATCH path = (start_node {first_name: 'Zeke'})-[HasChild*1..5 {guardian_role: 'father'}]->(end_node {last_name: 'Flintstone'}) RETURN path $$) AS (path agtype);`
12
10
 
13
- ## VERSION 0.9.0 - 2024-xx-xx
14
- - **AGE visual paths graph**
15
- * add `rails generate apache_age:visualize`
11
+ **to_rich_h**
16
12
 
17
- ## VERSION 0.8.0 - 2024-xx-xx
13
+ - added to_rich_h method to nodes, edges and paths (displays additional context information for readability and represents data closer to the original age data)
18
14
 
19
- - **cypher queries** (like active record queries)
20
- * schema override
21
- * query support
22
- * paths support
23
- * select attributes support
15
+ **Generic Queries**
24
16
 
25
- ## VERSION 0.8.0 - 2024-xx-xx
17
+ - ApacheAge::Node and ApacheAge::Edge can be used as the base for a query and return results instantiating the correct class (node, edge or path)
26
18
 
27
- breaking change?: namespaces (by default) will use their own schema? (add to database.yml & schema.rb ?)
19
+ **Read Me** largely updated
28
20
 
29
- - **AGE Schema override**
21
+ **Query Values**
30
22
 
31
- - **Multiple AGE Schema**
23
+ - data casting within query code (so that matches are accurate)
24
+ * string
25
+ * integer
26
+ * decimal
27
+ * date
28
+ * datetime
29
+ * boolean
32
30
 
33
- ## VERSION 0.7.0 - 2024-xx-xx
31
+ Not implemented (on the rails side)
32
+ * array
33
+ * hash
34
+ * json
34
35
 
35
- - **Age Path** - nodes and edges combined
36
- * add `rails generate apache_age:path_scaffold HasJob employee_role start_node:person end_node:company`
37
36
 
38
37
  ## VERSION 0.6.4 - 2024-10-30
39
38
 
40
39
  - **Query Sanitize**:
41
40
  * allow and sanitize query strings with multiple attributes, ie: `Person.where("find.first_name = ? AND find.last_name = ?", 'John', 'Doe')`
41
+ NOTE: for now the following keyords MuST be in caps!
42
+ ```
43
+ operators = ['=', '>', '<', '<>', '>=', '<=', '=~', 'ENDS WITH', 'CONTAINS', 'STARTS WITH', 'IN', 'IS NULL', 'IS NOT NULL']
44
+ separators = ["AND NOT", "OR NOT", "AND", "OR", "NOT"]
45
+ ```
42
46
 
43
47
  ## VERSION 0.6.3 - 2024-10-27
44
48
 
data/README.md CHANGED
@@ -2,36 +2,387 @@
2
2
 
3
3
  Apache Age integration within a Rails application.
4
4
 
5
+ Inspired by: https://github.com/apache/age/issues/370
6
+
5
7
  ## Quick Start - Essentials
6
8
 
7
- **NOTE:** you must be using Postgres as your database! Apache Age requires it.
9
+ ### Overview
10
+
11
+ This Gem uses 3 Major Concepts:
12
+
13
+ 1. **Nodes** (Vertices) - usually nouns
14
+ 2. **Edges** (Relationships) - usually verbs (or relational adjectives)
15
+ 3. **Paths** (connections between nodes - heavy dependend on the `match` feature in cypher)
16
+
17
+ In this mini example we have the Flintstone Family Tree.
18
+ - Person (Node) - has attributes: first_name, last_name, gender
19
+ - Pet (Node) - has attributes: name, gender, species
20
+ - HasChild (Edge) - has attributes: guardian_role
21
+ - HasSibling (Edge) - has attributes: relation
22
+ - HasSpouse (Edge) - has attributes: spousal_role
23
+ - HasJob (Edge) - has attributes: employee_role
24
+
25
+ Paths are how you build the `network` of nodes and edges.
26
+
27
+
28
+ ### Generators - simplify usage and rails integration
29
+
30
+ we have an installers and generators (we handle namespacing fully)
31
+
32
+ RailsAge generators (and generally support standard Rails datatypes) - not all AGE datatypes are supported (yet)
33
+
34
+ This includes:
35
+ * string
36
+ * integer
37
+ * decimal
38
+ * date
39
+ * datetime
40
+ * boolean
41
+
42
+ AGE supports (but not RailsAge):
43
+ * array
44
+ * hash
45
+ * json
46
+
47
+ **Installers**
8
48
 
9
49
  ```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
- ```
50
+ rails generate apache_age:install
18
51
 
19
- ## Generators
52
+ # optional, but handy, it prevents `bin/rails db:migrate` from breaking the schema file,
53
+ rails generate apache_age:override_db_migrate
54
+ ```
20
55
 
21
56
  **NODES**
22
57
 
23
58
  ```bash
24
59
  rails generate apache_age:scaffold_node Company company_name
25
-
26
60
  rails generate apache_age:scaffold_node Person first_name last_name
27
61
  ```
28
62
 
29
63
  **EDGES**
30
64
 
31
65
  ```bash
66
+ rails generate apache_age:scaffold_edge HasChild guardian_role
32
67
  rails generate apache_age:scaffold_edge HasJob employee_role start_date:date
68
+ rails generate apache_age:scaffold_edge HasSibling start_relation
69
+ rails generate apache_age:scaffold_edge HasSpouse spousal_role
70
+ ```
71
+
72
+
73
+ ### INSTALL APACHE AGE
74
+
75
+ [Install Apache Age](https://age.apache.org/getstarted/quickstart)
76
+
77
+ **Quick Docker Install**
78
+
79
+ ```bash
80
+ # pull the docker image
81
+ docker pull apache/age
82
+
83
+ # Create AGE docker container
84
+ docker run \
85
+ --name age \
86
+ -p 5455:5432 \
87
+ -e POSTGRES_USER=postgresUser \
88
+ -e POSTGRES_PASSWORD=postgresPW \
89
+ -e POSTGRES_DB=postgresDB \
90
+ -d \
91
+ apache/age
92
+
93
+ # enter the container and connect to the database (and test it)
94
+ docker exec -it age psql -d postgresDB -U postgresUser
95
+
96
+ # For every connection of AGE you start, you will need to load the AGE extension.
97
+ CREATE EXTENSION age;
98
+ LOAD 'age';
99
+ SET search_path = ag_catalog, "$user", public;
100
+ ```
101
+
102
+
103
+ ### RAILS PROJECT (Flintstone Family)
104
+
105
+ **NOTE:** you must be using Postgres as your database!
106
+
107
+ Apache Age requires using Postgres (`-d postgresql`)!
108
+
109
+ ```bash
110
+ # SETUP A RAILS PROJECT (Flintstone Family Tree)
111
+ rails new stone_age -T -d postgresql
112
+ cd stone_age
113
+ git commit -m "initail commit"
114
+
115
+ # if using the default dockerized AGE PostgreSQL server then add the following to your `config/database.yml`
116
+ # BE SURE TO MATCH THE USERNAME AND PASSWORD TO WHAT YOU SET IN THE DOCKER RUN COMMAND!
117
+ host: localhost
118
+ port: 5455
119
+ username: postgresUser
120
+ password: postgresPW
121
+
122
+ # create the database
123
+ rails db:create
124
+
125
+ # add the rails_age gem to your Gemfile
126
+ bundle add rails_age
127
+
128
+ # download the rails_age gem
129
+ bundle install
130
+
131
+ # configure the Apache AGE gem (ignore the OID warnings)
132
+ bin/rails apache_age:install
133
+ # if you get the error: `PG::FeatureNotSupported: ERROR: extension "age" is not available`
134
+ # then you have not installed the AGE code (in PostgreSQL) follow this instrcutions at:
135
+ # https://github.com/apache/age?tab=readme-ov-file#installation
136
+
137
+ # optional: prevents `bin/rails db:migrate` from modifying the schema file,
138
+ bin/rails apache_age:override_db_migrate
139
+ bin/rails db:migrate
140
+
141
+ # create nodes
142
+ bin/rails generate apache_age:scaffold_node Company company_name industry
143
+ bin/rails generate apache_age:scaffold_node Person first_name last_name gender
144
+ bin/rails generate apache_age:scaffold_node Pet name gender species
145
+ # adjust the validations in the nodes files
146
+
147
+ # create edges (relationships)
148
+ bin/rails generate apache_age:scaffold_edge HasChild guardian_role:string
149
+ bin/rails generate apache_age:scaffold_edge HasSibling relation:string
150
+ bin/rails generate apache_age:scaffold_edge HasSpouse spousal_role:string
151
+ bin/rails generate apache_age:scaffold_edge HasJob employee_role:string
152
+
153
+ # adjust the edge validations and start_node and end_node types: ie: thefore
154
+ # `HasChild` edge should add the model type following start_node and end_node
155
+ # in this case: `:person`
156
+ # app/edges/has_child.rb
157
+ class HasChild
158
+ include ApacheAge::Entities::Edge
159
+
160
+ attribute :guardian_role, :string
161
+ attribute :start_node, :person
162
+ attribute :end_node, :person
163
+ end
164
+
165
+ # seed file uses `db/seed.rb` (seed provides NO pets)
166
+ bin/rails db:seed
167
+
168
+ # list noutes
169
+ bin/rails routes
170
+
171
+ # start server `bin/rails s`
172
+ # or more likely when using JS:
173
+ bin/start
174
+
175
+ # visit the routes in your browser - you should have a basic but working AGE app - that can do both AGE (Graph) and normal Rails activities
33
176
  ```
34
177
 
178
+
179
+
180
+ ## Queries
181
+
182
+ Mostly mimic **ActiveRecord** queries - if the class is an AGE based object (ie: a node or edge) then the queries are rewritten into AGE queries before being executed. Age queries also automatically unwrap the results turning them into the appropriate Ruby class (node or edge).
183
+
184
+ 1. `.all`
185
+ 2. `.first`
186
+ 2. `.save`
187
+ 2. `.update`
188
+ 2. `.destroy`
189
+ 2. `.destroy_all` (not implemented)
190
+ 2. `.update_attributes`
191
+ 2. `.find(id)`
192
+ 2. `.exists?` (not implemented)
193
+ 3. `.find_by(first_name: 'Zeke', last_name: 'Flintstone')`
194
+ 3. `.where(first_name: 'Zeke', last_name: 'Flintstone')`
195
+ 4. `.order(last_name: :desc, first_name: :asc)`
196
+ 5. `.limit(3)` (returns the first 3 records)
197
+ 6. `.to_sql` (returns the SQL query)
198
+ 7. `.cypher` (primarily for path queries - _builds a specialized cypher query for efficetly doing path queries - building efficient `match` statements_)
199
+
200
+ ### Node Queries
201
+
202
+ ```ruby
203
+ Dog.all
204
+ Dog.where(name: 'Pema')
205
+
206
+ Person.all
207
+ Person.where(first_name: 'Zeke', last_name: 'Flintstone')
208
+
209
+ ApacheAge::Node.all
210
+ # untested applying a where on nodes of different types, but should work
211
+ ApacheAge::Node.where(first_name: 'Zeke', last_name: 'Flintstone')
212
+ ```
213
+
214
+ ### Edge Queries
215
+
216
+ ```ruby
217
+ HasChild.all
218
+ HasChild.where(guardian_role: 'father')
219
+
220
+ HasJob.all
221
+ HasJob.where(employee_role: 'doctor')
222
+
223
+ ApacheAge::Edge.all
224
+ # untested applying a where on edges of different types, but should work
225
+ ApacheAge::Edge.where(employee_role: 'doctor')
226
+ ```
227
+
228
+ ### Path Queries
229
+
230
+ the connections between nodes and edges - this is important for advanced queries and relies heavily on the `match` feature in cypher (using match is faster for large datasets than using where to filter the data!)
231
+
232
+ A Path Query must have the `cypher` method called on the `Path` class.
233
+ ```ruby
234
+ Path.cypher(path_edge: HasChild, path_length: "1..5", path_properties: {guardian_role: 'father'}, start_node_filter: {first_name: 'Zeke'}, end_node_filter: {last_name: 'Flintstone'})
235
+ .order(start_node: :last_name)
236
+ .limit(3)
237
+ ```
238
+ **NOTE**: you can order on the start_node and end_node attributes, BUT NOT ON THE EDGE ATTRIBUTES (age limitation), even though you can filter on the edge attributes!
239
+
240
+ A path query returns an array of paths (each path is an array of nodes and edges)
241
+ ```ruby
242
+ [
243
+ [betty (node), bettys_son (edge), bamm_bamm (node)],
244
+ [betty (node), bettys_son (edge), bamm_bamm (node), bamm_bamms_son (edge), chip (node)]
245
+ ]
246
+ ```
247
+
248
+ To make a path more readable for debugging you can use the `to_rich_h` method
249
+
250
+ ```ruby
251
+ # hash metadata (rich hash) and is data is in properties (closer to the DB format)
252
+ path_query.all.first.to_rich_h
253
+ path_query.all.map(&:to_rich_h)
254
+ # default to_h works on paths too with (flat data hash of record):
255
+ path_query.all.first.map(&:to_h)
256
+ path_query.all.map { |p| p.map(&:to_h) }
257
+ ```
258
+
259
+ SQL Comparison with `match` and with `where` (in small datasets you won't see efficiency differences, but for large datasets `match` is much more efficient)
260
+ ```ruby
261
+ # EFFICIENT - the data is filtered in one step during the data traversal
262
+ Path.cypher(path_edge: HasChild, path_length: "1..5", path_properties: {guardian_role: 'father'}, start_node_filter: {first_name: 'Zeke'}, end_node_filter: {last_name: 'Flintstone'})
263
+ .order(start_node: {last_name: :asc}, length: :desc)
264
+ .limit(3).to_sql
265
+ # SELECT *
266
+ # FROM cypher('age_schema', $$
267
+ # MATCH path = (start_node {first_name: 'Zeke'})-[HasChild*1..5 {guardian_role: 'father'}]->(end_node {last_name: 'Flintstone'})
268
+ # RETURN path
269
+ # ORDER BY start_node.last_name ASC, length(path) DESC
270
+ # LIMIT 3
271
+ # $$) AS (path agtype);
272
+
273
+
274
+ # INEFFICIENT - because where is done after the match traversal (two steps through the data)
275
+ Path.cypher(path_edge: HasChild, path_length: "1..5", path_properties: {guardian_role: 'father'})
276
+ .where(start_node: {first_name: 'Zeke'})
277
+ .where('end_node.last_name =~ ?', 'Flintstone')
278
+ .order(start_node: {last_name: :asc}, length: :desc)
279
+ .limit(3).to_sql
280
+ # SELECT *
281
+ # FROM cypher('age_schema', $$
282
+ # MATCH path = (start_node)-[HasChild*1..5 {guardian_role: 'father'}]->(end_node)
283
+ # WHERE start_node.first_name = 'Zeke' AND end_node.last_name =~ 'Flintstone'
284
+ # RETURN path
285
+ # ORDER BY start_node.last_name ASC, length(path) DESC
286
+ # LIMIT 3
287
+ # $$) AS (path agtype);
288
+ ```
289
+
290
+ ## Console Usage
291
+
292
+ **NOTE**
293
+ using rails attributes complicates the default output of the model, thus it is strong recommended to use the `to_rich_h` method to display the results with meta_data so the class is known (or `to_h` - just the essential data).
294
+
295
+ ```ruby
296
+ dino = Pet.create(name: 'Dino', gender: 'male', species: 'dinosaur')
297
+ dino.to_rich_h
298
+
299
+ # find a person
300
+ fred = Person.find_by(first_name: 'Fred', last_name: 'Flintstone')
301
+ fred.to_rich_h
302
+
303
+ pebbles = Person.find_by(first_name: 'Pebbles')
304
+ pebbles.to_rich_h
305
+
306
+ # find an edge
307
+ father_relationship = HasChild.find_by(start_node: fred, end_node: pebbles)
308
+ father_relationship.to_h
309
+ > {:id=>1407374883553310,
310
+ :end_id=>844424930131996,
311
+ :start_id=>844424930131986,
312
+ :role=>"father",
313
+ :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"},
314
+ :start_node=>{:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}}
315
+
316
+ # where - find multiple nodes
317
+ family = Person.where(last_name: 'Flintstone').order(:first_name).limit(4).all.puts family.map(&:to_h)
318
+
319
+ family
320
+ > [{:id=>844424930131974, :last_name=>"Flintstone", :first_name=>"Ed", :gender=>"male"},
321
+ > {:id=>844424930131976, :last_name=>"Flintstone", :first_name=>"Edna", :gender=>"female"},
322
+ ? {:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"},
323
+ > {:id=>844424930131975, :last_name=>"Flintstone", :first_name=>"Giggles", :gender=>"male"}]
324
+
325
+ # all - unsorted
326
+ all_family = Person.where(last_name: 'Flintstone').all
327
+ puts all_family.map(&:to_h)
328
+ > {:id=>844424930131969, :last_name=>"Flintstone", :first_name=>"Zeke", :gender=>"female"}
329
+ > {:id=>844424930131970, :last_name=>"Flintstone", :first_name=>"Jed", :gender=>"male"}
330
+ > {:id=>844424930131971, :last_name=>"Flintstone", :first_name=>"Rockbottom", :gender=>"male"}
331
+ > {:id=>844424930131974, :last_name=>"Flintstone", :first_name=>"Ed", :gender=>"male"}
332
+ > {:id=>844424930131975, :last_name=>"Flintstone", :first_name=>"Giggles", :gender=>"male"}
333
+ > {:id=>844424930131976, :last_name=>"Flintstone", :first_name=>"Edna", :gender=>"female"}
334
+ > {:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}
335
+ > {:id=>844424930131987, :last_name=>"Flintstone", :first_name=>"Wilma", :gender=>"female"}
336
+ > {:id=>844424930131995, :last_name=>"Flintstone", :first_name=>"Stoney", :gender=>"male"}
337
+ > {:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}
338
+
339
+ # where - multiple edges (relations) - for now only edge attributes and start/end nodes can be queried
340
+ parental_relations = HasChild.where(end_node: pebbles)
341
+ puts parental_relations.map(&:to_h)
342
+ > {:id=>1407374883553310, :end_id=>844424930131996, :start_id=>844424930131986, :role=>"father", :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}, :start_node=>{:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}}
343
+ > {:id=>1407374883553309, :end_id=>844424930131996, :start_id=>844424930131987, :role=>"mother", :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}, :start_node=>{:id=>844424930131987, :last_name=>"Flintstone", :first_name=>"Wilma", :gender=>"female"}}
344
+
345
+ # Path Queries - returns all elements that match a given path to find specific sets of relationships
346
+ path = Path.cypher(path_edge: HasChild, path_length: "1..5", path_properties: {guardian_role: 'father'}, start_node_filter: {first_name: 'Zeke'}, end_node_filter: {last_name: 'Flintstone'})
347
+ .order(start_node: {last_name: :asc}, length: :desc)
348
+ .limit(3)
349
+
350
+ # should redo the example using: path.all.map(&:to_rich_h) to make more readable
351
+ path.all.map { |p| p.map(&:to_h) }
352
+ >[[{:id=>844424930131969, :first_name=>"Zeke", :last_name=>"Flintstone", :gender=>"female"},
353
+ {:id=>1407374883553281,
354
+ :end_id=>844424930131971,
355
+ :start_id=>844424930131969,
356
+ :guardian_role=>"mother",
357
+ :end_node=>{:id=>844424930131971, :first_name=>"Rockbottom", :last_name=>"Flintstone", :gender=>"male"},
358
+ :start_node=>{:id=>844424930131969, :first_name=>"Zeke", :last_name=>"Flintstone", :gender=>"female"}},
359
+ {:id=>844424930131971, :first_name=>"Rockbottom", :last_name=>"Flintstone", :gender=>"male"}],
360
+ [{:id=>844424930131969, :first_name=>"Zeke", :last_name=>"Flintstone", :gender=>"female"},
361
+ {:id=>1407374883553281,
362
+ :end_id=>844424930131971,
363
+ :start_id=>844424930131969,
364
+ :guardian_role=>"mother",
365
+ :end_node=>{:id=>844424930131971, :first_name=>"Rockbottom", :last_name=>"Flintstone", :gender=>"male"},
366
+ :start_node=>{:id=>844424930131969, :first_name=>"Zeke", :last_name=>"Flintstone", :gender=>"female"}},
367
+ {:id=>844424930131971, :first_name=>"Rockbottom", :last_name=>"Flintstone", :gender=>"male"},
368
+ {:id=>1407374883553284,
369
+ :end_id=>844424930131975,
370
+ :start_id=>844424930131971,
371
+ :guardian_role=>"father",
372
+ :end_node=>{:id=>844424930131975, :first_name=>"Giggles", :last_name=>"Flintstone", :gender=>"male"},
373
+ :start_node=>{:id=>844424930131971, :first_name=>"Rockbottom", :last_name=>"Flintstone", :gender=>"male"}},
374
+ {:id=>844424930131975, :first_name=>"Giggles", :last_name=>"Flintstone", :gender=>"male"}]]
375
+
376
+ raw_pg_results = Person.where(last_name: 'Flintstone').order(:first_name).limit(4).execute
377
+ => #<PG::Result:0x000000012255f348 status=PGRES_TUPLES_OK ntuples=4 nfields=1 cmd_tuples=4>
378
+ raw_pg_results.values
379
+ > [["{\"id\": 844424930131974, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Ed\"}}::vertex"],
380
+ > ["{\"id\": 844424930131976, \"label\": \"Person\", \"properties\": {\"gender\": \"female\", \"last_name\": \"Flintstone\", \"first_name\": \"Edna\"}}::vertex"],
381
+ > ["{\"id\": 844424930131986, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Fred\"}}::vertex"],
382
+ > ["{\"id\": 844424930131975, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Giggles\"}}::vertex"]]
383
+ ```
384
+
385
+
35
386
  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
387
 
37
388
  ie:
@@ -78,6 +429,56 @@ $ psql -h localhost -p 5455 -U docker_username
78
429
  > \q
79
430
  ```
80
431
 
432
+ ### Experiment directly with AGE
433
+
434
+ Play with AGE/cypher directly (if desired) - see: https://age.apache.org/getstarted/quickstart
435
+
436
+ To create a graph, use the create_graph function located in the ag_catalog namespace.
437
+
438
+ enter postgres via:
439
+ - `psql -h localhost -p 5455 -U docker_username`
440
+ or
441
+ - `docker exec -it age psql -d postgresDB -U postgresUser`
442
+
443
+ then in psql:
444
+ ```sql
445
+ -- For every connection of AGE you start, you will need to load the AGE extension.
446
+ CREATE EXTENSION age;
447
+ LOAD 'age';
448
+ SET search_path = ag_catalog, "$user", public;
449
+
450
+ SELECT create_graph('graph_name');
451
+
452
+ # To create a single vertex with label and properties, use the CREATE clause.
453
+ SELECT *
454
+ FROM cypher('graph_name', $$
455
+ CREATE (:label {property:"Node A"})
456
+ $$) as (v agtype);
457
+
458
+ # create a second vertex (node)
459
+ SELECT *
460
+ FROM cypher('graph_name', $$
461
+ CREATE (:label {property:"Node B"})
462
+ $$) as (v agtype);
463
+
464
+ # To create an edge between the nodes and set its properties:
465
+ SELECT *
466
+ FROM cypher('graph_name', $$
467
+ MATCH (a:label), (b:label)
468
+ WHERE a.property = 'Node A' AND b.property = 'Node B'
469
+ CREATE (a)-[e:RELTYPE {property:a.property + '<->' + b.property}]->(b)
470
+ RETURN e
471
+ $$) as (e agtype);
472
+
473
+ # Query the connected nodes:
474
+ SELECT * from cypher('graph_name', $$
475
+ MATCH (V)-[R]-(V2)
476
+ RETURN V,R,V2
477
+ $$) as (V agtype, R agtype, V2 agtype);
478
+
479
+ \q
480
+ ```
481
+
81
482
  ### Install and Configure Rails (if not done already)
82
483
 
83
484
  AGE REQUIRES POSTGRESQL!
@@ -269,87 +670,6 @@ The generator will only allow `:node` (default type) since at the time of runnin
269
670
  `rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role`
270
671
  but that doesn't work yet!
271
672
 
272
- ### AGE Rails Quick Example
273
-
274
- ```bash
275
- rails new stone_age --database=postresql
276
- cd stone_age
277
-
278
- bundle add rails_age
279
- bundle install
280
- bin/rails apache_age:install
281
- bin/rails apache_age:override_db_migrate
282
- rails db:create
283
- rails db:migrate
284
- rails generate apache_age:scaffold_node Person first_name, last_name, gender
285
- rails generate apache_age:scaffold_node Pet name gender species
286
- rails generate apache_age:scaffold_edge HasChild role:string
287
- rails generate apache_age:scaffold_edge HasSibling role:string
288
- rails generate apache_age:scaffold_edge HasSpouse role:string
289
-
290
- # seed file: [db/seed.rb](SEED.md)
291
- rails db:seed
292
-
293
- # Console Usage (seed doesn't provide any pets)
294
- dino = Pet.create(name: 'Dino', gender: 'male', species: 'dinosaur')
295
- dino.to_h
296
-
297
- # find a person
298
- fred = Person.find_by(first_name: 'Fred', last_name: 'Flintstone')
299
- fred.to_h
300
-
301
- pebbles = Person.find_by(first_name: 'Pebbles')
302
- pebbles.to_h
303
-
304
- # find an edge
305
- father_relationship = HasChild.find_by(start_node: fred, end_node: pebbles)
306
- father_relationship.to_h
307
- > {:id=>1407374883553310,
308
- :end_id=>844424930131996,
309
- :start_id=>844424930131986,
310
- :role=>"father",
311
- :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"},
312
- :start_node=>{:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}}
313
-
314
- # where - find multiple nodes
315
- family = Person.where(last_name: 'Flintstone').order(:first_name).limit(4).all.puts family.map(&:to_h)
316
-
317
- family
318
- > [{:id=>844424930131974, :last_name=>"Flintstone", :first_name=>"Ed", :gender=>"male"},
319
- > {:id=>844424930131976, :last_name=>"Flintstone", :first_name=>"Edna", :gender=>"female"},
320
- ? {:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"},
321
- > {:id=>844424930131975, :last_name=>"Flintstone", :first_name=>"Giggles", :gender=>"male"}]
322
-
323
- # all - unsorted
324
- all_family = Person.where(last_name: 'Flintstone').all
325
- puts all_family.map(&:to_h)
326
- > {:id=>844424930131969, :last_name=>"Flintstone", :first_name=>"Zeke", :gender=>"female"}
327
- > {:id=>844424930131970, :last_name=>"Flintstone", :first_name=>"Jed", :gender=>"male"}
328
- > {:id=>844424930131971, :last_name=>"Flintstone", :first_name=>"Rockbottom", :gender=>"male"}
329
- > {:id=>844424930131974, :last_name=>"Flintstone", :first_name=>"Ed", :gender=>"male"}
330
- > {:id=>844424930131975, :last_name=>"Flintstone", :first_name=>"Giggles", :gender=>"male"}
331
- > {:id=>844424930131976, :last_name=>"Flintstone", :first_name=>"Edna", :gender=>"female"}
332
- > {:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}
333
- > {:id=>844424930131987, :last_name=>"Flintstone", :first_name=>"Wilma", :gender=>"female"}
334
- > {:id=>844424930131995, :last_name=>"Flintstone", :first_name=>"Stoney", :gender=>"male"}
335
- > {:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}
336
-
337
- # where - multiple edges (relations) - for now only edge attributes and start/end nodes can be queried
338
- parental_relations = HasChild.where(end_node: pebbles)
339
- puts parental_relations.map(&:to_h)
340
- > {:id=>1407374883553310, :end_id=>844424930131996, :start_id=>844424930131986, :role=>"father", :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}, :start_node=>{:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"}}
341
- > {:id=>1407374883553309, :end_id=>844424930131996, :start_id=>844424930131987, :role=>"mother", :end_node=>{:id=>844424930131996, :last_name=>"Flintstone", :first_name=>"Pebbles", :gender=>"female"}, :start_node=>{:id=>844424930131987, :last_name=>"Flintstone", :first_name=>"Wilma", :gender=>"female"}}
342
-
343
-
344
- raw_pg_results = Person.where(last_name: 'Flintstone').order(:first_name).limit(4).execute
345
- => #<PG::Result:0x000000012255f348 status=PGRES_TUPLES_OK ntuples=4 nfields=1 cmd_tuples=4>
346
- raw_pg_results.values
347
- > [["{\"id\": 844424930131974, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Ed\"}}::vertex"],
348
- > ["{\"id\": 844424930131976, \"label\": \"Person\", \"properties\": {\"gender\": \"female\", \"last_name\": \"Flintstone\", \"first_name\": \"Edna\"}}::vertex"],
349
- > ["{\"id\": 844424930131986, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Fred\"}}::vertex"],
350
- > ["{\"id\": 844424930131975, \"label\": \"Person\", \"properties\": {\"gender\": \"male\", \"last_name\": \"Flintstone\", \"first_name\": \"Giggles\"}}::vertex"]]
351
- ```
352
-
353
673
  ### Age Cypher Queries
354
674
 
355
675
  ```ruby