rails_age 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -7
- data/README.md +45 -8
- data/db/schema.rb +1 -1
- data/lib/generators/apache_age/edge/USAGE +54 -0
- data/lib/generators/apache_age/edge/edge_generator.rb +56 -0
- data/lib/generators/apache_age/edge/templates/edge.rb.tt +20 -0
- data/lib/generators/apache_age/node/USAGE +54 -0
- data/lib/generators/apache_age/node/node_generator.rb +56 -0
- data/lib/generators/apache_age/node/templates/node.rb.tt +17 -0
- data/lib/rails_age/version.rb +1 -1
- data/lib/tasks/copy_migrations.rake +33 -0
- data/lib/tasks/database_config.rake +99 -0
- data/lib/tasks/install.original.rake +257 -0
- data/lib/tasks/install.rake +8 -199
- data/lib/tasks/schema_config.rake +95 -0
- metadata +12 -3
- data/lib/tasks/rails_age_tasks.rake +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc953f22e786b1ce4280d8ced0bcb6ca768f89773c49a4ad0d7ddd3e3505e91e
|
4
|
+
data.tar.gz: fff1d2809ce6f439196bfbc8a50b7990453d17def541829ef332ee2711a7fd3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e03cb391c29274953ab63badc3da19a9a965d53b23844086775d5668e61f32feba68605078b506c2a1ef8bde9d6627c67c087b7b1752acd2ccff681a55d4eec2
|
7
|
+
data.tar.gz: 6373605ea72f6d85babde6ba167135f3098bb6b04c60a56015a04670e48f40e452a2473ce974de2e5577d2930d267aff72e253fc0a1fe10f740def2b28c0cf1b
|
data/CHANGELOG.md
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
## VERSION 0.4.0 - 2024-xx-xx
|
4
4
|
|
5
|
-
- **Edges**
|
6
|
-
* `find_edge` is deprecated - use `find_by` with :start_node, :end_node to find an edge with specific nodes
|
7
5
|
- **cypher**
|
8
6
|
* query support
|
9
7
|
* paths support
|
@@ -11,16 +9,38 @@
|
|
11
9
|
- **Paths**
|
12
10
|
* ?
|
13
11
|
|
14
|
-
## VERSION 0.3.
|
12
|
+
## VERSION 0.3.4 - 2024-xx-xx
|
13
|
+
|
14
|
+
- **Type Generators**
|
15
|
+
* add `rails generate apache_age:type`
|
16
|
+
* add `rails generate apache_age:node_type`
|
17
|
+
* add `rails generate apache_age:edge_type`
|
18
|
+
|
19
|
+
## VERSION 0.3.3 - 2024-xx-xx
|
20
|
+
|
21
|
+
- **Edge Generator**
|
22
|
+
* add `rails generate apache_age:edge` to create an edge model
|
23
|
+
|
24
|
+
|
25
|
+
## VERSION 0.3.2 - 2024-06-08
|
26
|
+
|
27
|
+
- **Node Generator**
|
28
|
+
* 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`
|
29
|
+
* add `rails generate apache_age:node Cat name age:integer` creates a node with attributes at: `app/nodes/cat.rb`
|
30
|
+
* add `rails destroy apache_age:node Cat` deletes an existing node at: `app/nodes/cat.rb`
|
31
|
+
|
32
|
+
## VERSION 0.3.1 - 2024-06-02
|
15
33
|
|
16
|
-
- **Genetator**
|
17
|
-
* add `rails generate apache_age:node` to create a node model (with its type in initializer)
|
18
|
-
* add `rails generate apache_age:edge` to create an edge model (with its type in initializer)
|
19
34
|
- **Installer**
|
20
|
-
*
|
35
|
+
* refactor into multiple independent tasks with tests
|
36
|
+
- **Documentation**
|
37
|
+
* updated README with additional information
|
38
|
+
* added `db/structure.sql` config to README
|
21
39
|
|
22
40
|
## VERSION 0.3.0 - 2024-05-28
|
23
41
|
|
42
|
+
- **Edges**
|
43
|
+
* `find_by(start_node:, :end_node:, properties:)` to find an edge with specific nodes & properties (deprecated `find_edge`)
|
24
44
|
- **Installer** (`rails generate apache_age:install`)
|
25
45
|
* copy Age PG Extenstion migration to `db/migrate`
|
26
46
|
* run the AGE PG Migration
|
data/README.md
CHANGED
@@ -14,14 +14,22 @@ gem "rails_age"
|
|
14
14
|
|
15
15
|
### Quick Install
|
16
16
|
|
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
|
+
|
17
19
|
```bash
|
18
20
|
$ bundle
|
19
21
|
$ bin/rails apache_age:install
|
20
22
|
$ git add .
|
21
23
|
$ git commit -m "Add Apache Age to Rails"
|
24
|
+
$ rails generate apache_age:node Company company_name
|
25
|
+
$ rails generate apache_age:node Person first_name last_name age:integer
|
22
26
|
```
|
23
27
|
|
24
|
-
NOTE: it is important to
|
28
|
+
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`).
|
29
|
+
|
30
|
+
**You can run `bin/rails apache_age:install` at any time to repair the schema file as needed.**
|
31
|
+
|
32
|
+
For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
|
25
33
|
|
26
34
|
### Manual Install
|
27
35
|
|
@@ -76,6 +84,7 @@ $ bin/rails db:migrate
|
|
76
84
|
|
77
85
|
Rails migrate will mangle the schema `db/schema.rb` file. You need to remove the lines that look like:
|
78
86
|
```ruby
|
87
|
+
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
79
88
|
create_schema "ag_catalog"
|
80
89
|
create_schema "age_schema"
|
81
90
|
|
@@ -99,12 +108,14 @@ Rails migrate will mangle the schema `db/schema.rb` file. You need to remove th
|
|
99
108
|
|
100
109
|
# other migrations
|
101
110
|
# ...
|
111
|
+
end
|
102
112
|
```
|
103
113
|
|
104
114
|
and replace them with the following lines:
|
105
115
|
```ruby
|
116
|
+
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
106
117
|
# These are extensions that must be enabled in order to support this database
|
107
|
-
enable_extension
|
118
|
+
enable_extension 'plpgsql'
|
108
119
|
|
109
120
|
# Allow age extension
|
110
121
|
execute('CREATE EXTENSION IF NOT EXISTS age;')
|
@@ -120,14 +131,38 @@ and replace them with the following lines:
|
|
120
131
|
|
121
132
|
# other migrations
|
122
133
|
# ...
|
134
|
+
end
|
123
135
|
```
|
124
136
|
|
125
|
-
NOTE:
|
126
|
-
|
137
|
+
NOTE: if using `db/structure.sql` use:
|
138
|
+
```sql
|
139
|
+
-- These are extensions that must be enabled in order to support this database
|
140
|
+
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA public;
|
141
|
+
|
142
|
+
-- Allow age extension (if not already enabled), this builds the age_catalog schema
|
143
|
+
CREATE EXTENSION IF NOT EXISTS age;
|
144
|
+
|
145
|
+
-- Load the age module
|
146
|
+
LOAD 'age';
|
147
|
+
|
148
|
+
-- Load the ag_catalog into the search path
|
149
|
+
SET search_path = ag_catalog, "$user", public;
|
150
|
+
|
151
|
+
-- Create age_schema graph if it doesn't exist
|
152
|
+
SELECT create_graph('age_schema');
|
153
|
+
|
154
|
+
# other migrations
|
155
|
+
# ...
|
156
|
+
|
157
|
+
INSERT INTO "schema_migrations" (version) VALUES
|
158
|
+
('20110315075839'),
|
159
|
+
--- ...
|
160
|
+
('20240521062349');
|
161
|
+
```
|
127
162
|
|
128
163
|
## Contributing
|
129
164
|
|
130
|
-
Create an
|
165
|
+
Create an merge request (with tests) and I will review it/merge it when ready.
|
131
166
|
|
132
167
|
## License
|
133
168
|
|
@@ -135,9 +170,12 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
135
170
|
|
136
171
|
## Usage
|
137
172
|
|
173
|
+
I suggest you creat a folder create a folder called `app/nodes` and `app/edges` to keep the code organized.
|
174
|
+
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))
|
175
|
+
|
138
176
|
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.
|
139
177
|
|
140
|
-
A
|
178
|
+
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.
|
141
179
|
|
142
180
|
### Nodes
|
143
181
|
|
@@ -168,9 +206,8 @@ module Nodes
|
|
168
206
|
attribute :last_name, :string, default: nil
|
169
207
|
attribute :given_name, :string, default: nil
|
170
208
|
attribute :nick_name, :string, default: nil
|
171
|
-
attribute :gender, :string, default: nil
|
172
209
|
|
173
|
-
validates :
|
210
|
+
validates :first_name, :last_name, :given_name, :nick_name,
|
174
211
|
presence: true
|
175
212
|
|
176
213
|
def initialize(**attributes)
|
data/db/schema.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
2
2
|
# These are extensions that must be enabled in order to support this database
|
3
|
-
enable_extension
|
3
|
+
enable_extension 'plpgsql'
|
4
4
|
|
5
5
|
# Allow age extension
|
6
6
|
execute('CREATE EXTENSION IF NOT EXISTS age;')
|
@@ -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
|
+
```
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module ApacheAge
|
5
|
+
class EdgeGenerator < Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
8
|
+
|
9
|
+
def perform_task
|
10
|
+
behavior == :invoke ? create_edge_file : destroy_edge_file
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def create_edge_file
|
16
|
+
template "edge.rb.tt", File.join("app/edge", class_path, "#{file_name}.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_edge_file
|
20
|
+
file_path = File.join("app/edge", class_path, "#{file_name}.rb")
|
21
|
+
File.delete(file_path) if File.exist?(file_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes_list
|
25
|
+
attributes.map { |attr| { name: attr.name, type: attr.type } }
|
26
|
+
end
|
27
|
+
|
28
|
+
def unique_attributes
|
29
|
+
attributes_list.map { |attr| attr[:name].to_sym }
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent_module
|
33
|
+
class_path.map(&:camelize).join('::')
|
34
|
+
end
|
35
|
+
|
36
|
+
def full_class_name
|
37
|
+
parent_module.empty? ? class_name : "#{parent_module}::#{class_name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def indented_namespace
|
41
|
+
return '' if parent_module.empty?
|
42
|
+
|
43
|
+
parent_module.split('::').map.with_index do |namespace, index|
|
44
|
+
"#{' ' * index}module #{namespace}"
|
45
|
+
end.join("\n") + "\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def indented_end_namespace
|
49
|
+
return '' if parent_module.empty?
|
50
|
+
|
51
|
+
parent_module.split('::').map.with_index do |_, index|
|
52
|
+
"#{' ' * (parent_module.split('::').length - 1 - index)}end"
|
53
|
+
end.join("\n") + "\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= indented_namespace %>
|
2
|
+
<%= ' ' * class_path.length %>class <%= class_name %>
|
3
|
+
<%= ' ' * class_path.length %> include ApacheAge::Entities::Edge
|
4
|
+
|
5
|
+
<%- attributes_list.each do |attribute| -%>
|
6
|
+
<%= ' ' * class_path.length %> attribute :<%= attribute[:name] %>, :<%= attribute[:reference] || attribute[:type] %>
|
7
|
+
<%- end -%>
|
8
|
+
|
9
|
+
<%= ' ' * class_path.length %> validates :<%= unique_attributes.first %>, presence: true
|
10
|
+
<%= ' ' * class_path.length %> validate :validate_unique
|
11
|
+
|
12
|
+
<%= ' ' * class_path.length %> private
|
13
|
+
|
14
|
+
<%= ' ' * class_path.length %> def validate_unique
|
15
|
+
<%= ' ' * class_path.length %> ApacheAge::Validators::UniqueEdgeValidator
|
16
|
+
<%= ' ' * class_path.length %> .new(attributes: <%= unique_attributes.inspect %>)
|
17
|
+
<%= ' ' * class_path.length %> .validate(self)
|
18
|
+
<%= ' ' * class_path.length %> end
|
19
|
+
<%= ' ' * class_path.length %>end
|
20
|
+
<%= indented_end_namespace %>
|
@@ -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
|
+
```
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module ApacheAge
|
5
|
+
class NodeGenerator < Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
8
|
+
|
9
|
+
def perform_task
|
10
|
+
behavior == :invoke ? create_node_file : destroy_node_file
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def create_node_file
|
16
|
+
template "node.rb.tt", File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy_node_file
|
20
|
+
file_path = File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
|
21
|
+
File.delete(file_path) if File.exist?(file_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes_list
|
25
|
+
attributes.map { |attr| { name: attr.name, type: attr.type } }
|
26
|
+
end
|
27
|
+
|
28
|
+
def unique_attributes
|
29
|
+
attributes_list.map { |attr| attr[:name].to_sym }
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent_module
|
33
|
+
class_path.map(&:camelize).join('::')
|
34
|
+
end
|
35
|
+
|
36
|
+
def full_class_name
|
37
|
+
parent_module.empty? ? class_name : "#{parent_module}::#{class_name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def indented_namespace
|
41
|
+
return '' if parent_module.empty?
|
42
|
+
|
43
|
+
parent_module.split('::').map.with_index do |namespace, index|
|
44
|
+
"#{' ' * index}module #{namespace}"
|
45
|
+
end.join("\n") + "\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def indented_end_namespace
|
49
|
+
return '' if parent_module.empty?
|
50
|
+
|
51
|
+
parent_module.split('::').map.with_index do |_, index|
|
52
|
+
"#{' ' * (parent_module.split('::').length - 1 - index)}end"
|
53
|
+
end.join("\n") + "\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class <%= class_name %>
|
2
|
+
include ApacheAge::Entities::Vertex
|
3
|
+
|
4
|
+
<%- attributes_list.each do |attribute| -%>
|
5
|
+
attribute :<%= attribute[:name] %>, :<%= attribute[:type] %>
|
6
|
+
<%- end -%>
|
7
|
+
|
8
|
+
<%- attributes_list.each do |attribute| -%>
|
9
|
+
validates :<%= attribute[:name] %>, presence: true
|
10
|
+
<%- end -%>
|
11
|
+
|
12
|
+
# unique node validator (remove any attributes that are not important to uniqueness)
|
13
|
+
validates_with(
|
14
|
+
ApacheAge::Validators::UniqueVertexValidator,
|
15
|
+
attributes: <%= unique_attributes.inspect %>
|
16
|
+
)
|
17
|
+
end
|
data/lib/rails_age/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage:
|
3
|
+
# * `bin/rails apache_age:copy_migrations`
|
4
|
+
# * `bundle exec rails apache_age:copy_migrations[destination_path.to_s]`
|
5
|
+
# * `bundle exec rails apache_age:copy_migrations.invoke(destination_path.to_s)`
|
6
|
+
namespace :apache_age do
|
7
|
+
desc "Copy migrations from rails_age to application and update schema"
|
8
|
+
task :copy_migrations, [:destination_path] => :environment do |t, args|
|
9
|
+
source = File.expand_path('../../../db/migrate', __FILE__)
|
10
|
+
destination_path =
|
11
|
+
File.expand_path(args[:destination_path].presence || "#{Rails.root}/db/migrate", __FILE__)
|
12
|
+
|
13
|
+
FileUtils.mkdir_p(destination_path) unless File.exist?(destination_path)
|
14
|
+
existing_migrations =
|
15
|
+
Dir.glob("#{destination_path}/*.rb").map { |file| File.basename(file).sub(/^\d+/, '') }
|
16
|
+
|
17
|
+
Dir.glob("#{source}/*.rb").each do |file|
|
18
|
+
filename = File.basename(file)
|
19
|
+
test_name = filename.sub(/^\d+/, '')
|
20
|
+
|
21
|
+
if existing_migrations.include?(test_name)
|
22
|
+
puts "Skipping migration: '#{filename}', it already exists"
|
23
|
+
else
|
24
|
+
migration_version = Time.now.utc.strftime("%Y_%m_%d_%H%M%S")
|
25
|
+
file_version = migration_version.delete('_')
|
26
|
+
new_filename = filename.sub(/^\d+/, file_version)
|
27
|
+
destination_file = File.join(destination_path, new_filename)
|
28
|
+
FileUtils.cp(file, destination_file)
|
29
|
+
puts "Created migration: '#{new_filename}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage: `rake apache_age:copy_migrations`
|
3
|
+
#
|
4
|
+
namespace :apache_age do
|
5
|
+
desc "Ensure the database.yml file is properly configured for Apache Age"
|
6
|
+
task :database_config => :environment do
|
7
|
+
|
8
|
+
db_config_file = File.expand_path("#{Rails.root}/config/database.yml", __FILE__)
|
9
|
+
|
10
|
+
# Read the file
|
11
|
+
lines = File.readlines(db_config_file)
|
12
|
+
|
13
|
+
# any uncommented "schema_search_path:" lines?
|
14
|
+
path_index = lines.find_index { |line| !line.include?('#') && line.include?('schema_search_path:') }
|
15
|
+
default_start_index = lines.index { |line| line.strip.start_with?('default:') }
|
16
|
+
|
17
|
+
# when it finds an existing schema_search_path, it updates it
|
18
|
+
if path_index && lines[path_index].include?('ag_catalog,age_schema')
|
19
|
+
puts "the schema_search_path in config/database.yml is already properly set, nothing to do."
|
20
|
+
else
|
21
|
+
if path_index
|
22
|
+
key, val = lines[path_index].split(': ')
|
23
|
+
# remove any unwanted characters
|
24
|
+
val = val.gsub(/[ "\s\"\"'\n]/, '')
|
25
|
+
lines[path_index] = "#{key}: ag_catalog,age_schema,#{val}\n"
|
26
|
+
puts "added ag_catalog,age_schema to schema_search_path in config/database.yml"
|
27
|
+
elsif default_start_index
|
28
|
+
puts "the schema_search_path in config/database.yml is now properly set."
|
29
|
+
sections_index = lines.map.with_index { |line, index| index if !line.start_with?(' ') }.compact.sort
|
30
|
+
|
31
|
+
# find the start of the default section
|
32
|
+
next_section_in_list = sections_index.index(default_start_index) + 1
|
33
|
+
|
34
|
+
# find the end of the default section (before the next section starts)
|
35
|
+
path_insert_index = sections_index[next_section_in_list]
|
36
|
+
|
37
|
+
lines.insert(path_insert_index, " schema_search_path: ag_catalog,age_schema,public\n")
|
38
|
+
else
|
39
|
+
puts "didn't find a default section in database.yml, please add the following line:"
|
40
|
+
puts " schema_search_path: ag_catalog,age_schema,public"
|
41
|
+
puts "to the apprpriate section of your database.yml"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Write the modified lines back to the file
|
45
|
+
File.open(db_config_file, 'w') { |file| file.write(lines.join) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# # lib/tasks/install.rake
|
51
|
+
# # Usage: `rake apache_age:copy_migrations`
|
52
|
+
# #
|
53
|
+
# namespace :apache_age do
|
54
|
+
# desc "Ensure the database.yml file is properly configured for Apache Age"
|
55
|
+
# task :database_config, [:destination_path] => :environment do |t, args|
|
56
|
+
# destination_path =
|
57
|
+
# File.expand_path(args[:destination_path].presence || "#{Rails.root}/config", __FILE__)
|
58
|
+
|
59
|
+
# db_config_file = File.expand_path("#{destination_path.to_s}/database.yml", __FILE__)
|
60
|
+
|
61
|
+
# # Read the file
|
62
|
+
# lines = File.readlines(db_config_file)
|
63
|
+
|
64
|
+
# # any uncommented "schema_search_path:" lines?
|
65
|
+
# path_index = lines.find_index { |line| !line.include?('#') && line.include?('schema_search_path:') }
|
66
|
+
# default_start_index = lines.index { |line| line.strip.start_with?('default:') }
|
67
|
+
|
68
|
+
# # when it finds an existing schema_search_path, it updates it
|
69
|
+
# if path_index && lines[path_index].include?('ag_catalog,age_schema')
|
70
|
+
# puts "the schema_search_path in config/database.yml is already properly set, nothing to do."
|
71
|
+
# else
|
72
|
+
# if path_index
|
73
|
+
# key, val = lines[path_index].split(': ')
|
74
|
+
# # remove any unwanted characters
|
75
|
+
# val = val.gsub(/[ "\s\"\"'\n]/, '')
|
76
|
+
# lines[path_index] = "#{key}: ag_catalog,age_schema,#{val}\n"
|
77
|
+
# puts "added ag_catalog,age_schema to schema_search_path in config/database.yml"
|
78
|
+
# elsif default_start_index
|
79
|
+
# puts "the schema_search_path in config/database.yml is now properly set."
|
80
|
+
# sections_index = lines.map.with_index { |line, index| index if !line.start_with?(' ') }.compact.sort
|
81
|
+
|
82
|
+
# # find the start of the default section
|
83
|
+
# next_section_in_list = sections_index.index(default_start_index) + 1
|
84
|
+
|
85
|
+
# # find the end of the default section (before the next section starts)
|
86
|
+
# path_insert_index = sections_index[next_section_in_list]
|
87
|
+
|
88
|
+
# lines.insert(path_insert_index, " schema_search_path: ag_catalog,age_schema,public\n")
|
89
|
+
# else
|
90
|
+
# puts "didn't find a default section in database.yml, please add the following line:"
|
91
|
+
# puts " schema_search_path: ag_catalog,age_schema,public"
|
92
|
+
# puts "to the apprpriate section of your database.yml"
|
93
|
+
# end
|
94
|
+
|
95
|
+
# # Write the modified lines back to the file
|
96
|
+
# File.open(db_config_file, 'w') { |file| file.write(lines.join) }
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
# end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage: `rake apache_age:install`
|
3
|
+
#
|
4
|
+
namespace :apache_age do
|
5
|
+
desc "Install & configure Apache Age within Rails (updates migrations, schema & database.yml)"
|
6
|
+
task :install_old => :environment do
|
7
|
+
source_schema = File.expand_path('../../../db/schema.rb', __FILE__)
|
8
|
+
destination_schema = File.expand_path("#{Rails.root}/db/schema.rb", __FILE__)
|
9
|
+
source_migrations = File.expand_path('../../../db/migrate', __FILE__)
|
10
|
+
destination_migrations = File.expand_path("#{Rails.root}/db/migrate", __FILE__)
|
11
|
+
# create the migrations folder if needed
|
12
|
+
FileUtils.mkdir_p(destination_migrations) unless File.exist?(destination_migrations)
|
13
|
+
original_migrations =
|
14
|
+
Dir.glob("#{destination_migrations}/*.rb").map { |file| File.basename(file).sub(/^\d+/, '') }
|
15
|
+
|
16
|
+
# # check if the schema is non-existent or blank (NEW) we need to know how to handle schema
|
17
|
+
# is_schema_blank = !File.exist?(destination_schema) || blank_schema?(destination_schema)
|
18
|
+
# puts "Schema is blank: #{is_schema_blank}"
|
19
|
+
|
20
|
+
# ensure we have a schema file
|
21
|
+
unless File.exist?(destination_schema)
|
22
|
+
run_db_create
|
23
|
+
run_db_migrate
|
24
|
+
end
|
25
|
+
|
26
|
+
# copy our migrations to the application (last_migration_version is nil if no migration necessary)
|
27
|
+
# last_migration_version = copy_migrations
|
28
|
+
Rake::Task["apache_age:copy_migrations"].invoke
|
29
|
+
|
30
|
+
updated_migrations =
|
31
|
+
Dir.glob("#{destination_migrations}/*.rb").map { |file| File.basename(file).sub(/^\d+/, '') }
|
32
|
+
|
33
|
+
# # run our new migrations (unless we have not added any new migrations)
|
34
|
+
# if original_migrations == updated_migrations
|
35
|
+
# puts "no new migrations were copied, skipping migrations"
|
36
|
+
# else
|
37
|
+
# puts "added Apache Age migrations, running migrations"
|
38
|
+
# run_db_migrate
|
39
|
+
# end
|
40
|
+
run_db_migrate
|
41
|
+
|
42
|
+
# adjust the schema file (unfortunately rails mangles the schema file)
|
43
|
+
# if is_schema_blank
|
44
|
+
# puts "creating new schema..."
|
45
|
+
# create_new_schema(last_migration_version, destination_schema, source_schema)
|
46
|
+
# else
|
47
|
+
# puts "updating existing schema..."
|
48
|
+
# update_existing_schema(last_migration_version, destination_schema, source_schema)
|
49
|
+
# end
|
50
|
+
Rake::Task["apache_age:schema_config"].invoke
|
51
|
+
|
52
|
+
# ensure the config/database.yml file has the proper configurations
|
53
|
+
# update_database_yml
|
54
|
+
Rake::Task["apache_age:database_config"].invoke
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_db_create
|
58
|
+
puts "Running db:create..."
|
59
|
+
Rake::Task["db:create"].invoke
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_db_migrate
|
63
|
+
puts "Running db:migrate..."
|
64
|
+
Rake::Task["db:migrate"].invoke
|
65
|
+
end
|
66
|
+
|
67
|
+
def copy_migrations
|
68
|
+
migration_version = nil
|
69
|
+
|
70
|
+
source = File.expand_path('../../../db/migrate', __FILE__)
|
71
|
+
destination = File.expand_path("#{Rails.root}/db/migrate", __FILE__)
|
72
|
+
|
73
|
+
FileUtils.mkdir_p(destination) unless File.exist?(destination)
|
74
|
+
existing_migrations =
|
75
|
+
Dir.glob("#{destination}/*.rb").map { |file| File.basename(file).sub(/^\d+/, '') }
|
76
|
+
|
77
|
+
Dir.glob("#{source}/*.rb").each do |file|
|
78
|
+
filename = File.basename(file)
|
79
|
+
test_name = filename.sub(/^\d+/, '')
|
80
|
+
|
81
|
+
if existing_migrations.include?(test_name)
|
82
|
+
puts "Skipping #{filename}, it already exists"
|
83
|
+
else
|
84
|
+
migration_version = Time.now.utc.strftime("%Y_%m_%d_%H%M%S")
|
85
|
+
file_version = migration_version.delete('_')
|
86
|
+
new_filename = filename.sub(/^\d+/, file_version)
|
87
|
+
destination_file = File.join(destination, new_filename)
|
88
|
+
FileUtils.cp(file, destination_file)
|
89
|
+
puts "Copied #{filename} to #{destination} as #{new_filename}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
migration_version
|
93
|
+
end
|
94
|
+
|
95
|
+
def blank_schema?(destination_schema)
|
96
|
+
return false unless File.exist?(destination_schema)
|
97
|
+
|
98
|
+
content = File.read(destination_schema)
|
99
|
+
content.include?('define(version: 0)') &&
|
100
|
+
(content.include?("enable_extension 'plpgsql'") || content.include?('enable_extension "plpgsql"')) &&
|
101
|
+
content.scan(/enable_extension/).size == 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def schema_rails_version(destination_schema)
|
105
|
+
if File.exist?(destination_schema)
|
106
|
+
content = File.read(destination_schema)
|
107
|
+
version_match = content.match(/ActiveRecord::Schema\[(.*?)\]/)
|
108
|
+
return version_match[1] if version_match
|
109
|
+
else
|
110
|
+
full_version = Rails.version
|
111
|
+
primary_secondary_version = full_version.split('.')[0..1].join('.')
|
112
|
+
primary_secondary_version
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_new_schema(last_migration_version, destination_schema, source_schema)
|
117
|
+
if File.exist?(source_schema) && File.exist?(destination_schema)
|
118
|
+
rails_version = schema_rails_version(destination_schema)
|
119
|
+
source_content = File.read(source_schema)
|
120
|
+
|
121
|
+
# ensure we use the Rails version from the destination schema
|
122
|
+
source_content.gsub!(
|
123
|
+
/ActiveRecord::Schema\[\d+\.\d+\]/,
|
124
|
+
"ActiveRecord::Schema[#{rails_version}]"
|
125
|
+
)
|
126
|
+
# ensure we use the last migration version (not the source schema version)
|
127
|
+
source_content.gsub!(
|
128
|
+
/define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do/,
|
129
|
+
"define(version: #{last_migration_version}) do"
|
130
|
+
)
|
131
|
+
|
132
|
+
File.write(destination_schema, source_content)
|
133
|
+
puts "Created new schema in #{destination_schema} with necessary extensions and configurations."
|
134
|
+
else
|
135
|
+
puts "local db/schema.rb file not found."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def update_existing_schema(last_migration_version, destination_schema, source_schema)
|
140
|
+
if File.exist?(source_schema) && File.exist?(destination_schema)
|
141
|
+
rails_version = schema_rails_version(destination_schema)
|
142
|
+
source_content = File.read(source_schema)
|
143
|
+
new_content =
|
144
|
+
source_content.gsub(
|
145
|
+
/.*ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n|\nend$/,
|
146
|
+
''
|
147
|
+
)
|
148
|
+
|
149
|
+
destination_content = File.read(destination_schema)
|
150
|
+
|
151
|
+
# Remove unwanted schema statements
|
152
|
+
destination_content.gsub!(%r{^.*?create_schema "ag_catalog".*?\n}, '')
|
153
|
+
destination_content.gsub!(%r{^.*?create_schema "age_schema".*?\n}, '')
|
154
|
+
destination_content.gsub!(%r{^.*?enable_extension "age".*?\n}, '')
|
155
|
+
destination_content.gsub!(%r{^.*?enable_extension "plpgsql".*?\n}, '')
|
156
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
157
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'regnamespace' for column 'namespace'.*?\n}, '')
|
158
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
159
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'regclass' for column 'relation'.*?\n}, '')
|
160
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'graphid' for column 'id'.*?\n}, '')
|
161
|
+
destination_content.gsub!(
|
162
|
+
%r{^.*?# Could not dump table "_ag_label_edge" because of following StandardError.*?\n}, ''
|
163
|
+
)
|
164
|
+
destination_content.gsub!(
|
165
|
+
%r{^.*?# Could not dump table "_ag_label_vertex" because of following StandardError.*?\n}, ''
|
166
|
+
)
|
167
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
168
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
169
|
+
destination_content.gsub!(%r{^.*?add_foreign_key "ag_label", "ag_graph".*?\n}, '')
|
170
|
+
|
171
|
+
# add new wanted schema statements (at the top of the schema)
|
172
|
+
unless destination_content.include?(%{execute("LOAD 'age';")}) &&
|
173
|
+
destination_content.include?(%{enable_extension 'plpgsql'}) &&
|
174
|
+
destination_content.include?(%{execute("SELECT create_graph('age_schema');")}) &&
|
175
|
+
destination_content.include?(%{execute('CREATE EXTENSION IF NOT EXISTS age;')}) &&
|
176
|
+
destination_content.include?(%{execute('SET search_path = ag_catalog, "$user", public;')})
|
177
|
+
# if not all are found then remove any found
|
178
|
+
destination_content.gsub!(%r{^.*?execute("LOAD 'age';")*?\n}, '')
|
179
|
+
destination_content.gsub!(%r{^.*?enable_extension 'plpgsql'*?\n}, '')
|
180
|
+
destination_content.gsub!(%r{^.*?execute("SELECT create_graph('age_schema');")*?\n}, '')
|
181
|
+
destination_content.gsub!(%r{^.*?execute('CREATE EXTENSION IF NOT EXISTS age;')*?\n}, '')
|
182
|
+
destination_content.gsub!(%r{^.*?execute('SET search_path = ag_catalog, "$user", public;')*?\n}, '')
|
183
|
+
destination_content.gsub!(%r{^.*?# Allow age extension*?\n}, '')
|
184
|
+
destination_content.gsub!(%r{^.*?# Load the ag_catalog into the search path*?\n}, '')
|
185
|
+
destination_content.gsub!(%r{^.*?# Create age_schema graph if it doesn't exist*?\n}, '')
|
186
|
+
destination_content.gsub!(%r{^.*?# These are extensions that must be enabled in order to support this database*?\n}, '')
|
187
|
+
|
188
|
+
# add all of the correct settings back in
|
189
|
+
destination_content.sub!(
|
190
|
+
%r{(ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n)},
|
191
|
+
"\\1#{new_content}\n"
|
192
|
+
)
|
193
|
+
puts 'db/schema.rb has been updated with the necessary configuration.'
|
194
|
+
else
|
195
|
+
puts 'db/schema.rb has the necessary configuration, no adjustments necessary'
|
196
|
+
end
|
197
|
+
|
198
|
+
existing_version = destination_content.match(/define\(version: (\d{4}(?:_\d{2}){2}(?:_\d{6})?)\)/)[1].gsub('_', '')
|
199
|
+
current_version = last_migration_version ? last_migration_version.gsub('_', '') : existing_version
|
200
|
+
|
201
|
+
# ensure we use the last migration version (not the source schema version)
|
202
|
+
if current_version.to_i > existing_version.to_i
|
203
|
+
destination_content.gsub!(
|
204
|
+
/define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do/,
|
205
|
+
"define(version: #{last_migration_version}) do"
|
206
|
+
)
|
207
|
+
puts "Updated schema version to the migration version #{last_migration_version}"
|
208
|
+
end
|
209
|
+
|
210
|
+
File.write(destination_schema, destination_content)
|
211
|
+
puts "Updated #{destination_schema} with necessary extensions and configurations."
|
212
|
+
else
|
213
|
+
puts "local db/schema.rb file not found."
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def update_database_yml
|
218
|
+
db_config_file = File.expand_path("#{Rails.root}/config/database.yml", __FILE__)
|
219
|
+
|
220
|
+
# Read the file
|
221
|
+
lines = File.readlines(db_config_file)
|
222
|
+
|
223
|
+
# any uncommented "schema_search_path:" lines?
|
224
|
+
path_index = lines.find_index { |line| !line.include?('#') && line.include?('schema_search_path:') }
|
225
|
+
default_start_index = lines.index { |line| line.strip.start_with?('default:') }
|
226
|
+
|
227
|
+
# when it finds an existing schema_search_path, it updates it
|
228
|
+
if path_index && lines[path_index].include?('ag_catalog,age_schema')
|
229
|
+
puts "schema_search_path already set to ag_catalog,age_schema nothing to do."
|
230
|
+
return
|
231
|
+
elsif path_index
|
232
|
+
key, val = lines[path_index].split(': ')
|
233
|
+
# remove any unwanted characters
|
234
|
+
val = val.gsub(/[ "\s\"\"'\n]/, '')
|
235
|
+
lines[path_index] = "#{key}: ag_catalog,age_schema,#{val}\n"
|
236
|
+
puts "add ag_catalog,age_schema to schema_search_path"
|
237
|
+
elsif default_start_index
|
238
|
+
puts "add ag_catalog,age_schema,public to schema_search_path in the default section of database.yml"
|
239
|
+
sections_index = lines.map.with_index { |line, index| index if !line.start_with?(' ') }.compact.sort
|
240
|
+
|
241
|
+
# find the start of the default section
|
242
|
+
next_section_in_list = sections_index.index(default_start_index) + 1
|
243
|
+
|
244
|
+
# find the end of the default section (before the next section starts)
|
245
|
+
path_insert_index = sections_index[next_section_in_list]
|
246
|
+
|
247
|
+
lines.insert(path_insert_index, " schema_search_path: ag_catalog,age_schema,public\n")
|
248
|
+
else
|
249
|
+
puts "didn't find a default section in database.yml, please add the following line:"
|
250
|
+
puts " schema_search_path: ag_catalog,age_schema,public"
|
251
|
+
puts "to the apprpriate section of your database.yml"
|
252
|
+
end
|
253
|
+
|
254
|
+
# Write the modified lines back to the file
|
255
|
+
File.open(db_config_file, 'w') { |file| file.write(lines.join) }
|
256
|
+
end
|
257
|
+
end
|
data/lib/tasks/install.rake
CHANGED
@@ -2,209 +2,18 @@
|
|
2
2
|
# Usage: `rake apache_age:install`
|
3
3
|
#
|
4
4
|
namespace :apache_age do
|
5
|
-
desc "
|
5
|
+
desc "Install & configure Apache Age within Rails (updates migrations, schema & database.yml)"
|
6
6
|
task :install => :environment do
|
7
|
-
|
8
|
-
|
7
|
+
# copy our migrations to the application (if needed)
|
8
|
+
Rake::Task["apache_age:copy_migrations"].invoke
|
9
9
|
|
10
|
-
#
|
11
|
-
run_migrations
|
12
|
-
|
13
|
-
# copy our migrations to the application
|
14
|
-
last_migration_version = copy_migrations
|
15
|
-
|
16
|
-
# check if the schema is blank (NEW) before running migrations!
|
17
|
-
is_schema_blank = blank_schema?(destination_schema)
|
18
|
-
puts "Schema is blank: #{is_schema_blank}"
|
19
|
-
|
20
|
-
# run our new migrations
|
21
|
-
run_migrations
|
22
|
-
|
23
|
-
# adjust the schema file (unfortunately rails mangles the schema file)
|
24
|
-
if is_schema_blank
|
25
|
-
puts "creating new schema..."
|
26
|
-
create_new_schema(last_migration_version, destination_schema, source_schema)
|
27
|
-
else
|
28
|
-
puts "updating existing schema..."
|
29
|
-
update_existing_schema(last_migration_version, destination_schema, source_schema)
|
30
|
-
end
|
31
|
-
|
32
|
-
update_database_yml
|
33
|
-
end
|
34
|
-
|
35
|
-
def copy_migrations
|
36
|
-
migration_version = nil
|
37
|
-
|
38
|
-
source = File.expand_path('../../../db/migrate', __FILE__)
|
39
|
-
destination = File.expand_path("#{Rails.root}/db/migrate", __FILE__)
|
40
|
-
|
41
|
-
FileUtils.mkdir_p(destination) unless File.exist?(destination)
|
42
|
-
existing_migrations =
|
43
|
-
Dir.glob("#{destination}/*.rb").map { |file| File.basename(file).sub(/^\d+/, '') }
|
44
|
-
|
45
|
-
Dir.glob("#{source}/*.rb").each do |file|
|
46
|
-
filename = File.basename(file)
|
47
|
-
test_name = filename.sub(/^\d+/, '')
|
48
|
-
|
49
|
-
if existing_migrations.include?(test_name)
|
50
|
-
puts "Skipping #{filename}, it already exists"
|
51
|
-
else
|
52
|
-
migration_version = Time.now.utc.strftime("%Y_%m_%d_%H%M%S")
|
53
|
-
file_version = migration_version.delete('_')
|
54
|
-
new_filename = filename.sub(/^\d+/, file_version)
|
55
|
-
destination_file = File.join(destination, new_filename)
|
56
|
-
FileUtils.cp(file, destination_file)
|
57
|
-
puts "Copied #{filename} to #{destination} as #{new_filename}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
migration_version
|
61
|
-
end
|
62
|
-
|
63
|
-
def blank_schema?(destination_schema)
|
64
|
-
return false unless File.exist?(destination_schema)
|
65
|
-
|
66
|
-
content = File.read(destination_schema)
|
67
|
-
content.include?('define(version: 0)') &&
|
68
|
-
content.include?('enable_extension "plpgsql"') &&
|
69
|
-
content.scan(/enable_extension/).size == 1
|
70
|
-
end
|
71
|
-
|
72
|
-
def run_migrations
|
73
|
-
puts "Running migrations..."
|
10
|
+
# run any new migrations
|
74
11
|
Rake::Task["db:migrate"].invoke
|
75
|
-
end
|
76
|
-
|
77
|
-
def extract_rails_version(destination_schema)
|
78
|
-
if File.exist?(destination_schema)
|
79
|
-
content = File.read(destination_schema)
|
80
|
-
version_match = content.match(/ActiveRecord::Schema\[(.*?)\]/)
|
81
|
-
return version_match[1] if version_match
|
82
|
-
else
|
83
|
-
full_version = Rails.version
|
84
|
-
primary_secondary_version = full_version.split('.')[0..1].join('.')
|
85
|
-
primary_secondary_version
|
86
|
-
end
|
87
|
-
end
|
88
12
|
|
89
|
-
|
90
|
-
|
91
|
-
rails_version = extract_rails_version(destination_schema)
|
92
|
-
source_content = File.read(source_schema)
|
93
|
-
|
94
|
-
# ensure we use the Rails version from the destination schema
|
95
|
-
source_content.gsub!(
|
96
|
-
/ActiveRecord::Schema\[\d+\.\d+\]/,
|
97
|
-
"ActiveRecord::Schema[#{rails_version}]"
|
98
|
-
)
|
99
|
-
# ensure we use the last migration version (not the source schema version)
|
100
|
-
source_content.gsub!(
|
101
|
-
/define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do/,
|
102
|
-
"define(version: #{last_migration_version}) do"
|
103
|
-
)
|
104
|
-
|
105
|
-
File.write(destination_schema, source_content)
|
106
|
-
puts "Created new schema in #{destination_schema} with necessary extensions and configurations."
|
107
|
-
else
|
108
|
-
puts "local db/schema.rb file not found."
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def update_existing_schema(last_migration_version, destination_schema, source_schema)
|
113
|
-
if File.exist?(source_schema) && File.exist?(destination_schema)
|
114
|
-
rails_version = extract_rails_version(destination_schema)
|
115
|
-
source_content = File.read(source_schema)
|
116
|
-
new_content =
|
117
|
-
source_content.gsub(
|
118
|
-
/.*ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n|\nend$/,
|
119
|
-
''
|
120
|
-
)
|
121
|
-
|
122
|
-
destination_content = File.read(destination_schema)
|
123
|
-
|
124
|
-
# Remove unwanted schema statements
|
125
|
-
destination_content.gsub!(%r{^.*?# These are extensions that must be enabled in order to support this database.*?\n}, '')
|
126
|
-
|
127
|
-
destination_content.gsub!(%r{^.*?create_schema "ag_catalog".*?\n}, '')
|
128
|
-
destination_content.gsub!(%r{^.*?create_schema "age_schema".*?\n}, '')
|
129
|
-
destination_content.gsub!(%r{^.*?enable_extension "age".*?\n}, '')
|
130
|
-
destination_content.gsub!(%r{^.*?enable_extension "plpgsql".*?\n}, '')
|
131
|
-
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
132
|
-
destination_content.gsub!(%r{^.*?# Unknown type 'regnamespace' for column 'namespace'.*?\n}, '')
|
133
|
-
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
134
|
-
destination_content.gsub!(%r{^.*?# Unknown type 'regclass' for column 'relation'.*?\n}, '')
|
135
|
-
destination_content.gsub!(%r{^.*?# Unknown type 'graphid' for column 'id'.*?\n}, '')
|
136
|
-
destination_content.gsub!(
|
137
|
-
%r{^.*?# Could not dump table "_ag_label_edge" because of following StandardError.*?\n}, ''
|
138
|
-
)
|
139
|
-
destination_content.gsub!(
|
140
|
-
%r{^.*?# Could not dump table "_ag_label_vertex" because of following StandardError.*?\n}, ''
|
141
|
-
)
|
142
|
-
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
143
|
-
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
144
|
-
destination_content.gsub!(%r{^.*?add_foreign_key "ag_label", "ag_graph".*?\n}, '')
|
145
|
-
|
146
|
-
# add new wanted schema statements (at the top of the schema)
|
147
|
-
destination_content.sub!(
|
148
|
-
%r{(ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n)},
|
149
|
-
"\\1#{new_content}\n"
|
150
|
-
)
|
151
|
-
|
152
|
-
existing_version = destination_content.match(/define\(version: (\d{4}(?:_\d{2}){2}(?:_\d{6})?)\)/)[1].gsub('_', '')
|
153
|
-
current_version = last_migration_version ? last_migration_version.gsub('_', '') : existing_version
|
154
|
-
|
155
|
-
# ensure we use the last migration version (not the source schema version)
|
156
|
-
if current_version.to_i > existing_version.to_i
|
157
|
-
destination_content.gsub!(
|
158
|
-
/define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do/,
|
159
|
-
"define(version: #{last_migration_version}) do"
|
160
|
-
)
|
161
|
-
end
|
162
|
-
|
163
|
-
File.write(destination_schema, destination_content)
|
164
|
-
puts "Updated #{destination_schema} with necessary extensions and configurations."
|
165
|
-
else
|
166
|
-
puts "local db/schema.rb file not found."
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def update_database_yml
|
171
|
-
db_config_file = File.expand_path("#{Rails.root}/config/database.yml", __FILE__)
|
172
|
-
|
173
|
-
# Read the file
|
174
|
-
lines = File.readlines(db_config_file)
|
175
|
-
|
176
|
-
# any uncommented "schema_search_path:" lines?
|
177
|
-
path_index = lines.find_index { |line| !line.include?('#') && line.include?('schema_search_path:') }
|
178
|
-
default_start_index = lines.index { |line| line.strip.start_with?('default:') }
|
179
|
-
|
180
|
-
# when it finds an existing schema_search_path, it updates it
|
181
|
-
if path_index && lines[path_index].include?('ag_catalog,age_schema')
|
182
|
-
puts "schema_search_path already set to ag_catalog,age_schema nothing to do."
|
183
|
-
return
|
184
|
-
elsif path_index
|
185
|
-
key, val = lines[path_index].split(': ')
|
186
|
-
# remove any unwanted characters
|
187
|
-
val = val.gsub(/[ "\s\"\"'\n]/, '')
|
188
|
-
lines[path_index] = "#{key}: ag_catalog,age_schema,#{val}\n"
|
189
|
-
puts "add ag_catalog,age_schema to schema_search_path"
|
190
|
-
elsif default_start_index
|
191
|
-
puts "add ag_catalog,age_schema,public to schema_search_path in the default section of database.yml"
|
192
|
-
sections_index = lines.map.with_index { |line, index| index if !line.start_with?(' ') }.compact.sort
|
193
|
-
|
194
|
-
# find the start of the default section
|
195
|
-
next_section_in_list = sections_index.index(default_start_index) + 1
|
196
|
-
|
197
|
-
# find the end of the default section (before the next section starts)
|
198
|
-
path_insert_index = sections_index[next_section_in_list]
|
199
|
-
|
200
|
-
lines.insert(path_insert_index, " schema_search_path: ag_catalog,age_schema,public\n")
|
201
|
-
else
|
202
|
-
puts "didn't find a default section in database.yml, please add the following line:"
|
203
|
-
puts " schema_search_path: ag_catalog,age_schema,public"
|
204
|
-
puts "to the apprpriate section of your database.yml"
|
205
|
-
end
|
13
|
+
# adjust the schema file (unfortunately rails mangles the schema file)
|
14
|
+
Rake::Task["apache_age:schema_config"].invoke
|
206
15
|
|
207
|
-
#
|
208
|
-
|
16
|
+
# ensure the config/database.yml file has the proper configurations
|
17
|
+
Rake::Task["apache_age:database_config"].invoke
|
209
18
|
end
|
210
19
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage: `rake apache_age:schema_config`
|
3
|
+
#
|
4
|
+
namespace :apache_age do
|
5
|
+
desc "Copy migrations from rails_age to application and update schema"
|
6
|
+
task :schema_config => :environment do
|
7
|
+
# source_schema = File.expand_path('../../../db/schema.rb', __FILE__)
|
8
|
+
destination_schema = File.expand_path("#{Rails.root}/db/schema.rb", __FILE__)
|
9
|
+
|
10
|
+
unless File.exist?(destination_schema)
|
11
|
+
puts "local db/schema.rb file not found. please run db:create and db:migrate first"
|
12
|
+
else
|
13
|
+
destination_content = File.read(destination_schema)
|
14
|
+
|
15
|
+
# Remove unwanted schema statements
|
16
|
+
destination_content.gsub!(%r{^.*?create_schema "ag_catalog".*?\n}, '')
|
17
|
+
destination_content.gsub!(%r{^.*?create_schema "age_schema".*?\n}, '')
|
18
|
+
destination_content.gsub!(%r{^.*?enable_extension "age".*?\n}, '')
|
19
|
+
destination_content.gsub!(%r{^.*?enable_extension "plpgsql".*?\n}, '')
|
20
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
21
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'regnamespace' for column 'namespace'.*?\n}, '')
|
22
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
23
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'regclass' for column 'relation'.*?\n}, '')
|
24
|
+
destination_content.gsub!(%r{^.*?# Unknown type 'graphid' for column 'id'.*?\n}, '')
|
25
|
+
destination_content.gsub!(
|
26
|
+
%r{^.*?# Could not dump table "_ag_label_edge" because of following StandardError.*?\n}, ''
|
27
|
+
)
|
28
|
+
destination_content.gsub!(
|
29
|
+
%r{^.*?# Could not dump table "_ag_label_vertex" because of following StandardError.*?\n}, ''
|
30
|
+
)
|
31
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_graph" because of following StandardError.*?\n}, '')
|
32
|
+
destination_content.gsub!(%r{^.*?# Could not dump table "ag_label" because of following StandardError.*?\n}, '')
|
33
|
+
destination_content.gsub!(%r{^.*?add_foreign_key "ag_label", "ag_graph".*?\n}, '')
|
34
|
+
|
35
|
+
# add necessary contents (as needed)
|
36
|
+
if destination_content.include?(%{execute("LOAD 'age';")}) &&
|
37
|
+
destination_content.include?(%{enable_extension 'plpgsql'}) &&
|
38
|
+
destination_content.include?(%{execute("SELECT create_graph('age_schema');")}) &&
|
39
|
+
destination_content.include?(%{execute('CREATE EXTENSION IF NOT EXISTS age;')}) &&
|
40
|
+
destination_content.include?(%{execute('SET search_path = ag_catalog, "$user", public;')})
|
41
|
+
puts "schema.rb is properly configured, nothing to do"
|
42
|
+
else
|
43
|
+
# if not all are found then remove any found
|
44
|
+
destination_content.gsub!(%r{^.*?execute("LOAD 'age';")*?\n}, '')
|
45
|
+
destination_content.gsub!(%r{^.*?enable_extension 'plpgsql'*?\n}, '')
|
46
|
+
destination_content.gsub!(%r{^.*?execute("SELECT create_graph('age_schema');")*?\n}, '')
|
47
|
+
destination_content.gsub!(%r{^.*?execute('CREATE EXTENSION IF NOT EXISTS age;')*?\n}, '')
|
48
|
+
destination_content.gsub!(%r{^.*?execute('SET search_path = ag_catalog, "$user", public;')*?\n}, '')
|
49
|
+
destination_content.gsub!(%r{^.*?# Allow age extension*?\n}, '')
|
50
|
+
destination_content.gsub!(%r{^.*?# Load the ag_catalog into the search path*?\n}, '')
|
51
|
+
destination_content.gsub!(%r{^.*?# Create age_schema graph if it doesn't exist*?\n}, '')
|
52
|
+
destination_content.gsub!(%r{^.*?# These are extensions that must be enabled in order to support this database*?\n}, '')
|
53
|
+
|
54
|
+
# add all of the correct settings back in
|
55
|
+
# source_content = File.read(source_schema)
|
56
|
+
source_content =
|
57
|
+
<<~RUBY
|
58
|
+
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
59
|
+
# These are extensions that must be enabled in order to support this database
|
60
|
+
enable_extension 'plpgsql'
|
61
|
+
|
62
|
+
# Allow age extension
|
63
|
+
execute('CREATE EXTENSION IF NOT EXISTS age;')
|
64
|
+
|
65
|
+
# Load the age code
|
66
|
+
execute("LOAD 'age';")
|
67
|
+
|
68
|
+
# Load the ag_catalog into the search path
|
69
|
+
execute('SET search_path = ag_catalog, "$user", public;')
|
70
|
+
|
71
|
+
# Create age_schema graph if it doesn't exist
|
72
|
+
execute("SELECT create_graph('age_schema');")
|
73
|
+
end
|
74
|
+
RUBY
|
75
|
+
|
76
|
+
age_config_contents =
|
77
|
+
source_content.gsub(
|
78
|
+
/.*ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n|\nend$/,
|
79
|
+
''
|
80
|
+
)
|
81
|
+
|
82
|
+
destination_content.sub!(
|
83
|
+
%r{(ActiveRecord::Schema\[\d+\.\d+\]\.define\(version: \d{4}(?:_\d{2}){2}(?:_\d{6})?\) do\n)},
|
84
|
+
"\\1#{age_config_contents}\n"
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove multiple consecutive empty lines
|
89
|
+
destination_content.gsub!(/\n{2,}/, "\n\n")
|
90
|
+
|
91
|
+
File.write(destination_schema, destination_content)
|
92
|
+
puts "The schema '#{destination_schema}' is ready to work with Apache Age."
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
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.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bill Tihen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -76,11 +76,20 @@ files:
|
|
76
76
|
- lib/apache_age/validators/unique_edge_validator.rb
|
77
77
|
- lib/apache_age/validators/unique_vertex_validator.rb
|
78
78
|
- lib/apache_age/validators/vertex_type_validator.rb
|
79
|
+
- lib/generators/apache_age/edge/USAGE
|
80
|
+
- lib/generators/apache_age/edge/edge_generator.rb
|
81
|
+
- lib/generators/apache_age/edge/templates/edge.rb.tt
|
82
|
+
- lib/generators/apache_age/node/USAGE
|
83
|
+
- lib/generators/apache_age/node/node_generator.rb
|
84
|
+
- lib/generators/apache_age/node/templates/node.rb.tt
|
79
85
|
- lib/rails_age.rb
|
80
86
|
- lib/rails_age/engine.rb
|
81
87
|
- lib/rails_age/version.rb
|
88
|
+
- lib/tasks/copy_migrations.rake
|
89
|
+
- lib/tasks/database_config.rake
|
90
|
+
- lib/tasks/install.original.rake
|
82
91
|
- lib/tasks/install.rake
|
83
|
-
- lib/tasks/
|
92
|
+
- lib/tasks/schema_config.rake
|
84
93
|
homepage: https://github.com/marpori/rails_age
|
85
94
|
licenses:
|
86
95
|
- MIT
|