rails_age 0.3.1 → 0.4.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: 272f8908dc8fb8a98adb443411e4b439eff914c8173b5eb64b53fa5362fa1572
4
- data.tar.gz: bf1921611698ffc1b651787f2e1b1a5d1bc503cbe1300639b2f9dada8202cdde
3
+ metadata.gz: 8228c60b43732a31b364b658e65b5d6a2e1b76018b573b1ec5b7fa25582f519c
4
+ data.tar.gz: 60f5211cb37aa6605523f76cde81158b6495448e90d4fbeabecd020c38520c4e
5
5
  SHA512:
6
- metadata.gz: 51592c31e872a9b8dc6e96fe8fa42374241496e4da6a17ce70bf63f5f118ac078e81200259c048529e7e16b2147c8c5bbef00315cee8f26237295d04ffaea887
7
- data.tar.gz: 0b24560f7703e4dc4f97d3d36de626906a47a2cda5f5137cc6c1840a5281bbcf2693743666fb6301b26723c9723244bd1854c9c8b21d5d657eb621d4caf0d864
6
+ metadata.gz: eec16e9c7b4512793b4aac9a3ac1c9ee0ce6280a1033712cc6aed5466218ffa17a47c1fca4d1f4d0508ce38e4a12a9a0c392d927f72a62c5ebcf8d2ea7c343f5
7
+ data.tar.gz: 3ccdeba21d32bc35de9162698c5c340efce5b0557b5930a3b1e0d9caaf5697f5afcd2b39ad65d0c976b5b3bc46df5edaf18a7bb96e023a2090642b08eb002786
data/CHANGELOG.md CHANGED
@@ -1,21 +1,64 @@
1
1
  # Change Log
2
2
 
3
- ## VERSION 0.4.0 - 2024-xx-xx
3
+ ## VERSION 0.5.0 - 2024-xx-xx
4
+
5
+ - **AGE Schema override** (instance and class methods) assumes db and migrations up-to-date
4
6
 
5
7
  - **cypher**
8
+ * schema override
6
9
  * query support
7
10
  * paths support
8
11
  * select attributes support
12
+
9
13
  - **Paths**
10
- * ?
11
14
 
12
- ## VERSION 0.3.1 - 2024-xx-xx
15
+ ## VERSION 0.4.4 - 2024-xx-xx
16
+
17
+ - **Edge Scaffold** (generates edge, type, view and controller)
18
+ * add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
19
+
20
+ ## VERSION 0.4.3 - 2024-xx-xx
21
+
22
+ - **Node Scaffold** (generates node, type, view and controller)
23
+ * add `rails generate apache_age:node_scaffold Person first_name last_name age:integer`
24
+
25
+ ## VERSION 0.4.2 - 2024-xx-xx
26
+
27
+ - **Edge Generator**
28
+ * add `rails generate apache_age:edge HasPet owner_role` just a property
29
+ * add `rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
30
+ with property and specified start-/end-nodes (person and pet nodes must have already been created)
31
+
32
+ ## VERSION 0.4.1 - 2024-xx-xx
33
+
34
+ - **OPTIONAL Installer**
35
+ * add `config_migrate` to `rails generate apache_age:install` auto fix the schema after `rails db:migrate`
36
+
37
+ ## VERSION 0.4.0 - 2024-06-14
38
+
39
+ Minor breaking change: type (:vertix) is now required in core for edges
40
+
41
+ - **Installer**
42
+ * AGE types added to installer (with tests)
43
+
44
+ - **Node Generator**
45
+ * add also creates node types (with tests)
46
+
47
+ - **Apache AGE Migrate**
48
+ * add `bin/rails apache_age:migrate` runs `bin/rails db:migrate` followed by `bin/rails apache_age:config_schema` to fix the schema file after `bin/rails db:migrate`
49
+
50
+ ## VERSION 0.3.2 - 2024-06-08
51
+
52
+ - **Node Generator**
53
+ * add `rails generate apache_age:node Pets/Cat name age:integer` creates a node with a namespace and attributes at: `app/nodes/pets/cat.rb`
54
+ * add `rails generate apache_age:node Cat name age:integer` creates a node with attributes at: `app/nodes/cat.rb`
55
+ * add `rails destroy apache_age:node Cat` deletes an existing node at: `app/nodes/cat.rb`
56
+
57
+ ## VERSION 0.3.1 - 2024-06-02
13
58
 
14
- - **Generators**
15
- * add `rails generate apache_age:node` to create a node model (with its type in initializer)
16
- * add `rails generate apache_age:edge` to create an edge model (with its type in initializer)
17
59
  - **Installer**
18
60
  * refactor into multiple independent tasks with tests
61
+
19
62
  - **Documentation**
20
63
  * updated README with additional information
21
64
  * added `db/structure.sql` config to README
@@ -24,6 +67,7 @@
24
67
 
25
68
  - **Edges**
26
69
  * `find_by(start_node:, :end_node:, properties:)` to find an edge with specific nodes & properties (deprecated `find_edge`)
70
+
27
71
  - **Installer** (`rails generate apache_age:install`)
28
72
  * copy Age PG Extenstion migration to `db/migrate`
29
73
  * run the AGE PG Migration
@@ -39,6 +83,7 @@ NOTE: the `rails generate apache_age:install` can be run at any time to repair t
39
83
  * add missing methods to use in rails controllers
40
84
  * validate edge start- & end-nodes are valid
41
85
  * add unique edge validations
86
+
42
87
  - **Nodes**
43
88
  * add missing methods to use in rails controllers
44
89
  * add unique node validations
@@ -50,9 +95,11 @@ Initial release has the following features:
50
95
  - **Nodes:**
51
96
  * `.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
52
97
  * verified with usage in a controller and views
98
+
53
99
  - **Edges:**
54
100
  *`.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
55
101
  * verified with usage in a controller and views
102
+
56
103
  - **Entities:**
57
104
  * `.all`, `.find(id)`, `.find_by(age_property)` use these when class, label, edge, node
58
105
 
data/README.md CHANGED
@@ -12,368 +12,109 @@ Add this line to your application's Gemfile:
12
12
  gem "rails_age"
13
13
  ```
14
14
 
15
- ### Quick Install
15
+ ## Quick Start
16
16
 
17
17
  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
18
 
19
+ setup (& Test) postgresql with AGE (using the docker version of AGE DB may be the easiest way to get started)
20
+ using the docker version of AGE DB, you can confirm psql AGE with the following commands:
19
21
  ```bash
20
- $ bundle
21
- $ bin/rails apache_age:install
22
- $ git add .
23
- $ git commit -m "Add Apache Age to Rails"
22
+ psql -h localhost -p 5455 -U docker_username
23
+ > CREATE EXTENSION IF NOT EXISTS age;
24
+ > LOAD 'age';
25
+ > SET search_path = ag_catalog, "$user", public;
26
+ > SELECT create_graph('age_schema');
27
+ > \q
24
28
  ```
25
29
 
26
- NOTE: it is important to commit the `db/schema.rb` to git because `rails db:migrate` inappropriately modifies the schema file (I haven't yet tested `db/structure.sql`). **You can run `bin/rails apache_age:install` at any time to repair the schema file as needed.**
27
-
28
- For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
29
-
30
- ### Manual Install
31
-
32
- create a migration to add the Apache Age extension to your database
30
+ create a new Rails app (WITH POSTGRESQL!)
33
31
  ```bash
34
- $ bin/rails g migration AddApacheAge
32
+ rails new age_demo -d postgresql
33
+ cd age_demo
34
+ git add .
35
+ git commit -m "Initial Rails App"
35
36
  ```
36
- copy the contents of https://github.com/marpori/rails_age/blob/main/db/migrate/20240521062349_add_apache_age.rb
37
- ```ruby
38
- class AddApacheAge < ActiveRecord::Migration[7.1]
39
- def up
40
- # Allow age extension
41
- execute('CREATE EXTENSION IF NOT EXISTS age;')
42
-
43
- # Load the age code
44
- execute("LOAD 'age';")
45
-
46
- # Load the ag_catalog into the search path
47
- execute('SET search_path = ag_catalog, "$user", public;')
48
-
49
- # Create age_schema graph if it doesn't exist
50
- execute("SELECT create_graph('age_schema');")
51
- end
52
-
53
- def down
54
- execute <<-SQL
55
- DO $$
56
- BEGIN
57
- IF EXISTS (
58
- SELECT 1
59
- FROM pg_constraint
60
- WHERE conname = 'fk_graph_oid'
61
- ) THEN
62
- ALTER TABLE ag_catalog.ag_label
63
- DROP CONSTRAINT fk_graph_oid;
64
- END IF;
65
- END $$;
66
- SQL
67
-
68
- execute("SELECT drop_graph('age_schema', true);")
69
- execute('DROP SCHEMA IF EXISTS ag_catalog CASCADE;')
70
- execute('DROP EXTENSION IF EXISTS age;')
71
- end
72
- end
37
+ configure `config/database.yml` when using the docker version of AGE DB my config looks like:
38
+ ```yaml
39
+ port: 5455
40
+ host: localhost
41
+ username: docker_username
42
+ password: dockerized_password
73
43
  ```
74
- into your new migration file
75
44
 
76
- then run the migration
45
+ now you should be able to create the rails database:
77
46
  ```bash
78
- $ bin/rails db:migrate
79
- ```
80
-
81
- Rails migrate will mangle the schema `db/schema.rb` file. You need to remove the lines that look like:
82
- ```ruby
83
- ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
84
- create_schema "ag_catalog"
85
- create_schema "age_schema"
86
-
87
- # These are extensions that must be enabled in order to support this database
88
- enable_extension "age"
89
- enable_extension "plpgsql"
90
-
91
- # Could not dump table "_ag_label_edge" because of following StandardError
92
- # Unknown type 'graphid' for column 'id'
93
-
94
- # Could not dump table "_ag_label_vertex" because of following StandardError
95
- # Unknown type 'graphid' for column 'id'
96
-
97
- # Could not dump table "ag_graph" because of following StandardError
98
- # Unknown type 'regnamespace' for column 'namespace'
99
-
100
- # Could not dump table "ag_label" because of following StandardError
101
- # Unknown type 'regclass' for column 'relation'
102
-
103
- add_foreign_key "ag_label", "ag_graph", column: "graph", primary_key: "graphid", name: "fk_graph_oid"
104
-
105
- # other migrations
106
- # ...
107
- end
108
- ```
109
-
110
- and replace them with the following lines:
111
- ```ruby
112
- ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
113
- # These are extensions that must be enabled in order to support this database
114
- enable_extension 'plpgsql'
115
-
116
- # Allow age extension
117
- execute('CREATE EXTENSION IF NOT EXISTS age;')
118
-
119
- # Load the age code
120
- execute("LOAD 'age';")
121
-
122
- # Load the ag_catalog into the search path
123
- execute('SET search_path = ag_catalog, "$user", public;')
124
-
125
- # Create age_schema graph if it doesn't exist
126
- execute("SELECT create_graph('age_schema');")
127
-
128
- # other migrations
129
- # ...
130
- end
47
+ rails db:create
48
+ rails db:migrate
49
+ git add .
50
+ git commit -m "Add Apache Age Postgres DB configured with Rails App"
131
51
  ```
132
52
 
133
- NOTE: if using `db/structure.sql` use:
134
- ```sql
135
- -- These are extensions that must be enabled in order to support this database
136
- CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA public;
137
-
138
- -- Allow age extension (if not already enabled), this builds the age_catalog schema
139
- CREATE EXTENSION IF NOT EXISTS age;
140
-
141
- -- Load the age module
142
- LOAD 'age';
143
-
144
- -- Load the ag_catalog into the search path
145
- SET search_path = ag_catalog, "$user", public;
146
-
147
- -- Create age_schema graph if it doesn't exist
148
- SELECT create_graph('age_schema');
149
-
150
- # other migrations
151
- # ...
152
-
153
- INSERT INTO "schema_migrations" (version) VALUES
154
- ('20110315075839'),
155
- --- ...
156
- ('20240521062349');
53
+ install Apache Age (you can ignore the `unknown OID` warnings)
54
+ ```bash
55
+ bundle add rails_age
56
+ bundle install
57
+ bin/rails apache_age:install
58
+ git add .
59
+ git commit -m "Add Apache Age to Rails"
157
60
  ```
158
61
 
159
- ## Contributing
160
-
161
- Create an merge request (with tests) and I will review it/merge it when ready.
162
-
163
- ## License
164
-
165
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
166
-
167
- ## Usage
168
-
169
- I suggest you creat a folder create a folder called `app/nodes` and `app/edges` to keep the code organized.
170
- I frequently use the `app/graphs` folder to keep all the graph related code together in a Module (as is done in the [rails age demo app](https://github.com/marpori/rails_age_demo_app))
171
-
172
- I suggest you creat a folder within app called `graphs` and under that create a folder called `nodes` and `edges`. This will help you keep your code organized.
173
-
174
- A trival, but fully functional [rails age demo app](https://github.com/marpori/rails_age_demo_app), based on the Flintstones Commic, is available for reference.
175
-
176
- ### Nodes
177
-
178
- ```ruby
179
- # app/graphs/nodes/company.rb
180
- module Nodes
181
- class Company
182
- include ApacheAge::Entities::Vertex
183
-
184
- attribute :company_name, :string
185
-
186
- validates :company_name, presence: true
187
- validates_with(
188
- ApacheAge::Validators::UniqueVertexValidator,
189
- attributes: [:company_name]
190
- )
191
- end
192
- end
62
+ make some nodes :string is the default type
63
+ ```bash
64
+ rails generate apache_age:node Company company_name
65
+ rails generate apache_age:node Person first_name last_name
66
+ rails generate apache_age:node Pet pet_name:string age:integer
193
67
  ```
68
+ make some edges (`:vertex` is the default type) for start_node and end_node
69
+ ```bash
70
+ # when start node and end node are not specified they are of type `:vertex`
71
+ # this is generally not recommended - exept when very generic relationships are needed
72
+ rails generate apache_age:edge HasJob employee_role
194
73
 
195
- ```ruby
196
- # app/graphs/nodes/person.rb
197
- module Nodes
198
- class Person
199
- include ApacheAge::Entities::Vertex
200
-
201
- attribute :first_name, :string, default: nil
202
- attribute :last_name, :string, default: nil
203
- attribute :given_name, :string, default: nil
204
- attribute :nick_name, :string, default: nil
205
- attribute :gender, :string, default: nil
206
-
207
- validates :gender, :first_name, :last_name, :given_name, :nick_name,
208
- presence: true
209
-
210
- def initialize(**attributes)
211
- super
212
- # use unless present? since attributes when empty sets to "" by default
213
- self.nick_name = first_name unless nick_name.present?
214
- self.given_name = last_name unless given_name.present?
215
- end
216
- end
217
- end
74
+ # this is recommended - use explicit start_node and end_node types
75
+ rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role
218
76
  ```
219
77
 
220
- ### Edges
221
-
222
- ```ruby
223
- # app/graphs/edges/has_job.rb
224
- module Edges
225
- class HasJob
226
- include ApacheAge::Entities::Edge
227
-
228
- attribute :employee_role, :string
229
- attribute :start_node, :person
230
- attribute :end_node, :company
231
-
232
- validates :employee_role, presence: true
233
- validate :validate_unique
234
- # or with a one-liner
235
- # validates_with(
236
- # ApacheAge::Validators::UniqueEdgeValidator,
237
- # attributes: %i[employee_role start_node end_node]
238
- # )
78
+ **NOTE:** the default `rails db:migrate` inappropriately modifies the schema file. This installer patches the migration to prevent this (however this might break on rails updates, etc). **You can run `bin/rails apache_age:install` at any time to repair the schema file as needed.**
239
79
 
240
- private
241
-
242
- def validate_unique
243
- ApacheAge::Validators::UniqueEdgeValidator
244
- .new(attributes: %i[employee_role start_node end_node])
245
- .validate(self)
246
- end
247
- end
248
- end
249
- ```
80
+ For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
250
81
 
251
82
  ### Rails Console Usage
252
83
 
253
84
  ```ruby
254
- fred = Nodes::Person.create(first_name: 'Fredrick Jay', nick_name: 'Fred', last_name: 'Flintstone', gender: 'male')
255
- fred.to_h
256
-
257
- quarry = Nodes::Company.create(company_name: 'Bedrock Quarry')
258
- quarry.to_h
259
-
260
- job = Edges::HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
261
- job.to_h
262
- ```
263
-
264
- ### Update Routes
265
-
266
- ```ruby
267
- Rails.application.routes.draw do
268
- # mount is not needed with the engine
269
- # mount RailsAge::Engine => "/rails_age"
270
-
271
- # defines the route for the people controller
272
- resources :people
273
-
274
- # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
275
- # Can be used by load balancers and uptime monitors to verify that the app is live.
276
- get 'up' => 'rails/health#show', as: :rails_health_check
277
-
278
- # Defines the root path route ("/")
279
- root 'people#index'
280
- end
281
- ```
282
-
283
- ### Types (Optional)
284
-
285
- ```ruby
286
- # spec/dummy/config/initializers/types.rb
287
- require 'apache_age/types/age_type_generator'
288
-
289
- Rails.application.config.to_prepare do
290
- # Ensure the files are loaded
291
- require_dependency 'nodes/company'
292
- require_dependency 'nodes/person'
293
-
294
- # Register the custom types
295
- ActiveModel::Type.register(:company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company))
296
- ActiveModel::Type.register(:person, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Person))
297
- end
298
- ```
299
-
300
- ### Controller Usage
301
-
302
- ```ruby
303
- # app/controllers/people_controller.rb
304
- class PeopleController < ApplicationController
305
- before_action :set_person, only: %i[show edit update destroy]
306
-
307
- # GET /people or /people.json
308
- def index
309
- @people = Nodes::Person.all
310
- end
311
-
312
- # GET /people/1 or /people/1.json
313
- def show; end
314
-
315
- # GET /people/new
316
- def new
317
- @person = Nodes::Person.new
318
- end
319
-
320
- # GET /people/1/edit
321
- def edit; end
322
-
323
- # POST /people or /people.json
324
- def create
325
- @person = Nodes::Person.new(**person_params)
326
- respond_to do |format|
327
- if @person.save
328
- format.html { redirect_to person_url(@person), notice: 'Person was successfully created.' }
329
- format.json { render :show, status: :created, location: @person }
330
- else
331
- format.html { render :new, status: :unprocessable_entity }
332
- format.json { render json: @person.errors, status: :unprocessable_entity }
333
- end
334
- end
335
- end
336
-
337
- # PATCH/PUT /people/1 or /people/1.json
338
- def update
339
- respond_to do |format|
340
- if @person.update(**person_params)
341
- format.html { redirect_to person_url(@person), notice: 'Person was successfully updated.' }
342
- format.json { render :show, status: :ok, location: @person }
343
- else
344
- format.html { render :edit, status: :unprocessable_entity }
345
- format.json { render json: @person.errors, status: :unprocessable_entity }
346
- end
347
- end
348
- end
349
-
350
- # DELETE /people/1 or /people/1.json
351
- def destroy
352
- @person.destroy!
353
-
354
- respond_to do |format|
355
- format.html { redirect_to people_url, notice: 'Person was successfully destroyed.' }
356
- format.json { head :no_content }
357
- end
358
- end
359
-
360
- private
361
-
362
- # Use callbacks to share common setup or constraints between actions.
363
- def set_person
364
- @person = Nodes::Person.find(params[:id])
365
- end
366
-
367
- # Only allow a list of trusted parameters through.
368
- def person_params
369
- # params.fetch(:person, {})
370
- params.require(:nodes_person).permit(:first_name, :last_name, :nick_name, :given_name, :gender)
371
- end
372
- end
373
- ```
374
-
375
- ### Views
376
-
377
- ```erb
378
-
379
- ```
85
+ bin/rails c
86
+
87
+ fred = Person.new(first_name: 'Fredrick Jay', last_name: 'Flintstone')
88
+ fred.valid?
89
+ fred.save
90
+ fred.to_h # should have an ID
91
+
92
+ # fails because of a missing required field (Property)
93
+ incomplete = Person.new(first_name: 'Fredrick Jay')
94
+ incomplete.valid?
95
+ incomplete.errors
96
+ incomplete.to_h
97
+
98
+ # fails because of uniqueness constraints
99
+ jay = Person.create(first_name: 'Fredrick Jay', last_name: 'Flintstone')
100
+ jay.to_h
101
+ => {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
102
+ jay.valid?
103
+ => false
104
+ jay.errors
105
+ => #<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={}>]>
106
+ irb(main):008> jav.to_h
107
+ => {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
108
+
109
+ # .create is a shortcut for .new and .save
110
+ quarry = Company.create(company_name: 'Bedrock Quarry')
111
+ quarry.to_h # should have an ID
112
+
113
+ # create an edge (no generator yet)
114
+ job = HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
115
+ job.to_h # should have an ID
116
+ ```
117
+
118
+ ## Manual Install, Config and Usage
119
+
120
+ see [Manuel Installation, Configuration and Usage](MANUAL_INSTALL.md)
@@ -11,9 +11,9 @@ module ApacheAge
11
11
  attribute :id, :integer
12
12
  attribute :end_id, :integer
13
13
  attribute :start_id, :integer
14
- # allow user to optionally specify the class type (or not) thus not adding: `:vertex`
15
- attribute :end_node
16
- attribute :start_node
14
+ # type: `:vertex` can be overriden with a specific node type
15
+ attribute :end_node, :vertex
16
+ attribute :start_node, :vertex
17
17
 
18
18
  validates :end_node, :start_node, presence: true
19
19
  validate :validate_nodes
@@ -35,10 +35,10 @@ module ApacheAge
35
35
  query = record.class.find_by(edge_attribs.compact)
36
36
  return if query.blank? || (query.id == record.id)
37
37
 
38
- record.errors.add(:base, 'attribute combination not unique')
39
- record.errors.add(:end_node, 'attribute combination not unique')
40
- record.errors.add(:start_node, 'attribute combination not unique')
41
- attributes.each { record.errors.add(_1, 'attribute combination not unique') }
38
+ record.errors.add(:base, 'record not unique')
39
+ record.errors.add(:end_node, 'node combination not unique')
40
+ record.errors.add(:start_node, 'node combination not unique')
41
+ attributes.each { record.errors.add(_1, 'prpoerty combination not unique') }
42
42
  end
43
43
 
44
44
  private
@@ -20,8 +20,8 @@ module ApacheAge
20
20
  # if no match is found or if it finds itself, it's valid
21
21
  return if query.blank? || (query.id == record.id)
22
22
 
23
- record.errors.add(:base, 'attribute combination not unique')
24
- attributes.each { record.errors.add(_1, 'attribute combination not unique') }
23
+ record.errors.add(:base, 'record not unique')
24
+ attributes.each { record.errors.add(_1, 'property combination not unique') }
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,54 @@
1
+ Description:
2
+ This creates Apache AGE nodes that work seamlessly with Rails.
3
+ A node can be created with or without a namespace.
4
+ See the below examples.
5
+
6
+ Example:
7
+ `bin/rails g apache_age:node Cat name age:integer`
8
+
9
+ This creates:
10
+ `app/nodes/cat.rb`
11
+
12
+ with the contents:
13
+ ```
14
+ class Cat
15
+ include ApacheAge::Entities::Vertex
16
+
17
+ attribute :name, :string
18
+ attribute :age, :integer
19
+
20
+ validates :name, presence: true
21
+ validates :age, presence: true
22
+
23
+ # unique node validator (remove any attributes that are not important to uniqueness)
24
+ validates_with(
25
+ ApacheAge::Validators::UniqueVertexValidator,
26
+ attributes: [:name, :age]
27
+ )
28
+ end
29
+ ```
30
+
31
+ A namespace can also be used:
32
+ `bin/rails g apache_age:node Animals/Cat name age:integer`
33
+
34
+ This creates:
35
+ `app/nodes/animals/cat.rb`
36
+
37
+ with the contents
38
+ ```
39
+ class Animals::Cat
40
+ include ApacheAge::Entities::Vertex
41
+
42
+ attribute :name, :string
43
+ attribute :age, :integer
44
+
45
+ validates :name, presence: true
46
+ validates :age, presence: true
47
+
48
+ # unique node validator (remove any attributes that are not important to uniqueness)
49
+ validates_with(
50
+ ApacheAge::Validators::UniqueVertexValidator,
51
+ attributes: [:name, :age]
52
+ )
53
+ end
54
+ ```