rails_age 0.6.0 → 0.6.2

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: 6777870b945c8684ea2ef5d10b8e7411259e002001b074754840a9422b594cee
4
- data.tar.gz: '008ab62bbe28d408d390acd5e00cecc764507e21ae3d38ca94d219e9d299435f'
3
+ metadata.gz: '01018a3efe8d4cef46fce365b6fd442882d8bf4ff51e0ad3d3bdb235db80ebb7'
4
+ data.tar.gz: 69a20eb39f9609c6350463d6a391b7f08887079e51e56602a6357d914571ed8f
5
5
  SHA512:
6
- metadata.gz: 8a9c122ce1a8b5b173fc9ffd6ba45a4bf97965eb0681ec0d44e9779dcc1ef647da8e7e5110a5564a9f28c329ebe9e83ba832d920f4504bb5026650f41cc102d2
7
- data.tar.gz: f9217b16e0177746a438ab5fed8a4e5c7f4bb6ff02b3e31a2a4d1db7a8bd246550e33be6494e0533c70dcbcde3a54078ed041b2c3a08718cb9b020c73353eee5
6
+ metadata.gz: cc2ea56dcb102213397ca5ffc19edc095341e51307b0500d0be7bc7595cb65f64e74c6490a678703f5faf903d0396010b63b8dd33e162ed218e038a4e95e05f5
7
+ data.tar.gz: 30e0472fb006bf883cb57a5c7406571d4fc4715a9d6d96e792dd5d3b4998e538eab74f809b5d384072d88e77ce2d6fb143cdb6a5c20fd044fd74673edd2bb13a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Change Log
2
2
 
3
- ## VERSION 0.6.4 - 2024-xx-xx
3
+ ## VERSION 0.9.1 - xxxx-xx-xx
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)
9
+
10
+ - **Edge Scaffold** with node types?
11
+ * add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
12
+
13
+ ## VERSION 0.9.0 - 2024-xx-xx
14
+ - **AGE visual paths graph**
15
+ * add `rails generate apache_age:visualize`
16
+
17
+ ## VERSION 0.8.0 - 2024-xx-xx
4
18
 
5
19
  - **cypher queries** (like active record queries)
6
20
  * schema override
@@ -8,37 +22,51 @@
8
22
  * paths support
9
23
  * select attributes support
10
24
 
11
- ## VERSION 0.6.3 - 2024-xx-xx
12
-
13
- - **Age Path**
14
-
15
- ## VERSION 0.6.2 - 2024-xx-xx
25
+ ## VERSION 0.8.0 - 2024-xx-xx
16
26
 
17
27
  breaking change?: namespaces (by default) will use their own schema? (add to database.yml & schema.rb ?)
18
28
 
19
29
  - **AGE Schema override**
20
30
 
21
- - **multiple AGE Schema**
31
+ - **Multiple AGE Schema**
22
32
 
23
- ## VERSION 0.6.1 - 2024-xx-xx
33
+ ## VERSION 0.7.0 - 2024-xx-xx
24
34
 
25
- - **Fix**
26
- * show validation errors in scaffold views
35
+ - **Age Path** - nodes and edges combined
36
+ * add `rails generate apache_age:path_scaffold HasJob employee_role start_node:person end_node:company`
27
37
 
28
- - **Edge Generator**
29
- * add start-/end-nodes types to edge generator (would make scaffold easier), ie:
30
- `rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
31
- with property and specified start-/end-nodes (person and pet nodes must have already been created)
32
38
 
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`
39
+ ## VERSION 0.6.3 - 2024-xx-xx
40
+
41
+ - **Query Sanitize**:
42
+ * reject attributes not defined in model
43
+ * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
44
+
45
+ ## VERSION 0.6.2 - 2024-09-30
46
+
47
+ - **Query Sanitize**
48
+ * hashes sanitized
49
+
50
+ - **TODO**:
51
+ * reject attributes not defined in model
52
+ * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
53
+
54
+ ## VERSION 0.6.1 - 2024-09-29
55
+
56
+ **Queries are not yet sanitize (injection filtered)!**
57
+
58
+ - **where nodes** - Edge and Node
59
+ - **where edges** - allow subquery on node attributes?
60
+ - **limit** - limit the number of results returned
61
+
62
+ ## VERSION 0.6.0 - 2024-06-28
35
63
 
36
- ## VERSION 0.6.0 - 2024-06-xx
64
+ **Document showing errors**
37
65
 
38
66
  **breaking changes**: update naming
39
67
  * renamed `Entities::Vertex` module to `Entities::Node`
40
68
  * renamed `UniqueVertex` to `UniqueNode`
41
- * rebamed `AgeTypeGenerator` to `Type::Factory`
69
+ * rebamed `AgeTypeGenerator.create_type_for` to `Type::Factory.type_for`
42
70
  * move `lib/generators/*` intp `lib/apache_age/generators`
43
71
 
44
72
  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
data/README.md CHANGED
@@ -147,24 +147,260 @@ rails generate apache_age:scaffold_node Person first_name last_name
147
147
  rails generate apache_age:scaffold_node Animals/Pet pet_name birthdate:date
148
148
  ```
149
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
-
150
+ now you can test your nodes at:
154
151
  ```bash
155
- rails generate apache_age:edge HasJob employee_role begin_date:date
152
+ http://localhost/people
153
+ # and
154
+ http://localhost/animals_pets
156
155
  ```
157
156
 
158
- _edge scaffold is coming soon._
157
+ Note: Turbo seems to interfere with the default rails template's ability to show errors, this can easily be fixed by disabling turbo for forms that where turbo isn't needed by adding `data: { turbo: false }` to the form, ie:
158
+ ```ruby
159
+ <%= form_with(model: animals_pet, data: { turbo: false }) do |form| %>
160
+ ...
161
+ <% end %>
162
+ ```
163
+
164
+ ### EDGE Scaffold Generation**
159
165
 
160
166
  ```bash
161
167
  # without a namespace
162
168
  rails generate apache_age:scaffold_edge HasPet caretaker_role
169
+ rails generate apache_age:edge HasJob employee_role begin_date:date
163
170
 
164
171
  # with a namespace
165
- rails generate apache_age:scaffold_edge People/HasSpouse spousal_role
172
+ rails generate apache_age:scaffold_edge People/HasSpouce spousal_role
166
173
  ```
167
174
 
175
+ now you can test your edges at:
176
+ ```bash
177
+ http://localhost/has_pets
178
+ http://localhost/has_jobs
179
+ # and
180
+ http://localhost/people/has_spouses
181
+ ```
182
+
183
+ you can improve the view to only show the items you expect to be associated with the start- and end-node by changing the selects in the form from the generic form (finds all nodes):
184
+ ```ruby
185
+ <div>
186
+ <%= form.label :end_node, style: "display: block" %>
187
+ <%= form.collection_select(:end_id, ApacheAge::Node.all, :id, :display, prompt: 'Select an End-Node') %>
188
+ </div>
189
+ ```
190
+ to selecting a specific node expected (along with the desired 'name' in the list)
191
+ ```ruby
192
+ <div>
193
+ <%= form.label :end_node, style: "display: block" %>
194
+ <%= form.collection_select(:end_id, Company.all, :id, :company_name, prompt: 'Select a Company') %>
195
+ </div>
196
+ ```
197
+ so full form change for has_job could look like:
198
+ ```ruby
199
+ # app/views/has_jobs/_form.html.erb
200
+ <%= form_with(model: has_job, url: form_url) do |form| %>
201
+ <% if has_job.errors.any? %>
202
+ <div style="color: red">
203
+ <h2><%= pluralize(has_job.errors.count, "error") %> prohibited this has_job from being saved:</h2>
204
+
205
+ <ul>
206
+ <% has_job.errors.each do |error| %>
207
+ <li><%= error.full_message %></li>
208
+ <% end %>
209
+ </ul>
210
+ </div>
211
+ <% end %>
212
+
213
+ <div>
214
+ <%= form.label :employee_role, style: "display: block" %>
215
+ <%= form.text_field :employee_role %>
216
+ </div>
217
+
218
+ <div>
219
+ <%= form.label :start_node, style: "display: block" %>
220
+ <%= form.collection_select(:start_id, Person.all, :id, :first_name, prompt: 'Select a person') %>
221
+ </div>
222
+
223
+ <div>
224
+ <%= form.label :end_node, style: "display: block" %>
225
+ <%= form.collection_select(:end_id, Company.all, :id, :company_name, prompt: 'Select a Company') %>
226
+ </div>
227
+
228
+ <div>
229
+ <%= form.submit %>
230
+ </div>
231
+ <% end %>
232
+ ```
233
+
234
+ To make your code more robust (enforce that the appropriate node type is associate with the start- and end-nodes) you can adjust the edge definition by adding the node type to the `start_node` and `end_node` attributes.
235
+ from
236
+ ```ruby
237
+ attribute :start_node
238
+ attribute :end_node
239
+ ```
240
+ to:
241
+ ```ruby
242
+ attribute :start_node, :person
243
+ attribute :end_node, :company
244
+ ```
245
+ For example you can make the edge/has_pet.rb more robust by making the model look like:
246
+ ```ruby
247
+ # app/edges/has_job.rb
248
+ class HasJob
249
+ include ApacheAge::Entities::Edge
250
+
251
+ attribute :employee_role, :string
252
+ attribute :start_node, :person
253
+ attribute :end_node, :company
254
+
255
+ validates :employee_role, presence: true
256
+ validate :validate_unique
257
+
258
+ private
259
+
260
+ def validate_unique
261
+ ApacheAge::Validators::UniqueEdge
262
+ .new(attributes: %i[employee_role start_node end_node])
263
+ .validate(self)
264
+ end
265
+ end
266
+ ```
267
+
268
+ The generator will only allow `:node` (default type) since at the time of running the generator (at least within tests, the custom types are not known), eventually, I hope to find a way to fix that and allow:
269
+ `rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role`
270
+ but that doesn't work yet!
271
+
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
+ ### Age Cypher Queries
354
+
355
+ ```ruby
356
+ flintstone_family =
357
+ Person.where(last_name: 'Flintstone')
358
+ .order(:first_name)
359
+ .limit(4).all
360
+ .map(&:to_h)
361
+
362
+ # generates the query
363
+ SELECT *
364
+ FROM cypher('age_schema', $$
365
+ MATCH (find:Person)
366
+ WHERE find.last_name = 'Flintstone'
367
+ RETURN find
368
+ ORDER BY find.first_name
369
+ LIMIT 4
370
+ $$) as (Person agtype);
371
+
372
+ # and returns:
373
+ [{:id=>844424930131974, :last_name=>"Flintstone", :first_name=>"Ed", :gender=>"male"},
374
+ {:id=>844424930131976, :last_name=>"Flintstone", :first_name=>"Edna", :gender=>"female"},
375
+ {:id=>844424930131986, :last_name=>"Flintstone", :first_name=>"Fred", :gender=>"male"},
376
+ {:id=>844424930131975, :last_name=>"Flintstone", :first_name=>"Giggles", :gender=>"male"}]
377
+ ```
378
+
379
+ ```ruby
380
+ query =
381
+ Person.
382
+ cypher('age_schema')
383
+ .match("(a:Person), (b:Person)")
384
+ .where("a.name = 'Node A'", "b.name = 'Node B'")
385
+ .return("a.name", "b.name")
386
+ .as("name_a agtype, name_b agtype")
387
+ .execute
388
+ ```
389
+
390
+ or more generally (soon - not yet tested nor santized):
391
+
392
+ ```ruby
393
+ tihen =
394
+ ApacheAge::Cypher
395
+ .new('age_schema')
396
+ .create("(person:Person {name: 'Tihen'})")
397
+ .return('person')
398
+ .as('Person agtype')
399
+ .execute
400
+ ```
401
+
402
+ see [AGE Cypher Queries](AGE_CYPHER_QUERIES.md)
403
+
168
404
  ### AGE Usage within Rails Console
169
405
 
170
406
  see [AGE Usage within Rails Console](AGE_CONSOLE_USAGE.md)
@@ -0,0 +1,95 @@
1
+ module ApacheAge
2
+ class Cypher
3
+ class << self
4
+ attr_accessor :model_class
5
+ end
6
+
7
+ def initialize(graph_name = 'age_schema')
8
+ @graph_name = graph_name
9
+ @query = ""
10
+ @as_type = "result agtype"
11
+ end
12
+
13
+ def match(pattern)
14
+ @query += "MATCH #{pattern} "
15
+ self
16
+ end
17
+
18
+ # WITH n.name as name, n.age as age
19
+ # WITH otherPerson, count(*) AS foaf WHERE foaf > 1
20
+ # with has a lot of cases - see docs
21
+ def with(*conditions)
22
+ @query += "WITH #{variables.join(', ')} "
23
+ self
24
+ end
25
+
26
+ def where(*conditions)
27
+ condition_str = conditions.join(' AND ')
28
+ # @query += "WHERE #{condition_str} "
29
+ # If there's already a WHERE clause in the query, append to it (they need to be adjacent!)
30
+ @query += (@query.include?("WHERE") ? " AND #{condition_str} " : "WHERE #{condition_str} ")
31
+ self
32
+ end
33
+
34
+ # ORDER BY n.age DESC, n.name ASC
35
+ def order_by(*conditions)
36
+ @query += "ORDER BY #{variables.join(', ')} "
37
+ self
38
+ end
39
+
40
+ # can use full names n.name or aliases (with) name
41
+ def return(*variables)
42
+ @query += "RETURN #{variables.join(', ')} "
43
+ self
44
+ end
45
+
46
+ def create(node)
47
+ @query += "CREATE #{node} "
48
+ self
49
+ end
50
+
51
+ def set(properties)
52
+ @query += "SET #{properties} "
53
+ self
54
+ end
55
+
56
+ def remove(property)
57
+ @query += "REMOVE #{property} "
58
+ self
59
+ end
60
+
61
+ def delete(entity)
62
+ @query += "DELETE #{entity} "
63
+ self
64
+ end
65
+
66
+ def merge(pattern)
67
+ @query += "MERGE #{pattern} "
68
+ self
69
+ end
70
+
71
+ def skip(count)
72
+ @query += "SKIP #{count} "
73
+ self
74
+ end
75
+
76
+ def limit(count)
77
+ @query += "LIMIT #{count} "
78
+ self
79
+ end
80
+
81
+ def as(type)
82
+ @as_type = type
83
+ self
84
+ end
85
+
86
+ def to_cypher
87
+ "SELECT * FROM cypher('#{@graph_name}', $$ #{@query.strip} $$) AS (#{@as_type});"
88
+ end
89
+
90
+ def execute
91
+ cypher_sql = to_cypher
92
+ ActiveRecord::Base.connection.execute(cypher_sql)
93
+ end
94
+ end
95
+ end
@@ -8,58 +8,44 @@ module ApacheAge
8
8
  instance
9
9
  end
10
10
 
11
- def find_by(attributes)
12
- return nil if attributes.reject{ |k,v| v.blank? }.empty?
13
-
14
- edge_keys = [:start_id, :start_node, :end_id, :end_node]
15
- return find_edge(attributes) if edge_keys.any? { |key| attributes.include?(key) }
16
-
17
- where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
18
- cypher_sql = find_sql(where_clause)
19
-
20
- execute_find(cypher_sql)
11
+ def where(attributes)
12
+ query_builder = QueryBuilder.new(self)
13
+ query_builder.where(attributes)
21
14
  end
22
15
 
23
- def find(id)
24
- where_clause = "id(find) = #{id}"
25
- cypher_sql = find_sql(where_clause)
26
- execute_find(cypher_sql)
27
- end
28
-
29
- def all
30
- age_results = ActiveRecord::Base.connection.execute(all_sql)
31
- return [] if age_results.values.count.zero?
16
+ def all = QueryBuilder.new(self).all
17
+ def first = QueryBuilder.new(self).limit(1).first
18
+ def find(id) = where(id: id).first
32
19
 
33
- age_results.values.map do |result|
34
- json_string = result.first.split('::').first
35
- hash = JSON.parse(json_string)
36
- attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
20
+ def find_by(attributes)
21
+ return nil if attributes.reject { |k, v| v.blank? }.empty?
37
22
 
38
- new(**attribs)
39
- end
23
+ where(attributes).limit(1).first
40
24
  end
41
25
 
42
26
  # Private stuff
43
27
 
44
- def find_edge(attributes)
28
+ # used? or dead code?
29
+ def where_edge(attributes)
45
30
  where_attribs =
46
31
  attributes
47
- .compact
48
- .except(:end_id, :start_id, :end_node, :start_node)
49
- .map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
32
+ .compact
33
+ .except(:end_id, :start_id, :end_node, :start_node)
34
+ .map { |k, v| ActiveRecord::Base.sanitize_sql(["find.#{k} = ?", v]) }
35
+ .join(' AND ')
50
36
  where_attribs = where_attribs.empty? ? nil : where_attribs
51
37
 
52
38
  end_id = attributes[:end_id] || attributes[:end_node]&.id
53
39
  start_id = attributes[:start_id] || attributes[:start_node]&.id
54
- where_end_id = end_id ? "id(end_node) = #{end_id}" : nil
55
- where_start_id = start_id ? "id(start_node) = #{start_id}" : nil
40
+ where_end_id = end_id ? ActiveRecord::Base.sanitize_sql(["id(end_node) = ?", end_id]) : nil
41
+ where_start_id = start_id ? ActiveRecord::Base.sanitize_sql(["id(start_node) = ?", start_id]) : nil
56
42
 
57
43
  where_clause = [where_attribs, where_start_id, where_end_id].compact.join(' AND ')
58
44
  return nil if where_clause.empty?
59
45
 
60
- cypher_sql = find_edge_sql(where_clause)
46
+ cypher_sql = edge_sql(where_clause)
61
47
 
62
- execute_find(cypher_sql)
48
+ execute_where(cypher_sql)
63
49
  end
64
50
 
65
51
  def age_graph = 'age_schema'
@@ -70,17 +56,21 @@ module ApacheAge
70
56
  age_type == 'vertex' ? "(find:#{age_label})" : "(start_node)-[find:#{age_label}]->(end_node)"
71
57
  end
72
58
 
73
- def execute_find(cypher_sql)
74
- age_result = ActiveRecord::Base.connection.execute(cypher_sql)
75
- return nil if age_result.values.count.zero?
59
+ def execute_sql(cypher_sql) = ActiveRecord::Base.connection.execute(cypher_sql)
60
+
61
+ def execute_find(cypher_sql) = execute_where(cypher_sql).first
76
62
 
77
- age_type = age_result.values.first.first.split('::').last
78
- json_data = age_result.values.first.first.split('::').first
63
+ def execute_where(cypher_sql)
64
+ age_results = ActiveRecord::Base.connection.execute(cypher_sql)
65
+ return [] if age_results.values.count.zero?
79
66
 
80
- hash = JSON.parse(json_data)
81
- attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
67
+ age_results.values.map do |value|
68
+ json_data = value.first.split('::').first
69
+ hash = JSON.parse(json_data)
70
+ attribs = hash.except('label', 'properties').merge(hash['properties']).symbolize_keys
82
71
 
83
- new(**attribs)
72
+ new(**attribs)
73
+ end
84
74
  end
85
75
 
86
76
  def all_sql
@@ -93,7 +83,7 @@ module ApacheAge
93
83
  SQL
94
84
  end
95
85
 
96
- def find_sql(where_clause)
86
+ def node_sql(where_clause)
97
87
  <<-SQL
98
88
  SELECT *
99
89
  FROM cypher('#{age_graph}', $$
@@ -104,7 +94,7 @@ module ApacheAge
104
94
  SQL
105
95
  end
106
96
 
107
- def find_edge_sql(where_clause)
97
+ def edge_sql(where_clause)
108
98
  <<-SQL
109
99
  SELECT *
110
100
  FROM cypher('#{age_graph}', $$
@@ -114,6 +104,56 @@ module ApacheAge
114
104
  $$) as (#{age_label} agtype);
115
105
  SQL
116
106
  end
107
+
108
+ private
109
+
110
+ def where_node_clause(attributes)
111
+ build_core_where_clause(attributes)
112
+ end
113
+
114
+ def where_edge_clause(attributes)
115
+ core_attributes = attributes.except(:end_id, :start_id, :end_node, :start_node)
116
+ core_clauses = core_attributes.empty? ? nil : build_core_where_clause(core_attributes)
117
+
118
+ end_id =
119
+ if attributes[:end_id]
120
+ attributes[:end_id]
121
+ elsif attributes[:end_node].is_a?(Node)
122
+ attributes[:end_node]&.id
123
+ end
124
+ where_end_id = end_id ? ActiveRecord::Base.sanitize_sql(["id(end_node) = ?", end_id]) : nil
125
+
126
+ start_id =
127
+ if attributes[:start_id]
128
+ attributes[:start_id]
129
+ elsif attributes[:start_node].is_a?(Node)
130
+ attributes[:start_node]&.id
131
+ end
132
+ where_start_id = start_id ? ActiveRecord::Base.sanitize_sql(["id(start_node) = ?", start_id]) : nil
133
+
134
+ where_end_attrs =
135
+ if attributes[:end_node].is_a?(Hash)
136
+ attributes[:end_node].map { |k, v| ActiveRecord::Base.sanitize_sql(["end_node.#{k} = ?", v]) }
137
+ end
138
+ where_start_attrs =
139
+ if attributes[:start_node].is_a?(Hash)
140
+ attributes[:start_node].map { |k, v| ActiveRecord::Base.sanitize_sql(["start_node.#{k} = ?", v]) }
141
+ end
142
+
143
+ [core_clauses, where_start_id, where_end_id, where_start_attrs, where_end_attrs]
144
+ .flatten.compact.join(' AND ')
145
+ end
146
+
147
+
148
+ def build_core_where_clause(attributes)
149
+ attributes
150
+ .compact
151
+ .map do |k, v|
152
+ query_string = k == :id ? "id(find) = #{v}" : "find.#{k} = '#{v}'"
153
+ ActiveRecord::Base.sanitize_sql([query_string, v])
154
+ end
155
+ .join(' AND ')
156
+ end
117
157
  end
118
158
  end
119
159
  end
@@ -57,13 +57,14 @@ module ApacheAge
57
57
  def destroy
58
58
  match_clause = (age_type == 'vertex' ? "(done:#{age_label})" : "()-[done:#{age_label}]->()")
59
59
  delete_clause = (age_type == 'vertex' ? 'DETACH DELETE done' : 'DELETE done')
60
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["id(done) = ?", id])
60
61
  cypher_sql =
61
62
  <<-SQL
62
63
  SELECT *
63
64
  FROM cypher('#{age_graph}', $$
64
65
  MATCH #{match_clause}
65
- WHERE id(done) = #{id}
66
- #{delete_clause}
66
+ WHERE #{sanitized_id}
67
+ #{delete_clause}
67
68
  return done
68
69
  $$) as (deleted agtype);
69
70
  SQL
@@ -99,7 +100,8 @@ module ApacheAge
99
100
  def properties_to_s
100
101
  string_values =
101
102
  age_properties.each_with_object([]) do |(key, val), array|
102
- array << "#{key}: '#{val}'"
103
+ sanitized_val = ActiveRecord::Base.sanitize_sql(["?", val])
104
+ array << "#{key}: #{sanitized_val}"
103
105
  end
104
106
  "{#{string_values.join(', ')}}"
105
107
  end
@@ -7,6 +7,8 @@ module ApacheAge
7
7
  include ActiveModel::Model
8
8
  include ActiveModel::Dirty
9
9
  include ActiveModel::Attributes
10
+ include ActiveModel::Validations
11
+ include ActiveModel::Validations::Callbacks
10
12
 
11
13
  attribute :id, :integer
12
14
  # attribute :label, :string
@@ -68,12 +70,25 @@ module ApacheAge
68
70
  def create_sql
69
71
  self.start_node = start_node.save unless start_node.persisted?
70
72
  self.end_node = end_node.save unless end_node.persisted?
73
+
74
+ start_node_age_label = ActiveRecord::Base.sanitize_sql(start_node.age_label)
75
+ end_node_age_label = ActiveRecord::Base.sanitize_sql(end_node.age_label)
76
+ sanitized_start_id = ActiveRecord::Base.sanitize_sql(["?", start_node.id])
77
+ sanitized_end_id = ActiveRecord::Base.sanitize_sql(["?", end_node.id])
78
+ # cant use sanitize_sql_like because it escapes the % and _ characters
79
+ # label_name = ActiveRecord::Base.sanitize_sql_like(age_label)
80
+
81
+ reject_keys = %i[id start_id end_id start_node end_node]
82
+ sanitized_properties =
83
+ self.to_h.reject { |k, _v| reject_keys.include?(k) }.reject { |_k, v| v.nil? }
84
+ .map { |k, v| "#{k}: #{ActiveRecord::Base.sanitize_sql(["?", v])}" }
85
+ .join(', ')
71
86
  <<-SQL
72
87
  SELECT *
73
88
  FROM cypher('#{age_graph}', $$
74
- MATCH (from_node:#{start_node.age_label}), (to_node:#{end_node.age_label})
75
- WHERE id(from_node) = #{start_node.id} and id(to_node) = #{end_node.id}
76
- CREATE (from_node)-[edge#{self}]->(to_node)
89
+ MATCH (from_node:#{start_node_age_label}), (to_node:#{end_node_age_label})
90
+ WHERE id(from_node) = #{sanitized_start_id} AND id(to_node) = #{sanitized_end_id}
91
+ CREATE (from_node)-[edge:#{age_label} {#{sanitized_properties}}]->(to_node)
77
92
  RETURN edge
78
93
  $$) as (edge agtype);
79
94
  SQL
@@ -82,14 +97,24 @@ module ApacheAge
82
97
  # So far just properties of string type with '' around them
83
98
  def update_sql
84
99
  alias_name = age_alias || age_label.downcase
85
- set_caluse =
86
- age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
100
+ set_clause =
101
+ age_properties.map do |k, v|
102
+ if v
103
+ sanitized_value = ActiveRecord::Base.sanitize_sql(["?", v])
104
+ "#{alias_name}.#{k} = #{sanitized_value}"
105
+ else
106
+ "#{alias_name}.#{k} = NULL"
107
+ end
108
+ end.join(', ')
109
+
110
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["?", id])
111
+
87
112
  <<-SQL
88
113
  SELECT *
89
114
  FROM cypher('#{age_graph}', $$
90
115
  MATCH ()-[#{alias_name}:#{age_label}]->()
91
- WHERE id(#{alias_name}) = #{id}
92
- SET #{set_caluse}
116
+ WHERE id(#{alias_name}) = #{sanitized_id}
117
+ SET #{set_clause}
93
118
  RETURN #{alias_name}
94
119
  $$) as (#{age_label} agtype);
95
120
  SQL
@@ -3,14 +3,20 @@ module ApacheAge
3
3
  class Entity
4
4
  class << self
5
5
  def find_by(attributes)
6
- where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
6
+ where_clause =
7
+ attributes
8
+ .map do |k, v|
9
+ if k == :id
10
+ ActiveRecord::Base.sanitize_sql(["id(find) = ?", v])
11
+ else
12
+ ActiveRecord::Base.sanitize_sql(["find.#{k} = ?", v])
13
+ end
14
+ end
15
+ .join(' AND ')
7
16
  handle_find(where_clause)
8
17
  end
9
18
 
10
- def find(id)
11
- where_clause = "id(find) = #{id}"
12
- handle_find(where_clause)
13
- end
19
+ def find(id) = find_by(id: id)
14
20
 
15
21
  private
16
22
 
@@ -46,19 +52,24 @@ module ApacheAge
46
52
  json_data = JSON.parse(json_string)
47
53
 
48
54
  age_label = json_data['label']
49
- attribs = json_data.except('label', 'properties')
50
- .merge(json_data['properties'])
51
- .symbolize_keys
55
+ attribs =
56
+ json_data
57
+ .except('label', 'properties')
58
+ .merge(json_data['properties'])
59
+ .symbolize_keys
52
60
 
53
61
  "#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
54
62
  end
55
63
 
56
64
  def find_sql(match_clause, where_clause)
65
+ sanitized_match_clause = ActiveRecord::Base.sanitize_sql(match_clause)
66
+ sanitized_where_clause = where_clause # Already sanitized in `find_by` or `find`
67
+
57
68
  <<-SQL
58
69
  SELECT *
59
70
  FROM cypher('#{age_graph}', $$
60
- MATCH #{match_clause}
61
- WHERE #{where_clause}
71
+ MATCH #{sanitized_match_clause}
72
+ WHERE #{sanitized_where_clause}
62
73
  RETURN find
63
74
  $$) as (found agtype);
64
75
  SQL
@@ -7,6 +7,8 @@ module ApacheAge
7
7
  include ActiveModel::Model
8
8
  include ActiveModel::Dirty
9
9
  include ActiveModel::Attributes
10
+ include ActiveModel::Validations
11
+ include ActiveModel::Validations::Callbacks
10
12
 
11
13
  attribute :id, :integer
12
14
 
@@ -23,11 +25,21 @@ module ApacheAge
23
25
  # RETURN company
24
26
  # $$) as (Company agtype);
25
27
  def create_sql
28
+ # can't use sanitiye without a solution for '_' in the alias name & label
29
+ # alias_name = ActiveRecord::Base.sanitize_sql_like(age_alias || age_label.downcase)
30
+ # label_name = ActiveRecord::Base.sanitize_sql_like(age_label)
31
+
26
32
  alias_name = age_alias || age_label.downcase
27
- <<-SQL
33
+ sanitized_properties =
34
+ self
35
+ .to_h.reject { |k, v| k == :id }.reject { |k, v| v.nil? }
36
+ .map { |k, v| "#{k}: #{ActiveRecord::Base.sanitize_sql(["?", v])}" }
37
+ .join(', ')
38
+
39
+ <<~SQL.squish
28
40
  SELECT *
29
41
  FROM cypher('#{age_graph}', $$
30
- CREATE (#{alias_name}#{self})
42
+ CREATE (#{alias_name}:#{age_label} {#{sanitized_properties}})
31
43
  RETURN #{alias_name}
32
44
  $$) as (#{age_label} agtype);
33
45
  SQL
@@ -35,19 +47,29 @@ module ApacheAge
35
47
 
36
48
  # So far just properties of string type with '' around them
37
49
  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(', ')
50
+ alias_name = ActiveRecord::Base.sanitize_sql_like(age_alias || age_label.downcase)
51
+ sanitized_set_clause = age_properties.map do |k, v|
52
+ if v
53
+ sanitized_value = ActiveRecord::Base.sanitize_sql(["?", v])
54
+ "#{alias_name}.#{k} = #{sanitized_value}"
55
+ else
56
+ "#{alias_name}.#{k} = NULL"
57
+ end
58
+ end.join(', ')
59
+
60
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["?", id])
61
+
41
62
  <<-SQL
42
63
  SELECT *
43
64
  FROM cypher('#{age_graph}', $$
44
65
  MATCH (#{alias_name}:#{age_label})
45
- WHERE id(#{alias_name}) = #{id}
46
- SET #{set_caluse}
66
+ WHERE id(#{alias_name}) = #{sanitized_id}
67
+ SET #{sanitized_set_clause}
47
68
  RETURN #{alias_name}
48
69
  $$) as (#{age_label} agtype);
49
70
  SQL
50
71
  end
72
+
51
73
  end
52
74
  end
53
75
  end
@@ -0,0 +1,27 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module Path
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Model
8
+ include ActiveModel::Dirty
9
+ include ActiveModel::Attributes
10
+
11
+ attribute :id, :integer
12
+ # attribute :label, :string
13
+ attribute :end_id, :integer
14
+ attribute :start_id, :integer
15
+ # override with a specific node type in the defining class
16
+ attribute :end_node
17
+ attribute :start_node
18
+
19
+ validates :end_node, :start_node, presence: true
20
+ validate :validate_nodes
21
+
22
+ extend ApacheAge::Entities::ClassMethods
23
+ include ApacheAge::Entities::CommonMethods
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,180 @@
1
+ # query =
2
+ # Person.
3
+ # cypher('age_schema')
4
+ # .match("(a:Person), (b:Person)")
5
+ # .where("a.name = 'Node A'", "b.name = 'Node B'")
6
+ # .return("a.name", "b.name")
7
+ # .as("name_a agtype, name_b agtype")
8
+ # .execute
9
+ # def cypher(graph_name = 'age_schema')
10
+ # ApacheAge::Cypher.new(graph_name)
11
+ # self
12
+ # end
13
+
14
+ module ApacheAge
15
+ module Entities
16
+ class QueryBuilder
17
+ attr_accessor :where_clauses, :order_clause, :limit_clause, :model_class, :match_clause,
18
+ :graph_name, :return_clause, :return_names, :return_variables
19
+
20
+ def initialize(model_class, graph_name: nil)
21
+ @model_class = model_class
22
+ @where_clauses = []
23
+ @return_names = ['find']
24
+ @return_clause = 'find'
25
+ @return_variables = []
26
+ @order_clause = nil
27
+ @limit_clause = nil
28
+ @match_clause = model_class.match_clause
29
+ @graph_name = graph_name || model_class.age_graph
30
+ end
31
+
32
+ # def cypher(graph_name = 'age_schema')
33
+ # return self if graph_name.blank?
34
+
35
+ # @graph_name = graph_name
36
+ # self
37
+ # end
38
+
39
+ def match(match_string)
40
+ @match_clause = match_string
41
+ self
42
+ end
43
+
44
+ # TODO: need to handle string inputs too: instead of: \
45
+ # "id(find) = #{id}" & "find.name = #{name}"
46
+ # we can have: "id(find) = ?", id & "find.name = ?", name
47
+ # ActiveRecord::Base.sanitize_sql([query_string, v])
48
+ def where(attributes)
49
+ return self if attributes.blank?
50
+
51
+ @where_clauses <<
52
+ if attributes.is_a?(String)
53
+ if attributes.include?('id(') || attributes.include?('find.')
54
+ attributes
55
+ else
56
+ "find.#{attributes}"
57
+ end
58
+ else
59
+ edge_keys = [:start_id, :start_node, :end_id, :end_node]
60
+ if edge_keys.any? { |key| attributes.include?(key) }
61
+ model_class.send(:where_edge_clause, attributes)
62
+ else
63
+ model_class.send(:where_node_clause, attributes)
64
+ end
65
+ end
66
+
67
+ self
68
+ end
69
+
70
+ # New return method
71
+ def return(*variables)
72
+ return self if variables.blank?
73
+
74
+ @return_variables = variables
75
+ # @return_names = variables.empty? ? ['find'] : variables
76
+ # @return_clause = variables.empty? ? 'find' : "find.#{variables.join(', find.')}"
77
+ self
78
+ end
79
+
80
+ def order(ordering)
81
+ @order_clause = nil
82
+ return self if ordering.blank?
83
+
84
+ order_by_values = Array.wrap(ordering).map { |order| parse_ordering(order) }.join(', ')
85
+ @order_clause = "ORDER BY #{order_by_values}"
86
+ self
87
+ end
88
+
89
+ def limit(limit_value)
90
+ @limit_clause = "LIMIT #{limit_value}"
91
+ self
92
+ end
93
+
94
+ def all
95
+ cypher_sql = build_query
96
+ results = model_class.send(:execute_where, cypher_sql)
97
+ return results if return_variables.empty?
98
+
99
+ results.map(&:to_h).map { _1.slice(*return_variables) }
100
+ end
101
+
102
+ def execute
103
+ cypher_sql = build_query
104
+ model_class.send(:execute_sql, cypher_sql)
105
+ end
106
+
107
+ def first
108
+ cypher_sql = build_query(limit_clause || "LIMIT 1")
109
+ model_class.send(:execute_find, cypher_sql)
110
+ end
111
+
112
+ def to_sql
113
+ build_query.strip
114
+ end
115
+
116
+ private
117
+
118
+ # TODO: ensure ordering keys are present in the model
119
+ def parse_ordering(ordering)
120
+ if ordering.is_a?(Hash)
121
+ ordering =
122
+ ordering
123
+ .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
124
+ .join(', ')
125
+ elsif ordering.is_a?(Symbol)
126
+ ordering = "find.#{ordering}"
127
+ elsif ordering.is_a?(String)
128
+ ordering
129
+ elsif ordering.is_a?(Array)
130
+ ordering = ordering.map do |order|
131
+ if order.is_a?(Hash)
132
+ order
133
+ .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
134
+ .join(', ')
135
+ elsif order.is_a?(Symbol)
136
+ "find.#{order}"
137
+ elsif order.is_a?(String)
138
+ order
139
+ else
140
+ raise ArgumentError, 'Array elements must be a string, symbol, or hash'
141
+ end
142
+ end.join(', ')
143
+ else
144
+ raise ArgumentError, 'Ordering must be a string, symbol, hash, or array'
145
+ end
146
+ end
147
+
148
+ def build_query(_extra_clause = nil)
149
+ where_sql = where_clauses.any? ? "WHERE #{where_clauses.join(' AND ')}" : ''
150
+ order_by = order_clause.present? ? order_clause : ''
151
+ <<-SQL.squish
152
+ SELECT *
153
+ FROM cypher('#{graph_name}', $$
154
+ MATCH #{match_clause}
155
+ #{where_sql}
156
+ RETURN #{return_clause}
157
+ #{order_clause}
158
+ #{limit_clause}
159
+ $$) AS (#{return_names.join(' agtype, ')} agtype);
160
+ SQL
161
+ end
162
+ # def build_query(_extra_clause = nil)
163
+ # sanitized_where_sql = where_clauses.any? ? "WHERE #{where_clauses.map { |clause| ActiveRecord::Base.sanitize_sql_like(clause) }.join(' AND ')}" : ''
164
+ # sanitized_order_by = order_clause.present? ? ActiveRecord::Base.sanitize_sql_like(order_clause) : ''
165
+ # sanitized_limit_clause = limit_clause.present? ? ActiveRecord::Base.sanitize_sql_like(limit_clause) : ''
166
+
167
+ # <<-SQL.squish
168
+ # SELECT *
169
+ # FROM cypher('#{graph_name}', $$
170
+ # MATCH #{ActiveRecord::Base.sanitize_sql_like(match_clause)}
171
+ # #{sanitized_where_sql}
172
+ # RETURN #{ActiveRecord::Base.sanitize_sql_like(return_clause)}
173
+ # #{sanitized_order_by}
174
+ # #{sanitized_limit_clause}
175
+ # $$) AS (#{return_names.map { |name| "#{ActiveRecord::Base.sanitize_sql_like(name)} agtype" }.join(', ')});
176
+ # SQL
177
+ # end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,4 @@
1
+ module ApacheAge
2
+ class Path
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.6.0'
2
+ VERSION = '0.6.2'
3
3
  end
data/lib/rails_age.rb CHANGED
@@ -6,13 +6,17 @@ module RailsAge
6
6
  end
7
7
 
8
8
  module ApacheAge
9
+ require 'apache_age/cypher.rb'
10
+ require 'apache_age/entities/query_builder'
9
11
  require 'apache_age/entities/class_methods'
10
12
  require 'apache_age/entities/common_methods'
11
13
  require 'apache_age/entities/entity'
12
14
  require 'apache_age/entities/node'
13
15
  require 'apache_age/entities/edge'
16
+ # require 'apache_age/entities/path'
14
17
  require 'apache_age/node'
15
18
  require 'apache_age/edge'
19
+ # require 'apache_age/path'
16
20
  require 'apache_age/validators/expected_node_type'
17
21
  require 'apache_age/validators/unique_node'
18
22
  require 'apache_age/validators/unique_edge'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_age
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill Tihen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-27 00:00:00.000000000 Z
11
+ date: 2024-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '9.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: ostruct
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: rspec-rails
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -96,14 +110,17 @@ files:
96
110
  - config/routes.rb
97
111
  - db/migrate/20240521062349_add_apache_age.rb
98
112
  - db/schema.rb
113
+ - lib/apache_age/cypher.rb
99
114
  - lib/apache_age/edge.rb
100
115
  - lib/apache_age/entities/class_methods.rb
101
116
  - lib/apache_age/entities/common_methods.rb
102
117
  - lib/apache_age/entities/edge.rb
103
118
  - lib/apache_age/entities/entity.rb
104
119
  - lib/apache_age/entities/node.rb
105
- - lib/apache_age/entities/vertex.rb
120
+ - lib/apache_age/entities/path.rb
121
+ - lib/apache_age/entities/query_builder.rb
106
122
  - lib/apache_age/node.rb
123
+ - lib/apache_age/path.rb
107
124
  - lib/apache_age/types/factory.rb
108
125
  - lib/apache_age/validators/expected_node_type.rb
109
126
  - lib/apache_age/validators/node_type_validator.rb
@@ -162,14 +179,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
162
179
  requirements:
163
180
  - - ">="
164
181
  - !ruby/object:Gem::Version
165
- version: '3.0'
182
+ version: '3.2'
166
183
  required_rubygems_version: !ruby/object:Gem::Requirement
167
184
  requirements:
168
185
  - - ">="
169
186
  - !ruby/object:Gem::Version
170
187
  version: '0'
171
188
  requirements: []
172
- rubygems_version: 3.5.10
189
+ rubygems_version: 3.5.18
173
190
  signing_key:
174
191
  specification_version: 4
175
192
  summary: Apache AGE plugin for Rails 7.x
@@ -1,53 +0,0 @@
1
- # module ApacheAge
2
- # module Entities
3
- # module Vertex
4
- # extend ActiveSupport::Concern
5
-
6
- # included do
7
- # include ActiveModel::Model
8
- # include ActiveModel::Dirty
9
- # include ActiveModel::Attributes
10
-
11
- # attribute :id, :integer
12
-
13
- # extend ApacheAge::Entities::ClassMethods
14
- # include ApacheAge::Entities::CommonMethods
15
- # end
16
-
17
- # def age_type = 'vertex'
18
-
19
- # # AgeSchema::Nodes::Company.create(company_name: 'Bedrock Quarry')
20
- # # SELECT *
21
- # # FROM cypher('age_schema', $$
22
- # # CREATE (company:Company {company_name: 'Bedrock Quarry'})
23
- # # RETURN company
24
- # # $$) as (Company agtype);
25
- # def create_sql
26
- # alias_name = age_alias || age_label.downcase
27
- # <<-SQL
28
- # SELECT *
29
- # FROM cypher('#{age_graph}', $$
30
- # CREATE (#{alias_name}#{self})
31
- # RETURN #{alias_name}
32
- # $$) as (#{age_label} agtype);
33
- # SQL
34
- # end
35
-
36
- # # So far just properties of string type with '' around them
37
- # def update_sql
38
- # alias_name = age_alias || age_label.downcase
39
- # set_caluse =
40
- # age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
41
- # <<-SQL
42
- # SELECT *
43
- # FROM cypher('#{age_graph}', $$
44
- # MATCH (#{alias_name}:#{age_label})
45
- # WHERE id(#{alias_name}) = #{id}
46
- # SET #{set_caluse}
47
- # RETURN #{alias_name}
48
- # $$) as (#{age_label} agtype);
49
- # SQL
50
- # end
51
- # end
52
- # end
53
- # end