rails_age 0.5.2 → 0.5.3

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -7
  3. data/README.md +108 -57
  4. data/config/initializers/types.rb +20 -0
  5. data/lib/apache_age/edge.rb +5 -0
  6. data/lib/apache_age/entities/common_methods.rb +6 -0
  7. data/lib/apache_age/entities/edge.rb +4 -3
  8. data/lib/apache_age/entities/node.rb +53 -0
  9. data/lib/apache_age/entities/vertex.rb +1 -1
  10. data/lib/apache_age/node.rb +36 -0
  11. data/lib/apache_age/types/age_type_factory.rb +46 -0
  12. data/lib/apache_age/validators/expected_node_type.rb +17 -0
  13. data/lib/apache_age/validators/unique_node.rb +32 -0
  14. data/lib/apache_age/validators/{vertex_type_validator.rb → vertex_type_validator copy.rb } +2 -1
  15. data/lib/generators/apache_age/edge/templates/edge.rb.tt +3 -3
  16. data/lib/generators/apache_age/generator_entity_helpers.rb +5 -2
  17. data/lib/generators/apache_age/scaffold_edge/USAGE +16 -0
  18. data/lib/generators/apache_age/scaffold_edge/scaffold_edge_generator.rb +67 -0
  19. data/lib/generators/apache_age/scaffold_edge/scaffold_node_generator.rb +67 -0
  20. data/lib/generators/apache_age/scaffold_edge/templates/controller.rb.tt +50 -0
  21. data/lib/generators/apache_age/{scaffold_node/templates/views/_form.html.erb copy.tt → scaffold_edge/templates/views/_form.html.erb.tt} +10 -0
  22. data/lib/generators/apache_age/{scaffold_node/templates/views/index.html.erb copy.tt → scaffold_edge/templates/views/index.html.erb.tt} +1 -1
  23. data/lib/generators/apache_age/{scaffold_node/templates/views/partial.html.erb copy.tt → scaffold_edge/templates/views/partial.html.erb.tt} +11 -2
  24. data/lib/generators/apache_age/{scaffold_node/templates/views/show.html.erb copy.tt → scaffold_edge/templates/views/show.html.erb.tt} +1 -1
  25. data/lib/generators/apache_age/scaffold_node/scaffold_node_generator.rb +19 -20
  26. data/lib/rails_age/version.rb +1 -1
  27. data/lib/rails_age.rb +7 -1
  28. data/lib/tasks/config_types.rake +4 -4
  29. metadata +49 -10
  30. /data/lib/generators/apache_age/{scaffold_node/templates/views/edit.html.erb copy.tt → scaffold_edge/templates/views/edit.html.erb.tt} +0 -0
  31. /data/lib/generators/apache_age/{scaffold_node/templates/views/new.html.erb copy.tt → scaffold_edge/templates/views/new.html.erb.tt} +0 -0
  32. /data/lib/generators/apache_age/scaffold_node/templates/{node_controller.rb.tt → controller.rb.tt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d54712ca67a303ffa92d78165d30a500b1f695be6176b3198e7ff3f9f60ab4f9
4
- data.tar.gz: 8f8f999c42777dc432bf543b6256852d10a0fbcbcd4fe33ad06817277a47bb5d
3
+ metadata.gz: cd10883497fc245765a9e8313265e4d06c7a602a2f83cad592ecb5a951ab488d
4
+ data.tar.gz: ce03c6dc4535e2a54dddc9f2b9df8e4b413c6aeb690ee00138c4120fe1d72ce1
5
5
  SHA512:
6
- metadata.gz: 75c804a5b29705a18344ea70d9a799ea3578d90055b056ba48b70984132f2fbd04bdd906ac744e2a587fe095e9a8e112d773ecc84cf6325be192c212d769090d
7
- data.tar.gz: 932a6c5843243a23ef48b69e87f0fef940030c9e47ce652be141bc6efb45a8cbde04c191853b3d15e754e3ee71d7033b301ab6432982c1fc357250fceb892f02
6
+ metadata.gz: dcb00b35abd3a931c95ffce304c079eee2d1ba1b09ee3aa17f9c9150f4bc3b55276e7670cf3925e4ae484a117bef66bddc0242efc0cbc8295da03538ae628805
7
+ data.tar.gz: d6ac9aaa1ba0b04c9a48ed320e53a4de65a74e6cb6574f42c407b0c1124fafc650def68df4c41beaad27607b6f1fa22a7962518108c2fc39cd7c0e95f76d8c09
data/CHANGELOG.md CHANGED
@@ -20,24 +20,34 @@ breaking change?: namespaces (by default) will use their own schema? (add to dat
20
20
 
21
21
  - **multiple AGE Schema**
22
22
 
23
- ## VERSION 0.5.3 - 2024-xx-xx
23
+ ## VERSION 0.5.4 - 2024-xx-xx
24
24
 
25
- - **Edge Scaffold** (generates edge, type, view and controller)
26
- * add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
27
-
28
- ## VERSION 0.5.2 - 2024-xx-xx
25
+ - **Fix**
26
+ * show validation errors in scaffold views
29
27
 
30
28
  - **Edge Generator**
31
29
  * add start-/end-nodes types to edge generator (would make scaffold easier), ie:
32
30
  `rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
33
31
  with property and specified start-/end-nodes (person and pet nodes must have already been created)
34
32
 
33
+ - **Edge Scaffold** (generates edge, type, view and controller)
34
+ * add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
35
+
36
+ ## VERSION 0.5.3 - 2024-06-23
37
+
38
+ - **Edge Scaffold** (generates edge, type, view and controller) - without start-/end-nodes types!?
39
+ * add `rails generate apache_age:edge_scaffold HasJob employee_role`
40
+ * add system test (to dummy app after scaffold_node is run)
41
+
42
+ - **Node Scaffold** (generates node, type, view and controller)
43
+ * add system test (to dummy app after scaffold_node is run)
44
+
35
45
  ## VERSION 0.5.2 - 2024-06-16
36
46
 
37
47
  - **Node Scaffold** (generates node, type, view and controller)
38
48
  * add `rails generate apache_age:node_scaffold Person first_name last_name age:integer`
39
49
 
40
- ## VERSION 0.5.1 - 2024-06-16
50
+ ## VERSION 0.5.1 - 2024-06-16 (yanked)
41
51
 
42
52
  **yanked** (2024-06-16) - had an issue with the generator
43
53
 
@@ -50,7 +60,7 @@ breaking change?: namespaces (by default) will use their own schema? (add to dat
50
60
 
51
61
  - **Edge Generator**
52
62
  * add `rails generate apache_age:edge HasPet owner_role`
53
- caveate: start_node and end_node are of type `:vertex` in the generator but can be changed manually in the class file - having trouble with the generator loading the types (the generator rejects custom types - but rails still works with custom types)
63
+ caveate: start_node and end_node are of type `:node` in the generator but can be changed manually in the class file - having trouble with the generator loading the types (the generator rejects custom types - but rails still works with custom types)
54
64
 
55
65
  ## VERSION 0.4.1 - 2024-06-15
56
66
 
data/README.md CHANGED
@@ -1,25 +1,76 @@
1
1
  # RailsAge
2
2
 
3
- Simplify Apache Age usage within a Rails application.
3
+ Apache Age integration within a Rails application.
4
4
 
5
- ## Installation
5
+ ## Quick Start - Essentials
6
6
 
7
7
  **NOTE:** you must be using Postgres as your database! Apache Age requires it.
8
8
 
9
- Add this line to your application's Gemfile:
9
+ ```bash
10
+ bundle add rails_age
11
+ bundle install
12
+ bin/rails apache_age:install
13
+ # optional: prevents `bin/rails db:migrate` from modifying the schema file,
14
+ # bin/rails apache_age:override_db_migrate
15
+ git add .
16
+ git commit -m "Add & configure Apache Age within Rails"
17
+ ```
18
+
19
+ ## Generators
20
+
21
+ **NODES**
22
+
23
+ ```bash
24
+ rails generate apache_age:scaffold_node Company company_name
25
+
26
+ rails generate apache_age:scaffold_node Person first_name last_name
27
+ ```
28
+
29
+ **EDGES**
10
30
 
31
+ ```bash
32
+ rails generate apache_age:scaffold_edge HasJob employee_role start_date:date
33
+ ```
34
+
35
+ Ideally, edit the HasJob class so that `start_node` would use a type `:person` and the `end_node` uses at type `:company`
36
+
37
+ ie:
11
38
  ```ruby
12
- gem "rails_age"
39
+ # app/edges/has_job.rb
40
+ class HasJob
41
+ include ApacheAge::Entities::Edge
42
+
43
+ attribute :employee_role, :string
44
+ attribute :start_node, :person # instead of `:node`
45
+ attribute :end_node, :company # instead of `:node`
46
+
47
+ validates :employee_role, presence: true
48
+ validate :validate_unique_edge
49
+
50
+ private
51
+
52
+ def validate_unique_edge
53
+ ApacheAge::Validators::UniqueEdge
54
+ .new(attributes: %i[employee_role start_node end_node])
55
+ .validate(self)
56
+ end
57
+ end
13
58
  ```
14
59
 
15
- ## Quick Start
60
+ ## Installation in Detail
16
61
 
17
62
  using the installer, creates the migration to install age, runs the migration, and adjusts the schema file, and updates the `config/database.yml` file.
18
63
 
19
- 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:
64
+ ### Install Apache Age
65
+
66
+ see: [Apache AGE Installation](https://age.apache.org/age-manual/master/intro/setup.html#installation)
67
+ (The docker install is probably the easiest way to get started with a new application - for existing applications you may need to compile the extension from source.)
68
+
69
+ Verify your PostgreSQL AGE with the following commands:
70
+
21
71
  ```bash
22
- psql -h localhost -p 5455 -U docker_username
72
+ $ psql -h localhost -p 5455 -U docker_username
73
+
23
74
  > CREATE EXTENSION IF NOT EXISTS age;
24
75
  > LOAD 'age';
25
76
  > SET search_path = ag_catalog, "$user", public;
@@ -27,7 +78,12 @@ psql -h localhost -p 5455 -U docker_username
27
78
  > \q
28
79
  ```
29
80
 
81
+ ### Install and Configure Rails (if not done already)
82
+
83
+ AGE REQUIRES POSTGRESQL!
84
+
30
85
  create a new Rails app (WITH POSTGRESQL!)
86
+
31
87
  ```bash
32
88
  rails new age_demo -d postgresql
33
89
  cd age_demo
@@ -35,6 +91,7 @@ git add .
35
91
  git commit -m "Initial Rails App"
36
92
  ```
37
93
  configure `config/database.yml` when using the docker version of AGE DB my config looks like:
94
+
38
95
  ```yaml
39
96
  port: 5455
40
97
  host: localhost
@@ -42,15 +99,19 @@ username: docker_username
42
99
  password: dockerized_password
43
100
  ```
44
101
 
45
- now you should be able to create the rails database:
102
+ If both the Rails DB config and AGE DB are correctly configured, you should be able to run the following command without error:
103
+
46
104
  ```bash
47
105
  rails db:create
48
106
  rails db:migrate
49
107
  git add .
50
- git commit -m "Add Apache Age Postgres DB configured with Rails App"
108
+ git commit -m "Basic Rails Configuration"
51
109
  ```
52
110
 
53
- install Apache Age (you can ignore the `unknown OID` warnings)
111
+ ### install Apache Age Plugin
112
+
113
+ NOTE: _ignore the `unknown OID` warnings_
114
+
54
115
  ```bash
55
116
  bundle add rails_age
56
117
  bundle install
@@ -62,62 +123,52 @@ git add .
62
123
  git commit -m "Add & configure Apache Age within Rails"
63
124
  ```
64
125
 
65
- make some nodes :string is the default type
126
+ ### Optional Migration override (OPTIONAL)
127
+
128
+ run `bin/rails apache_age:override_db_migrate` to ensure that running `rails db:migrate` does not inappropriately modify the schema file.
129
+
130
+ However, if you are familiar with the schema file and git then you can safely ignore this step and manage the changes after a migration manually - only submitting changes directly related to the newest migration and not those related AGE.
131
+
132
+ **NOTE:**
133
+ * **You can run `bin/rails apache_age:config_schema` at any time to repair the schema file as needed.**
134
+ ( **You can run `bin/rails apache_age:install` at any time to repair any AGE related config file**
135
+
136
+ If you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge).
137
+
138
+ ### NODE Scaffold Generation
139
+
66
140
  ```bash
67
- rails generate apache_age:node Company company_name
68
- rails generate apache_age:node Person first_name last_name
69
- rails generate apache_age:node Pet pet_name:string age:integer
141
+ rails generate apache_age:scaffold_node Company company_name:string
142
+
143
+ # string is the default type (so it can be omitted)
144
+ rails generate apache_age:scaffold_node Person first_name last_name
145
+
146
+ # with a namespace
147
+ rails generate apache_age:scaffold_node Animals/Pet pet_name birthdate:date
70
148
  ```
71
- make some edges (`:vertex` is the default type) for start_node and end_node
149
+
150
+ ### EDGE Scaffold Generation**
151
+
152
+ NOTE: the generator will only allow `:node` (default type) for start_node and end_node, however, it is strongly recommended to specify the start_node and end_node types manually. _Hopefully, I can find a way to get the generators to recognize and allow the usage of custom node types. Thus eventually, I hope: `rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role` will work._
153
+
72
154
  ```bash
73
- # when start node and end node are not specified they are of type `:vertex`
74
- # this is generally not recommended - exept when very generic relationships are needed
75
155
  rails generate apache_age:edge HasJob employee_role begin_date:date
76
-
77
- # # this is recommended - (but not yet working) add explicit start_node and end_node types manually
78
- # rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role
79
156
  ```
80
157
 
81
- **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.**
82
-
83
- For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
158
+ _edge scaffold is coming soon._
84
159
 
85
- ### Rails Console Usage
160
+ ```bash
161
+ # without a namespace
162
+ rails generate apache_age:scaffold_edge HasPet caretaker_role
86
163
 
87
- ```ruby
88
- bin/rails c
89
-
90
- fred = Person.new(first_name: 'Fredrick Jay', last_name: 'Flintstone')
91
- fred.valid?
92
- fred.save
93
- fred.to_h # should have an ID
94
-
95
- # fails because of a missing required field (Property)
96
- incomplete = Person.new(first_name: 'Fredrick Jay')
97
- incomplete.valid?
98
- incomplete.errors
99
- incomplete.to_h
100
-
101
- # fails because of uniqueness constraints
102
- jay = Person.create(first_name: 'Fredrick Jay', last_name: 'Flintstone')
103
- jay.to_h
104
- => {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
105
- jay.valid?
106
- => false
107
- jay.errors
108
- => #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=record not unique, options={}>, #<ActiveModel::Error attribute=first_name, type=property combination not unique, options={}>, #<ActiveModel::Error attribute=last_name, type=property combination not unique, options={}>]>
109
- irb(main):008> jav.to_h
110
- => {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
111
-
112
- # .create is a shortcut for .new and .save
113
- quarry = Company.create(company_name: 'Bedrock Quarry')
114
- quarry.to_h # should have an ID
115
-
116
- # create an edge (no generator yet)
117
- job = HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
118
- job.to_h # should have an ID
164
+ # with a namespace
165
+ rails generate apache_age:scaffold_edge People/HasSpouse spousal_role
119
166
  ```
120
167
 
168
+ ### AGE Usage within Rails Console
169
+
170
+ see [AGE Usage within Rails Console](AGE_CONSOLE_USAGE.md)
171
+
121
172
  ## Manual Install, Config and Usage
122
173
 
123
174
  see [Manuel Installation, Configuration and Usage](MANUAL_INSTALL.md)
@@ -0,0 +1,20 @@
1
+ # config/initializers/types.rb
2
+
3
+ require 'apache_age/types/age_type_factory'
4
+ # USAGE (with edges or nodes) - ie:
5
+ # require_dependency 'nodes/company'
6
+ # ActiveModel::Type.register(
7
+ # :company, ApacheAge::Types::AgeTypeFactory.create_type_for(Nodes::Company)
8
+ # )
9
+
10
+ Rails.application.config.to_prepare do
11
+ # Register AGE types
12
+ require_dependency 'apache_age/node'
13
+ ActiveModel::Type.register(
14
+ :node, ApacheAge::Types::AgeTypeFactory.create_type_for(ApacheAge::Node)
15
+ )
16
+ require_dependency 'apache_age/edge'
17
+ ActiveModel::Type.register(
18
+ :edge, ApacheAge::Types::AgeTypeFactory.create_type_for(ApacheAge::Edge)
19
+ )
20
+ end
@@ -0,0 +1,5 @@
1
+ module ApacheAge
2
+ class Edge
3
+ include ApacheAge::Entities::Edge
4
+ end
5
+ end
@@ -7,6 +7,12 @@ module ApacheAge
7
7
  def persisted? = id.present?
8
8
  def to_s = ":#{age_label} #{properties_to_s}"
9
9
 
10
+ # default display value
11
+ def display
12
+ info = age_properties&.values&.first
13
+ info.blank? ? "#{age_label} (#{id})" : "#{info} (#{age_label})"
14
+ end
15
+
10
16
  def to_h
11
17
  base_h = attributes.to_hash
12
18
  if age_type == 'edge'
@@ -9,11 +9,12 @@ module ApacheAge
9
9
  include ActiveModel::Attributes
10
10
 
11
11
  attribute :id, :integer
12
+ # attribute :label, :string
12
13
  attribute :end_id, :integer
13
14
  attribute :start_id, :integer
14
- # type: `:vertex` can be overriden with a specific node type
15
- attribute :end_node, :vertex
16
- attribute :start_node, :vertex
15
+ # override with a specific node type in the defining class
16
+ attribute :end_node
17
+ attribute :start_node
17
18
 
18
19
  validates :end_node, :start_node, presence: true
19
20
  validate :validate_nodes
@@ -0,0 +1,53 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module 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
@@ -1,6 +1,6 @@
1
1
  module ApacheAge
2
2
  module Entities
3
- module Vertex
3
+ module Node
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
@@ -0,0 +1,36 @@
1
+ module ApacheAge
2
+ class Node
3
+ include ApacheAge::Entities::Node
4
+
5
+ attribute :label, :string
6
+ attribute :properties
7
+ # attribute :properties, :hash
8
+
9
+ # def display = [label, properties&.values&.first].compact.join(' - ')
10
+ def display
11
+ info = properties&.values&.first
12
+ info.blank? ? "#{label} (#{id})" : "#{info} (#{label})"
13
+ end
14
+
15
+ def self.all
16
+ all_nodes_sql = <<~SQL
17
+ SELECT *
18
+ FROM cypher('age_schema', $$
19
+ MATCH (node)
20
+ RETURN node
21
+ $$) as (node agtype);
22
+ SQL
23
+ age_results = ActiveRecord::Base.connection.execute(all_nodes_sql)
24
+ return [] if age_results.values.count.zero?
25
+
26
+ age_results.values.map do |result|
27
+ json_string = result.first.split('::').first
28
+ hash = JSON.parse(json_string)
29
+ attribs = hash.slice('id', 'label').symbolize_keys
30
+ attribs[:properties] = hash['properties'].symbolize_keys
31
+
32
+ new(**attribs)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # lib/apache_age/types/age_type_factory.rb
2
+ # Automatically generates ActiveModel::Type classes
3
+ # Dynamically builds this (as a concrete example):
4
+ # module ApacheAge
5
+ # module Types
6
+ # class CompanyType < ActiveModel::Type::Value
7
+ # def cast(value)
8
+ # case value
9
+ # when Nodes::Company
10
+ # value
11
+ # when Hash
12
+ # Nodes::Company.new(value)
13
+ # else
14
+ # nil
15
+ # end
16
+ # end
17
+ # def serialize(value)
18
+ # value.is_a?(Nodes::Company) ? value.attributes : nil
19
+ # end
20
+ # end
21
+ # end
22
+ # end
23
+ module ApacheAge
24
+ module Types
25
+ class AgeTypeFactory
26
+ def self.create_type_for(klass)
27
+ Class.new(ActiveModel::Type::Value) do
28
+ define_method(:cast) do |value|
29
+ case value
30
+ when klass
31
+ value
32
+ when Hash
33
+ klass.new(value)
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
39
+ define_method(:serialize) do |value|
40
+ value.is_a?(klass) ? value.attributes : nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module ApacheAge
2
+ module Validators
3
+ module ExpectedNodeType
4
+ def
5
+ # Register the AGE types vertex_attribute(attribute_name, type_symbol, klass)
6
+ attribute attribute_name, type_symbol
7
+
8
+ validate do
9
+ value = send(attribute_name)
10
+ unless value.is_a?(klass)
11
+ errors.add(attribute_name, "must be a #{klass.name}")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # lib/apache_age/validators/unique_node.rb
2
+
3
+ # Usage:
4
+ # validates_with(
5
+ # ApacheAge::Validators::UniqueNode,
6
+ # attributes: [:first_name, :last_name, :gender]
7
+ # )
8
+
9
+ module ApacheAge
10
+ module Validators
11
+ class UniqueNode < ActiveModel::Validator
12
+ def validate(record)
13
+ allowed_keys = record.age_properties.keys
14
+ attributes = options[:attributes]
15
+ return if attributes.blank?
16
+
17
+ record_attribs =
18
+ attributes
19
+ .map { |attr| [attr, record.send(attr)] }
20
+ .to_h.symbolize_keys
21
+ .slice(*allowed_keys)
22
+ query = record.class.find_by(record_attribs)
23
+
24
+ # if no match is found or if it finds itself, it's valid
25
+ return if query.blank? || (query.id == record.id)
26
+
27
+ record.errors.add(:base, 'record not unique')
28
+ attributes.each { record.errors.add(_1, 'property combination not unique') }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,6 +1,7 @@
1
1
  module ApacheAge
2
2
  module VertexTypeValidator
3
- def vertex_attribute(attribute_name, type_symbol, klass)
3
+ def
4
+ # Register the AGE typesvertex_attribute(attribute_name, type_symbol, klass)
4
5
  attribute attribute_name, type_symbol
5
6
 
6
7
  validate do
@@ -4,10 +4,10 @@ class <%= class_name %>
4
4
  <%- attributes_list.each do |attribute| -%>
5
5
  attribute :<%= attribute[:name] %>, :<%= attribute[:reference] || attribute[:type] %>
6
6
  <%- end -%>
7
- # recommendation for (start_node and end_node): change `:vertex` with the 'node' type
7
+ # recommendation for (start_node and end_node): change `:node` with the actual 'node' type
8
8
  # see `config/initializers/apache_age.rb` for the list of available node types
9
- attribute :start_node, :vertex
10
- attribute :end_node, :vertex
9
+ attribute :start_node
10
+ attribute :end_node
11
11
 
12
12
  <%- attributes_list.each do |attribute| -%>
13
13
  validates :<%= attribute[:name] %>, presence: true
@@ -3,12 +3,15 @@
3
3
  module ApacheAge
4
4
  module GeneratorEntityHelpers
5
5
  def generate_age_entity(age_type)
6
- template "#{age_type}.rb.tt", File.join(destination_root, "app/#{age_type}s", class_path, "#{file_name}.rb")
6
+ template(
7
+ "#{age_type}.rb.tt",
8
+ File.join(Rails.root, "app/#{age_type}s", class_path, "#{file_name}.rb")
9
+ )
7
10
  add_type_config
8
11
  end
9
12
 
10
13
  def destroy_age_entity(age_type)
11
- file_path = File.join(destination_root, "app/#{age_type}s", class_path, "#{file_name}.rb")
14
+ file_path = File.join(Rails.root, "app/#{age_type}s", class_path, "#{file_name}.rb")
12
15
  File.delete(file_path) if File.exist?(file_path)
13
16
  remove_type_config
14
17
  end
@@ -0,0 +1,16 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate apache_age:scaffold_edge HasPet careciver_role
6
+
7
+ This will create:
8
+ create app/edge/has_pet.rb
9
+ modified: config/initializers/types.rb
10
+ create app/controllers/has_pets_controller.rb
11
+ route resources :has_pets
12
+ create app/views/pets/index.html.erb
13
+ create app/views/pets/edit.html.erb
14
+ create app/views/pets/show.html.erb
15
+ create app/views/pets/new.html.erb
16
+ create app/views/pets/partial.html.erb
@@ -0,0 +1,67 @@
1
+ # lib/generators/apache_age/scaffold_edge/scaffold_edge_generator.rb
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/named_base'
5
+ require 'rails/generators/resource_helpers'
6
+
7
+ module ApacheAge
8
+ class ScaffoldEdgeGenerator < Rails::Generators::NamedBase
9
+ include Rails::Generators::ResourceHelpers
10
+
11
+ desc "Generates an edge, and its controller and views with the given attributes."
12
+
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
16
+
17
+ def create_model_file
18
+ invoke 'apache_age:edge', [name] + attributes.collect { |attr| "#{attr.name}:#{attr.type}" }
19
+ end
20
+
21
+ def create_controller_files
22
+ template(
23
+ "controller.rb.tt",
24
+ File.join(Rails.root, "app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
25
+ )
26
+ end
27
+
28
+ def create_route
29
+ route_content = route_text(class_path, file_name)
30
+ inject_into_file(
31
+ File.join(Rails.root, 'config', 'routes.rb'), "\n#{route_content}",
32
+ after: "Rails.application.routes.draw do"
33
+ )
34
+ end
35
+
36
+ def copy_view_files
37
+ available_views.each do |view|
38
+ view_name = view == 'partial' ? "_#{singular_table_name}" : view
39
+ filename = filename_with_extensions(view_name)
40
+ template(
41
+ "views/#{view}.html.erb.tt",
42
+ File.join(Rails.root, "app/views", controller_file_path, filename)
43
+ )
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def available_views
50
+ %w[index edit show new partial _form]
51
+ end
52
+
53
+ def filename_with_extensions(view)
54
+ [view, :html, :erb].compact.join('.')
55
+ end
56
+
57
+ def route_text(class_path, file_name)
58
+ return " resources :#{file_name.pluralize}" if class_path.empty?
59
+
60
+ <<-RUBY
61
+ namespace :#{class_path.join(':')} do
62
+ resources :#{file_name.pluralize}
63
+ end
64
+ RUBY
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,67 @@
1
+ # lib/generators/apache_age/scaffold_node/scaffold_node_generator.rb
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/named_base'
5
+ require 'rails/generators/resource_helpers'
6
+
7
+ module ApacheAge
8
+ class ScaffoldNodeGenerator < Rails::Generators::NamedBase
9
+ include Rails::Generators::ResourceHelpers
10
+
11
+ desc "Generates a node, and its controller and views with the given attributes."
12
+
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
16
+
17
+ def create_model_file
18
+ invoke 'apache_age:edge', [name] + attributes.collect { |attr| "#{attr.name}:#{attr.type}" }
19
+ end
20
+
21
+ def create_controller_files
22
+ template(
23
+ "controller.rb.tt",
24
+ File.join(Rails.root, "app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
25
+ )
26
+ end
27
+
28
+ def create_route
29
+ route_content = route_text(class_path, file_name)
30
+ inject_into_file(
31
+ File.join(Rails.root, 'config', 'routes.rb'), "\n#{route_content}",
32
+ after: "Rails.application.routes.draw do"
33
+ )
34
+ end
35
+
36
+ def copy_view_files
37
+ available_views.each do |view|
38
+ view_name = view == 'partial' ? "_#{singular_table_name}" : view
39
+ filename = filename_with_extensions(view_name)
40
+ template(
41
+ "views/#{view}.html.erb.tt",
42
+ File.join(Rails.root, "app/views", controller_file_path, filename)
43
+ )
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def available_views
50
+ %w[index edit show new partial _form]
51
+ end
52
+
53
+ def filename_with_extensions(view)
54
+ [view, :html, :erb].compact.join('.')
55
+ end
56
+
57
+ def route_text(class_path, file_name)
58
+ return " resources :#{file_name.pluralize}" if class_path.empty?
59
+
60
+ <<-RUBY
61
+ namespace :#{class_path.join(':')} do
62
+ resources :#{file_name.pluralize}
63
+ end
64
+ RUBY
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ class <%= controller_class_name %>Controller < ApplicationController
2
+ before_action :set_<%= singular_table_name %>, only: %i[show edit update destroy]
3
+
4
+ def index
5
+ @<%= plural_table_name %> = <%= class_name %>.all
6
+ end
7
+
8
+ def show
9
+ end
10
+
11
+ def new
12
+ @<%= singular_table_name %> = <%= class_name %>.new
13
+ end
14
+
15
+ def edit
16
+ end
17
+
18
+ def create
19
+ @<%= singular_table_name %> = <%= class_name %>.new(<%= singular_table_name %>_params)
20
+
21
+ if @<%= singular_table_name %>.save
22
+ redirect_to @<%= singular_table_name %>, notice: '<%= human_name %> was successfully created.'
23
+ else
24
+ render :new
25
+ end
26
+ end
27
+
28
+ def update
29
+ if @<%= singular_table_name %>.update(<%= singular_table_name %>_params)
30
+ redirect_to @<%= singular_table_name %>, notice: '<%= human_name %> was successfully updated.'
31
+ else
32
+ render :edit
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ @<%= singular_table_name %>.destroy
38
+ redirect_to <%= index_helper %>_url, notice: '<%= human_name %> was successfully destroyed.'
39
+ end
40
+
41
+ private
42
+
43
+ def set_<%= singular_table_name %>
44
+ @<%= singular_table_name %> = <%= class_name %>.find(params[:id])
45
+ end
46
+
47
+ def <%= singular_table_name %>_params
48
+ params.require(:<%= singular_table_name %>).permit(<%= (attributes + %i[start_id end_id]).map { |attr| ":#{attr.name}" }.join(', ') %>)
49
+ end
50
+ end
@@ -31,6 +31,16 @@
31
31
  </div>
32
32
 
33
33
  <% end -%>
34
+ <div>
35
+ <%%= form.label :start_node, style: "display: block" %>
36
+ <%%= form.collection_select(:start_id, ApacheAge::Node.all, :id, :display, prompt: 'Select a Start-Node') %>
37
+ </div>
38
+
39
+ <div>
40
+ <%%= form.label :end_node, style: "display: block" %>
41
+ <%%= form.collection_select(:end_id, ApacheAge::Node.all, :id, :display, prompt: 'Select an End-Node') %>
42
+ </div>
43
+
34
44
  <div>
35
45
  <%%= form.submit %>
36
46
  </div>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div id="<%= plural_table_name %>">
8
8
  <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
9
- <%%= render <%= singular_table_name %> %>
9
+ <%%= render '<%= singular_table_name %>', <%= singular_table_name %>: <%= singular_table_name %> %>
10
10
  <p>
11
11
  <%%= link_to "Show this <%= human_name.downcase %>", <%= model_resource_name(singular_table_name) %> %>
12
12
  </p>
@@ -5,8 +5,8 @@
5
5
  <% if attribute.attachment? -%>
6
6
  <%%= link_to <%= singular_table_name %>.<%= attribute.column_name %>.filename, <%= singular_table_name %>.<%= attribute.column_name %> if <%= singular_table_name %>.<%= attribute.column_name %>.attached? %>
7
7
  <% elsif attribute.attachments? -%>
8
- <%% <%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_table_name %>| %>
9
- <div><%%= link_to <%= attribute.singular_table_name %>.filename, <%= attribute.singular_table_name %> %></div>
8
+ <%% <%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
9
+ <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
10
10
  <%% end %>
11
11
  <% else -%>
12
12
  <%%= <%= singular_table_name %>.<%= attribute.column_name %> %>
@@ -14,4 +14,13 @@
14
14
  </p>
15
15
 
16
16
  <% end -%>
17
+ <p>
18
+ <strong>Start-Node:</strong>
19
+ <%%= <%= singular_table_name %>.start_node.display %>
20
+ </p>
21
+
22
+ <p>
23
+ <strong>End-Node:</strong>
24
+ <%%= <%= singular_table_name %>.end_node.display %>
25
+ </p>
17
26
  </div>
@@ -1,6 +1,6 @@
1
1
  <p style="color: green"><%%= notice %></p>
2
2
 
3
- <%%= render @<%= singular_table_name %> %>
3
+ <%%= render '<%= singular_table_name %>', <%= singular_table_name %>: @<%= singular_table_name %> %>
4
4
 
5
5
  <div>
6
6
  <%%= link_to "Edit this <%= human_name.downcase %>", <%= edit_helper(type: :path) %> %> |
@@ -4,14 +4,9 @@ require 'rails/generators'
4
4
  require 'rails/generators/named_base'
5
5
  require 'rails/generators/resource_helpers'
6
6
 
7
- require_relative '../generator_entity_helpers'
8
- require_relative '../generator_resource_helpers'
9
-
10
7
  module ApacheAge
11
8
  class ScaffoldNodeGenerator < Rails::Generators::NamedBase
12
9
  include Rails::Generators::ResourceHelpers
13
- # include ApacheAge::GeneratorEntityHelpers
14
- # include ApacheAge::GeneratorResourceHelpers
15
10
 
16
11
  desc "Generates a node, and its controller and views with the given attributes."
17
12
 
@@ -25,24 +20,27 @@ module ApacheAge
25
20
 
26
21
  def create_controller_files
27
22
  template(
28
- "node_controller.rb.tt",
29
- File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
23
+ "controller.rb.tt",
24
+ File.join(Rails.root, "app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
30
25
  )
31
26
  end
32
27
 
33
28
  def create_route
34
- if class_path.empty?
35
- route "resources :#{file_name.pluralize}"
36
- else
37
- route nested_route(class_path, file_name)
38
- end
29
+ route_content = route_text(class_path, file_name)
30
+ inject_into_file(
31
+ File.join(Rails.root, 'config', 'routes.rb'), "\n#{route_content}",
32
+ after: "Rails.application.routes.draw do"
33
+ )
39
34
  end
40
35
 
41
36
  def copy_view_files
42
37
  available_views.each do |view|
43
38
  view_name = view == 'partial' ? "_#{singular_table_name}" : view
44
39
  filename = filename_with_extensions(view_name)
45
- template "views/#{view}.html.erb.tt", File.join("app/views", controller_file_path, filename)
40
+ template(
41
+ "views/#{view}.html.erb.tt",
42
+ File.join(Rails.root, "app/views", controller_file_path, filename)
43
+ )
46
44
  end
47
45
  end
48
46
 
@@ -56,13 +54,14 @@ module ApacheAge
56
54
  [view, :html, :erb].compact.join('.')
57
55
  end
58
56
 
59
- def nested_route(class_path, file_name)
60
- # "namespace :#{class_path.join(':')} do\n resources :#{file_name.pluralize}\nend"
61
- <<~RUBY
62
- namespace :#{class_path.join(':')} do
63
- resources :#{file_name.pluralize}
64
- end
65
- RUBY
57
+ def route_text(class_path, file_name)
58
+ return " resources :#{file_name.pluralize}" if class_path.empty?
59
+
60
+ <<-RUBY
61
+ namespace :#{class_path.join(':')} do
62
+ resources :#{file_name.pluralize}
63
+ end
64
+ RUBY
66
65
  end
67
66
  end
68
67
  end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.5.2'
2
+ VERSION = '0.5.3'
3
3
  end
data/lib/rails_age.rb CHANGED
@@ -8,10 +8,16 @@ end
8
8
  module ApacheAge
9
9
  require 'apache_age/entities/class_methods'
10
10
  require 'apache_age/entities/common_methods'
11
- require 'apache_age/entities/edge'
12
11
  require 'apache_age/entities/entity'
13
12
  require 'apache_age/entities/vertex'
13
+ require 'apache_age/entities/node'
14
+ require 'apache_age/entities/edge'
15
+ require 'apache_age/node'
16
+ require 'apache_age/edge'
17
+ require 'apache_age/validators/expected_node_type'
18
+ require 'apache_age/validators/unique_node'
14
19
  require 'apache_age/validators/unique_edge'
15
20
  require 'apache_age/validators/unique_vertex'
21
+ require 'apache_age/types/age_type_factory'
16
22
  require 'apache_age/types/age_type_generator'
17
23
  end
@@ -17,16 +17,16 @@ namespace :apache_age do
17
17
  RUBY
18
18
  node_type_content =
19
19
  <<-RUBY
20
- require_dependency 'apache_age/entities/vertex'
20
+ require_dependency 'apache_age/node'
21
21
  ActiveModel::Type.register(
22
- :vertex, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Vertex)
22
+ :node, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Node)
23
23
  )
24
24
  RUBY
25
25
  edge_type_content =
26
26
  <<-RUBY
27
- require_dependency 'apache_age/entities/edge'
27
+ require_dependency 'apache_age/edge'
28
28
  ActiveModel::Type.register(
29
- :edge, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Edge)
29
+ :edge, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Edge)
30
30
  )
31
31
  RUBY
32
32
 
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.5.2
4
+ version: 0.5.3
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-16 00:00:00.000000000 Z
11
+ date: 2024-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -44,6 +44,34 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '6.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: capybara
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.4'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.4'
61
+ - !ruby/object:Gem::Dependency
62
+ name: selenium-webdriver
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
47
75
  description: This plugin integrates Apache AGE for PostgreSQL with Rails 7.x, providing
48
76
  tools and helpers for working with graph databases within a Rails application.
49
77
  email:
@@ -64,18 +92,25 @@ files:
64
92
  - app/mailers/rails_age/application_mailer.rb
65
93
  - app/models/rails_age/application_record.rb
66
94
  - app/views/layouts/rails_age/application.html.erb
95
+ - config/initializers/types.rb
67
96
  - config/routes.rb
68
97
  - db/migrate/20240521062349_add_apache_age.rb
69
98
  - db/schema.rb
99
+ - lib/apache_age/edge.rb
70
100
  - lib/apache_age/entities/class_methods.rb
71
101
  - lib/apache_age/entities/common_methods.rb
72
102
  - lib/apache_age/entities/edge.rb
73
103
  - lib/apache_age/entities/entity.rb
104
+ - lib/apache_age/entities/node.rb
74
105
  - lib/apache_age/entities/vertex.rb
106
+ - lib/apache_age/node.rb
107
+ - lib/apache_age/types/age_type_factory.rb
75
108
  - lib/apache_age/types/age_type_generator.rb
109
+ - lib/apache_age/validators/expected_node_type.rb
76
110
  - lib/apache_age/validators/unique_edge.rb
111
+ - lib/apache_age/validators/unique_node.rb
77
112
  - lib/apache_age/validators/unique_vertex.rb
78
- - lib/apache_age/validators/vertex_type_validator.rb
113
+ - lib/apache_age/validators/vertex_type_validator copy.rb
79
114
  - lib/generators/apache_age/edge/USAGE
80
115
  - lib/generators/apache_age/edge/edge_generator.rb
81
116
  - lib/generators/apache_age/edge/templates/edge.rb.tt
@@ -84,20 +119,24 @@ files:
84
119
  - lib/generators/apache_age/node/USAGE
85
120
  - lib/generators/apache_age/node/node_generator.rb
86
121
  - lib/generators/apache_age/node/templates/node.rb.tt
122
+ - lib/generators/apache_age/scaffold_edge/USAGE
123
+ - lib/generators/apache_age/scaffold_edge/scaffold_edge_generator.rb
124
+ - lib/generators/apache_age/scaffold_edge/scaffold_node_generator.rb
125
+ - lib/generators/apache_age/scaffold_edge/templates/controller.rb.tt
126
+ - lib/generators/apache_age/scaffold_edge/templates/views/_form.html.erb.tt
127
+ - lib/generators/apache_age/scaffold_edge/templates/views/edit.html.erb.tt
128
+ - lib/generators/apache_age/scaffold_edge/templates/views/index.html.erb.tt
129
+ - lib/generators/apache_age/scaffold_edge/templates/views/new.html.erb.tt
130
+ - lib/generators/apache_age/scaffold_edge/templates/views/partial.html.erb.tt
131
+ - lib/generators/apache_age/scaffold_edge/templates/views/show.html.erb.tt
87
132
  - lib/generators/apache_age/scaffold_node/USAGE
88
133
  - lib/generators/apache_age/scaffold_node/scaffold_node_generator.rb
89
- - lib/generators/apache_age/scaffold_node/templates/node_controller.rb.tt
90
- - lib/generators/apache_age/scaffold_node/templates/views/_form.html.erb copy.tt
134
+ - lib/generators/apache_age/scaffold_node/templates/controller.rb.tt
91
135
  - lib/generators/apache_age/scaffold_node/templates/views/_form.html.erb.tt
92
- - lib/generators/apache_age/scaffold_node/templates/views/edit.html.erb copy.tt
93
136
  - lib/generators/apache_age/scaffold_node/templates/views/edit.html.erb.tt
94
- - lib/generators/apache_age/scaffold_node/templates/views/index.html.erb copy.tt
95
137
  - lib/generators/apache_age/scaffold_node/templates/views/index.html.erb.tt
96
- - lib/generators/apache_age/scaffold_node/templates/views/new.html.erb copy.tt
97
138
  - lib/generators/apache_age/scaffold_node/templates/views/new.html.erb.tt
98
- - lib/generators/apache_age/scaffold_node/templates/views/partial.html.erb copy.tt
99
139
  - lib/generators/apache_age/scaffold_node/templates/views/partial.html.erb.tt
100
- - lib/generators/apache_age/scaffold_node/templates/views/show.html.erb copy.tt
101
140
  - lib/generators/apache_age/scaffold_node/templates/views/show.html.erb.tt
102
141
  - lib/rails_age.rb
103
142
  - lib/rails_age/engine.rb