rails_age 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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