rails_age 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -9
- data/README.md +82 -344
- data/lib/apache_age/entities/edge.rb +3 -3
- data/lib/apache_age/validators/unique_edge_validator.rb +4 -4
- data/lib/apache_age/validators/unique_vertex_validator.rb +2 -2
- data/lib/generators/apache_age/node/node_generator.rb +38 -0
- data/lib/rails_age/version.rb +1 -1
- data/lib/tasks/{database_config.rake → config_database.rake} +1 -1
- data/lib/tasks/config_migrate.rake +35 -0
- data/lib/tasks/{schema_config.rake → config_schema.rake} +1 -2
- data/lib/tasks/config_types.rake +85 -0
- data/lib/tasks/install.rake +10 -3
- data/lib/tasks/migrate.rake +7 -0
- metadata +7 -5
- data/lib/tasks/install.original.rake +0 -257
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8228c60b43732a31b364b658e65b5d6a2e1b76018b573b1ec5b7fa25582f519c
|
4
|
+
data.tar.gz: 60f5211cb37aa6605523f76cde81158b6495448e90d4fbeabecd020c38520c4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eec16e9c7b4512793b4aac9a3ac1c9ee0ce6280a1033712cc6aed5466218ffa17a47c1fca4d1f4d0508ce38e4a12a9a0c392d927f72a62c5ebcf8d2ea7c343f5
|
7
|
+
data.tar.gz: 3ccdeba21d32bc35de9162698c5c340efce5b0557b5930a3b1e0d9caaf5697f5afcd2b39ad65d0c976b5b3bc46df5edaf18a7bb96e023a2090642b08eb002786
|
data/CHANGELOG.md
CHANGED
@@ -1,26 +1,51 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## VERSION 0.
|
3
|
+
## VERSION 0.5.0 - 2024-xx-xx
|
4
|
+
|
5
|
+
- **AGE Schema override** (instance and class methods) assumes db and migrations up-to-date
|
4
6
|
|
5
7
|
- **cypher**
|
8
|
+
* schema override
|
6
9
|
* query support
|
7
10
|
* paths support
|
8
11
|
* select attributes support
|
12
|
+
|
9
13
|
- **Paths**
|
10
|
-
* ?
|
11
14
|
|
12
|
-
## VERSION 0.
|
15
|
+
## VERSION 0.4.4 - 2024-xx-xx
|
16
|
+
|
17
|
+
- **Edge Scaffold** (generates edge, type, view and controller)
|
18
|
+
* add `rails generate apache_age:edge_scaffold HasJob employee_role start_node:person end_node:company`
|
13
19
|
|
14
|
-
-
|
15
|
-
* add `rails generate apache_age:type`
|
16
|
-
* add `rails generate apache_age:node_type`
|
17
|
-
* add `rails generate apache_age:edge_type`
|
20
|
+
## VERSION 0.4.3 - 2024-xx-xx
|
18
21
|
|
19
|
-
|
22
|
+
- **Node Scaffold** (generates node, type, view and controller)
|
23
|
+
* add `rails generate apache_age:node_scaffold Person first_name last_name age:integer`
|
24
|
+
|
25
|
+
## VERSION 0.4.2 - 2024-xx-xx
|
20
26
|
|
21
27
|
- **Edge Generator**
|
22
|
-
* add `rails generate apache_age:edge
|
28
|
+
* add `rails generate apache_age:edge HasPet owner_role` just a property
|
29
|
+
* add `rails generate apache_age:edge HasPet owner_role start_node:person end_node:pet`
|
30
|
+
with property and specified start-/end-nodes (person and pet nodes must have already been created)
|
31
|
+
|
32
|
+
## VERSION 0.4.1 - 2024-xx-xx
|
23
33
|
|
34
|
+
- **OPTIONAL Installer**
|
35
|
+
* add `config_migrate` to `rails generate apache_age:install` auto fix the schema after `rails db:migrate`
|
36
|
+
|
37
|
+
## VERSION 0.4.0 - 2024-06-14
|
38
|
+
|
39
|
+
Minor breaking change: type (:vertix) is now required in core for edges
|
40
|
+
|
41
|
+
- **Installer**
|
42
|
+
* AGE types added to installer (with tests)
|
43
|
+
|
44
|
+
- **Node Generator**
|
45
|
+
* add also creates node types (with tests)
|
46
|
+
|
47
|
+
- **Apache AGE Migrate**
|
48
|
+
* add `bin/rails apache_age:migrate` runs `bin/rails db:migrate` followed by `bin/rails apache_age:config_schema` to fix the schema file after `bin/rails db:migrate`
|
24
49
|
|
25
50
|
## VERSION 0.3.2 - 2024-06-08
|
26
51
|
|
@@ -33,6 +58,7 @@
|
|
33
58
|
|
34
59
|
- **Installer**
|
35
60
|
* refactor into multiple independent tasks with tests
|
61
|
+
|
36
62
|
- **Documentation**
|
37
63
|
* updated README with additional information
|
38
64
|
* added `db/structure.sql` config to README
|
@@ -41,6 +67,7 @@
|
|
41
67
|
|
42
68
|
- **Edges**
|
43
69
|
* `find_by(start_node:, :end_node:, properties:)` to find an edge with specific nodes & properties (deprecated `find_edge`)
|
70
|
+
|
44
71
|
- **Installer** (`rails generate apache_age:install`)
|
45
72
|
* copy Age PG Extenstion migration to `db/migrate`
|
46
73
|
* run the AGE PG Migration
|
@@ -56,6 +83,7 @@ NOTE: the `rails generate apache_age:install` can be run at any time to repair t
|
|
56
83
|
* add missing methods to use in rails controllers
|
57
84
|
* validate edge start- & end-nodes are valid
|
58
85
|
* add unique edge validations
|
86
|
+
|
59
87
|
- **Nodes**
|
60
88
|
* add missing methods to use in rails controllers
|
61
89
|
* add unique node validations
|
@@ -67,9 +95,11 @@ Initial release has the following features:
|
|
67
95
|
- **Nodes:**
|
68
96
|
* `.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
|
69
97
|
* verified with usage in a controller and views
|
98
|
+
|
70
99
|
- **Edges:**
|
71
100
|
*`.create`, `.read`, `.update`, `.delete`, `.all`, `.find(by id)`, `.find_by(age_properties)`
|
72
101
|
* verified with usage in a controller and views
|
102
|
+
|
73
103
|
- **Entities:**
|
74
104
|
* `.all`, `.find(id)`, `.find_by(age_property)` use these when class, label, edge, node
|
75
105
|
|
data/README.md
CHANGED
@@ -12,371 +12,109 @@ Add this line to your application's Gemfile:
|
|
12
12
|
gem "rails_age"
|
13
13
|
```
|
14
14
|
|
15
|
-
|
15
|
+
## Quick Start
|
16
16
|
|
17
17
|
using the installer, creates the migration to install age, runs the migration, and adjusts the schema file, and updates the `config/database.yml` file.
|
18
18
|
|
19
|
+
setup (& Test) postgresql with AGE (using the docker version of AGE DB may be the easiest way to get started)
|
20
|
+
using the docker version of AGE DB, you can confirm psql AGE with the following commands:
|
19
21
|
```bash
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
psql -h localhost -p 5455 -U docker_username
|
23
|
+
> CREATE EXTENSION IF NOT EXISTS age;
|
24
|
+
> LOAD 'age';
|
25
|
+
> SET search_path = ag_catalog, "$user", public;
|
26
|
+
> SELECT create_graph('age_schema');
|
27
|
+
> \q
|
26
28
|
```
|
27
29
|
|
28
|
-
|
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.
|
33
|
-
|
34
|
-
### Manual Install
|
35
|
-
|
36
|
-
create a migration to add the Apache Age extension to your database
|
30
|
+
create a new Rails app (WITH POSTGRESQL!)
|
37
31
|
```bash
|
38
|
-
|
32
|
+
rails new age_demo -d postgresql
|
33
|
+
cd age_demo
|
34
|
+
git add .
|
35
|
+
git commit -m "Initial Rails App"
|
39
36
|
```
|
40
|
-
|
41
|
-
```
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
# Load the age code
|
48
|
-
execute("LOAD 'age';")
|
49
|
-
|
50
|
-
# Load the ag_catalog into the search path
|
51
|
-
execute('SET search_path = ag_catalog, "$user", public;')
|
52
|
-
|
53
|
-
# Create age_schema graph if it doesn't exist
|
54
|
-
execute("SELECT create_graph('age_schema');")
|
55
|
-
end
|
56
|
-
|
57
|
-
def down
|
58
|
-
execute <<-SQL
|
59
|
-
DO $$
|
60
|
-
BEGIN
|
61
|
-
IF EXISTS (
|
62
|
-
SELECT 1
|
63
|
-
FROM pg_constraint
|
64
|
-
WHERE conname = 'fk_graph_oid'
|
65
|
-
) THEN
|
66
|
-
ALTER TABLE ag_catalog.ag_label
|
67
|
-
DROP CONSTRAINT fk_graph_oid;
|
68
|
-
END IF;
|
69
|
-
END $$;
|
70
|
-
SQL
|
71
|
-
|
72
|
-
execute("SELECT drop_graph('age_schema', true);")
|
73
|
-
execute('DROP SCHEMA IF EXISTS ag_catalog CASCADE;')
|
74
|
-
execute('DROP EXTENSION IF EXISTS age;')
|
75
|
-
end
|
76
|
-
end
|
37
|
+
configure `config/database.yml` when using the docker version of AGE DB my config looks like:
|
38
|
+
```yaml
|
39
|
+
port: 5455
|
40
|
+
host: localhost
|
41
|
+
username: docker_username
|
42
|
+
password: dockerized_password
|
77
43
|
```
|
78
|
-
into your new migration file
|
79
44
|
|
80
|
-
|
45
|
+
now you should be able to create the rails database:
|
81
46
|
```bash
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
88
|
-
create_schema "ag_catalog"
|
89
|
-
create_schema "age_schema"
|
90
|
-
|
91
|
-
# These are extensions that must be enabled in order to support this database
|
92
|
-
enable_extension "age"
|
93
|
-
enable_extension "plpgsql"
|
94
|
-
|
95
|
-
# Could not dump table "_ag_label_edge" because of following StandardError
|
96
|
-
# Unknown type 'graphid' for column 'id'
|
97
|
-
|
98
|
-
# Could not dump table "_ag_label_vertex" because of following StandardError
|
99
|
-
# Unknown type 'graphid' for column 'id'
|
100
|
-
|
101
|
-
# Could not dump table "ag_graph" because of following StandardError
|
102
|
-
# Unknown type 'regnamespace' for column 'namespace'
|
103
|
-
|
104
|
-
# Could not dump table "ag_label" because of following StandardError
|
105
|
-
# Unknown type 'regclass' for column 'relation'
|
106
|
-
|
107
|
-
add_foreign_key "ag_label", "ag_graph", column: "graph", primary_key: "graphid", name: "fk_graph_oid"
|
108
|
-
|
109
|
-
# other migrations
|
110
|
-
# ...
|
111
|
-
end
|
112
|
-
```
|
113
|
-
|
114
|
-
and replace them with the following lines:
|
115
|
-
```ruby
|
116
|
-
ActiveRecord::Schema[7.1].define(version: 2024_05_21_062349) do
|
117
|
-
# These are extensions that must be enabled in order to support this database
|
118
|
-
enable_extension 'plpgsql'
|
119
|
-
|
120
|
-
# Allow age extension
|
121
|
-
execute('CREATE EXTENSION IF NOT EXISTS age;')
|
122
|
-
|
123
|
-
# Load the age code
|
124
|
-
execute("LOAD 'age';")
|
125
|
-
|
126
|
-
# Load the ag_catalog into the search path
|
127
|
-
execute('SET search_path = ag_catalog, "$user", public;')
|
128
|
-
|
129
|
-
# Create age_schema graph if it doesn't exist
|
130
|
-
execute("SELECT create_graph('age_schema');")
|
131
|
-
|
132
|
-
# other migrations
|
133
|
-
# ...
|
134
|
-
end
|
47
|
+
rails db:create
|
48
|
+
rails db:migrate
|
49
|
+
git add .
|
50
|
+
git commit -m "Add Apache Age Postgres DB configured with Rails App"
|
135
51
|
```
|
136
52
|
|
137
|
-
|
138
|
-
```
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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');
|
53
|
+
install Apache Age (you can ignore the `unknown OID` warnings)
|
54
|
+
```bash
|
55
|
+
bundle add rails_age
|
56
|
+
bundle install
|
57
|
+
bin/rails apache_age:install
|
58
|
+
git add .
|
59
|
+
git commit -m "Add Apache Age to Rails"
|
161
60
|
```
|
162
61
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
170
|
-
|
171
|
-
## Usage
|
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
|
-
|
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.
|
177
|
-
|
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.
|
179
|
-
|
180
|
-
### Nodes
|
181
|
-
|
182
|
-
```ruby
|
183
|
-
# app/graphs/nodes/company.rb
|
184
|
-
module Nodes
|
185
|
-
class Company
|
186
|
-
include ApacheAge::Entities::Vertex
|
187
|
-
|
188
|
-
attribute :company_name, :string
|
189
|
-
|
190
|
-
validates :company_name, presence: true
|
191
|
-
validates_with(
|
192
|
-
ApacheAge::Validators::UniqueVertexValidator,
|
193
|
-
attributes: [:company_name]
|
194
|
-
)
|
195
|
-
end
|
196
|
-
end
|
62
|
+
make some nodes :string is the default type
|
63
|
+
```bash
|
64
|
+
rails generate apache_age:node Company company_name
|
65
|
+
rails generate apache_age:node Person first_name last_name
|
66
|
+
rails generate apache_age:node Pet pet_name:string age:integer
|
197
67
|
```
|
68
|
+
make some edges (`:vertex` is the default type) for start_node and end_node
|
69
|
+
```bash
|
70
|
+
# when start node and end node are not specified they are of type `:vertex`
|
71
|
+
# this is generally not recommended - exept when very generic relationships are needed
|
72
|
+
rails generate apache_age:edge HasJob employee_role
|
198
73
|
|
199
|
-
|
200
|
-
|
201
|
-
module Nodes
|
202
|
-
class Person
|
203
|
-
include ApacheAge::Entities::Vertex
|
204
|
-
|
205
|
-
attribute :first_name, :string, default: nil
|
206
|
-
attribute :last_name, :string, default: nil
|
207
|
-
attribute :given_name, :string, default: nil
|
208
|
-
attribute :nick_name, :string, default: nil
|
209
|
-
|
210
|
-
validates :first_name, :last_name, :given_name, :nick_name,
|
211
|
-
presence: true
|
212
|
-
|
213
|
-
def initialize(**attributes)
|
214
|
-
super
|
215
|
-
# use unless present? since attributes when empty sets to "" by default
|
216
|
-
self.nick_name = first_name unless nick_name.present?
|
217
|
-
self.given_name = last_name unless given_name.present?
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
74
|
+
# this is recommended - use explicit start_node and end_node types
|
75
|
+
rails generate apache_age:node HasPet start_node:person end_node:pet caretaker_role
|
221
76
|
```
|
222
77
|
|
223
|
-
|
78
|
+
**NOTE:** the default `rails db:migrate` inappropriately modifies the schema file. This installer patches the migration to prevent this (however this might break on rails updates, etc). **You can run `bin/rails apache_age:install` at any time to repair the schema file as needed.**
|
224
79
|
|
225
|
-
|
226
|
-
# app/graphs/edges/has_job.rb
|
227
|
-
module Edges
|
228
|
-
class HasJob
|
229
|
-
include ApacheAge::Entities::Edge
|
230
|
-
|
231
|
-
attribute :employee_role, :string
|
232
|
-
attribute :start_node, :person
|
233
|
-
attribute :end_node, :company
|
234
|
-
|
235
|
-
validates :employee_role, presence: true
|
236
|
-
validate :validate_unique
|
237
|
-
# or with a one-liner
|
238
|
-
# validates_with(
|
239
|
-
# ApacheAge::Validators::UniqueEdgeValidator,
|
240
|
-
# attributes: %i[employee_role start_node end_node]
|
241
|
-
# )
|
242
|
-
|
243
|
-
private
|
244
|
-
|
245
|
-
def validate_unique
|
246
|
-
ApacheAge::Validators::UniqueEdgeValidator
|
247
|
-
.new(attributes: %i[employee_role start_node end_node])
|
248
|
-
.validate(self)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
```
|
80
|
+
For now, if you are using `db/structure.sql` you will need to manually configure Apache Age (RailsAge) as described below.
|
253
81
|
|
254
82
|
### Rails Console Usage
|
255
83
|
|
256
84
|
```ruby
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
```
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
# Ensure the files are loaded
|
294
|
-
require_dependency 'nodes/company'
|
295
|
-
require_dependency 'nodes/person'
|
296
|
-
|
297
|
-
# Register the custom types
|
298
|
-
ActiveModel::Type.register(:company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company))
|
299
|
-
ActiveModel::Type.register(:person, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Person))
|
300
|
-
end
|
301
|
-
```
|
302
|
-
|
303
|
-
### Controller Usage
|
304
|
-
|
305
|
-
```ruby
|
306
|
-
# app/controllers/people_controller.rb
|
307
|
-
class PeopleController < ApplicationController
|
308
|
-
before_action :set_person, only: %i[show edit update destroy]
|
309
|
-
|
310
|
-
# GET /people or /people.json
|
311
|
-
def index
|
312
|
-
@people = Nodes::Person.all
|
313
|
-
end
|
314
|
-
|
315
|
-
# GET /people/1 or /people/1.json
|
316
|
-
def show; end
|
317
|
-
|
318
|
-
# GET /people/new
|
319
|
-
def new
|
320
|
-
@person = Nodes::Person.new
|
321
|
-
end
|
322
|
-
|
323
|
-
# GET /people/1/edit
|
324
|
-
def edit; end
|
325
|
-
|
326
|
-
# POST /people or /people.json
|
327
|
-
def create
|
328
|
-
@person = Nodes::Person.new(**person_params)
|
329
|
-
respond_to do |format|
|
330
|
-
if @person.save
|
331
|
-
format.html { redirect_to person_url(@person), notice: 'Person was successfully created.' }
|
332
|
-
format.json { render :show, status: :created, location: @person }
|
333
|
-
else
|
334
|
-
format.html { render :new, status: :unprocessable_entity }
|
335
|
-
format.json { render json: @person.errors, status: :unprocessable_entity }
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
# PATCH/PUT /people/1 or /people/1.json
|
341
|
-
def update
|
342
|
-
respond_to do |format|
|
343
|
-
if @person.update(**person_params)
|
344
|
-
format.html { redirect_to person_url(@person), notice: 'Person was successfully updated.' }
|
345
|
-
format.json { render :show, status: :ok, location: @person }
|
346
|
-
else
|
347
|
-
format.html { render :edit, status: :unprocessable_entity }
|
348
|
-
format.json { render json: @person.errors, status: :unprocessable_entity }
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
# DELETE /people/1 or /people/1.json
|
354
|
-
def destroy
|
355
|
-
@person.destroy!
|
356
|
-
|
357
|
-
respond_to do |format|
|
358
|
-
format.html { redirect_to people_url, notice: 'Person was successfully destroyed.' }
|
359
|
-
format.json { head :no_content }
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
|
-
private
|
364
|
-
|
365
|
-
# Use callbacks to share common setup or constraints between actions.
|
366
|
-
def set_person
|
367
|
-
@person = Nodes::Person.find(params[:id])
|
368
|
-
end
|
369
|
-
|
370
|
-
# Only allow a list of trusted parameters through.
|
371
|
-
def person_params
|
372
|
-
# params.fetch(:person, {})
|
373
|
-
params.require(:nodes_person).permit(:first_name, :last_name, :nick_name, :given_name, :gender)
|
374
|
-
end
|
375
|
-
end
|
376
|
-
```
|
377
|
-
|
378
|
-
### Views
|
379
|
-
|
380
|
-
```erb
|
381
|
-
|
382
|
-
```
|
85
|
+
bin/rails c
|
86
|
+
|
87
|
+
fred = Person.new(first_name: 'Fredrick Jay', last_name: 'Flintstone')
|
88
|
+
fred.valid?
|
89
|
+
fred.save
|
90
|
+
fred.to_h # should have an ID
|
91
|
+
|
92
|
+
# fails because of a missing required field (Property)
|
93
|
+
incomplete = Person.new(first_name: 'Fredrick Jay')
|
94
|
+
incomplete.valid?
|
95
|
+
incomplete.errors
|
96
|
+
incomplete.to_h
|
97
|
+
|
98
|
+
# fails because of uniqueness constraints
|
99
|
+
jay = Person.create(first_name: 'Fredrick Jay', last_name: 'Flintstone')
|
100
|
+
jay.to_h
|
101
|
+
=> {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
|
102
|
+
jay.valid?
|
103
|
+
=> false
|
104
|
+
jay.errors
|
105
|
+
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=record not unique, options={}>, #<ActiveModel::Error attribute=first_name, type=property combination not unique, options={}>, #<ActiveModel::Error attribute=last_name, type=property combination not unique, options={}>]>
|
106
|
+
irb(main):008> jav.to_h
|
107
|
+
=> {:id=>nil, :first_name=>"Fredrick Jay", :last_name=>"Flintstone"}
|
108
|
+
|
109
|
+
# .create is a shortcut for .new and .save
|
110
|
+
quarry = Company.create(company_name: 'Bedrock Quarry')
|
111
|
+
quarry.to_h # should have an ID
|
112
|
+
|
113
|
+
# create an edge (no generator yet)
|
114
|
+
job = HasJob.create(start_node: fred, end_node: quarry, employee_role: 'Crane Operator')
|
115
|
+
job.to_h # should have an ID
|
116
|
+
```
|
117
|
+
|
118
|
+
## Manual Install, Config and Usage
|
119
|
+
|
120
|
+
see [Manuel Installation, Configuration and Usage](MANUAL_INSTALL.md)
|
@@ -11,9 +11,9 @@ module ApacheAge
|
|
11
11
|
attribute :id, :integer
|
12
12
|
attribute :end_id, :integer
|
13
13
|
attribute :start_id, :integer
|
14
|
-
#
|
15
|
-
attribute :end_node
|
16
|
-
attribute :start_node
|
14
|
+
# type: `:vertex` can be overriden with a specific node type
|
15
|
+
attribute :end_node, :vertex
|
16
|
+
attribute :start_node, :vertex
|
17
17
|
|
18
18
|
validates :end_node, :start_node, presence: true
|
19
19
|
validate :validate_nodes
|
@@ -35,10 +35,10 @@ module ApacheAge
|
|
35
35
|
query = record.class.find_by(edge_attribs.compact)
|
36
36
|
return if query.blank? || (query.id == record.id)
|
37
37
|
|
38
|
-
record.errors.add(:base, '
|
39
|
-
record.errors.add(:end_node, '
|
40
|
-
record.errors.add(:start_node, '
|
41
|
-
attributes.each { record.errors.add(_1, '
|
38
|
+
record.errors.add(:base, 'record not unique')
|
39
|
+
record.errors.add(:end_node, 'node combination not unique')
|
40
|
+
record.errors.add(:start_node, 'node combination not unique')
|
41
|
+
attributes.each { record.errors.add(_1, 'prpoerty combination not unique') }
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -20,8 +20,8 @@ module ApacheAge
|
|
20
20
|
# if no match is found or if it finds itself, it's valid
|
21
21
|
return if query.blank? || (query.id == record.id)
|
22
22
|
|
23
|
-
record.errors.add(:base, '
|
24
|
-
attributes.each { record.errors.add(_1, '
|
23
|
+
record.errors.add(:base, 'record not unique')
|
24
|
+
attributes.each { record.errors.add(_1, 'property combination not unique') }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -14,11 +14,13 @@ module ApacheAge
|
|
14
14
|
|
15
15
|
def create_node_file
|
16
16
|
template "node.rb.tt", File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
|
17
|
+
add_type_config
|
17
18
|
end
|
18
19
|
|
19
20
|
def destroy_node_file
|
20
21
|
file_path = File.join(destination_root, "app/nodes", class_path, "#{file_name}.rb")
|
21
22
|
File.delete(file_path) if File.exist?(file_path)
|
23
|
+
remove_type_config
|
22
24
|
end
|
23
25
|
|
24
26
|
def attributes_list
|
@@ -52,5 +54,41 @@ module ApacheAge
|
|
52
54
|
"#{' ' * (parent_module.split('::').length - 1 - index)}end"
|
53
55
|
end.join("\n") + "\n"
|
54
56
|
end
|
57
|
+
|
58
|
+
def add_type_config
|
59
|
+
return unless File.exist?(types_config_file)
|
60
|
+
|
61
|
+
types_content = File.read(types_config_file)
|
62
|
+
types_content.sub!(/^end\s*$/, "#{new_type_content}end")
|
63
|
+
File.open(types_config_file, 'w') { |file| file.write(types_content) }
|
64
|
+
puts " modified: config/initializers/types.rb"
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove_type_config
|
68
|
+
return unless File.exist?(types_config_file)
|
69
|
+
|
70
|
+
type_to_remove = new_type_content
|
71
|
+
|
72
|
+
types_content = File.read(types_config_file)
|
73
|
+
types_content.gsub!(type_to_remove, '')
|
74
|
+
File.open(types_config_file, 'w') { |file| file.write(types_content) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def types_config_file = File.join(Rails.root, 'config/initializers/types.rb')
|
78
|
+
|
79
|
+
def new_type_content
|
80
|
+
file_path = [class_path, file_name].reject(&:blank?).join('/').downcase
|
81
|
+
node_namespace = class_path.map(&:capitalize).join('::')
|
82
|
+
node_class_name = file_name.split('_').map(&:capitalize).join
|
83
|
+
node_namespaced_class = [node_namespace, node_class_name].reject(&:blank?).join('::')
|
84
|
+
type_name = [class_path.join('_'), file_name].reject(&:blank?).join('_')
|
85
|
+
content =
|
86
|
+
<<-RUBY
|
87
|
+
require_dependency '#{file_path}'
|
88
|
+
ActiveModel::Type.register(
|
89
|
+
:#{type_name}, ApacheAge::Types::AgeTypeGenerator.create_type_for(#{node_namespaced_class})
|
90
|
+
)
|
91
|
+
RUBY
|
92
|
+
end
|
55
93
|
end
|
56
94
|
end
|
data/lib/rails_age/version.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
namespace :apache_age do
|
5
5
|
desc "Ensure the database.yml file is properly configured for Apache Age"
|
6
|
-
task :
|
6
|
+
task :config_database => :environment do
|
7
7
|
|
8
8
|
db_config_file = File.expand_path("#{Rails.root}/config/database.yml", __FILE__)
|
9
9
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# namespace :apache_age do
|
2
|
+
# desc "Ensure db:migrate is followed by apache_age:config_schema"
|
3
|
+
# task :config_migrate do
|
4
|
+
# bin_rails_path = File.expand_path('../../../bin/rails', __FILE__)
|
5
|
+
# migration_hook =
|
6
|
+
# <<-RUBY
|
7
|
+
# # ensure db:migrate is followed with: `Rake::Task["apache_age:config_schema"].invoke`
|
8
|
+
# # which repairs the schema.rb file after the migration mangles it
|
9
|
+
# require_relative '../config/boot'
|
10
|
+
# require 'rails/commands'
|
11
|
+
|
12
|
+
# if ARGV.first == 'db:migrate'
|
13
|
+
# require 'rake'
|
14
|
+
# Rails.application.load_tasks
|
15
|
+
# Rake::Task['db:migrate'].invoke
|
16
|
+
# Rake::Task["apache_age:config_schema"].invoke
|
17
|
+
# else
|
18
|
+
# Rake::Task['rails:commands'].invoke(*ARGV)
|
19
|
+
# end
|
20
|
+
# RUBY
|
21
|
+
|
22
|
+
# # Read the current content of the bin/rails file
|
23
|
+
# bin_rails_content = File.read(bin_rails_path)
|
24
|
+
|
25
|
+
# # Check if the migration hook is already present
|
26
|
+
# unless bin_rails_content.include?('Rake::Task["apache_age:config_schema"].invoke')
|
27
|
+
# # Append the migration hook to the end of the bin/rails file
|
28
|
+
# File.open(bin_rails_path, 'a') do |file|
|
29
|
+
# file.write(migration_hook)
|
30
|
+
# end
|
31
|
+
# puts "Migration hook added to bin/rails."
|
32
|
+
# else
|
33
|
+
# puts "Migration hook already present in bin/rails."
|
34
|
+
# end
|
35
|
+
# end
|
@@ -3,8 +3,7 @@
|
|
3
3
|
#
|
4
4
|
namespace :apache_age do
|
5
5
|
desc "Copy migrations from rails_age to application and update schema"
|
6
|
-
task :
|
7
|
-
# source_schema = File.expand_path('../../../db/schema.rb', __FILE__)
|
6
|
+
task :config_schema => :environment do
|
8
7
|
destination_schema = File.expand_path("#{Rails.root}/db/schema.rb", __FILE__)
|
9
8
|
|
10
9
|
unless File.exist?(destination_schema)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# lib/tasks/install.rake
|
2
|
+
# Usage: `rake apache_age:config_types`
|
3
|
+
#
|
4
|
+
namespace :apache_age do
|
5
|
+
desc "Install AGE types from rails_age into the rails initializers"
|
6
|
+
task :config_types => :environment do
|
7
|
+
types_file_path = File.expand_path("#{Rails.root}/config/initializers/types.rb", __FILE__)
|
8
|
+
required_file_path = "require 'apache_age/types/age_type_generator'"
|
9
|
+
required_file_content =
|
10
|
+
<<~RUBY
|
11
|
+
require 'apache_age/types/age_type_generator'
|
12
|
+
# AGE Type Definition Usage (edges/nodes):
|
13
|
+
# require_dependency 'nodes/company'
|
14
|
+
# ActiveModel::Type.register(
|
15
|
+
# :company, ApacheAge::Types::AgeTypeGenerator.create_type_for(Nodes::Company)
|
16
|
+
# )
|
17
|
+
RUBY
|
18
|
+
node_type_content =
|
19
|
+
<<-RUBY
|
20
|
+
require_dependency 'apache_age/entities/vertex'
|
21
|
+
ActiveModel::Type.register(
|
22
|
+
:vertex, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Vertex)
|
23
|
+
)
|
24
|
+
RUBY
|
25
|
+
edge_type_content =
|
26
|
+
<<-RUBY
|
27
|
+
require_dependency 'apache_age/entities/edge'
|
28
|
+
ActiveModel::Type.register(
|
29
|
+
:edge, ApacheAge::Types::AgeTypeGenerator.create_type_for(ApacheAge::Entities::Edge)
|
30
|
+
)
|
31
|
+
RUBY
|
32
|
+
|
33
|
+
unless File.exist?(types_file_path)
|
34
|
+
source_content =
|
35
|
+
<<~RUBY
|
36
|
+
# config/initializers/types.rb
|
37
|
+
|
38
|
+
#{required_file_content}
|
39
|
+
Rails.application.config.to_prepare do
|
40
|
+
# Register AGE types
|
41
|
+
#{node_type_content}
|
42
|
+
#{edge_type_content}
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
File.open(types_file_path, 'w') { |file| file.write(source_content) }
|
46
|
+
puts "config/initializers/types.rb file created with AGE base types"
|
47
|
+
else
|
48
|
+
destination_content = File.read(types_file_path)
|
49
|
+
original_content = destination_content.dup
|
50
|
+
|
51
|
+
unless destination_content.include?(required_file_path)
|
52
|
+
destination_content.sub!(
|
53
|
+
/^(\s*Rails\.application\.config\.to_prepare do\n)/,
|
54
|
+
"#{required_file_content}\n\\1"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
unless destination_content.include?('# Register AGE types')
|
59
|
+
destination_content.sub!(
|
60
|
+
/^(\s*Rails\.application\.config\.to_prepare do\n)/,
|
61
|
+
"\\1 # Register AGE types\n"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
unless destination_content.include?(edge_type_content)
|
66
|
+
destination_content.sub!(
|
67
|
+
/^(\s*Rails\.application\.config\.to_prepare do\n # Register AGE types\n)/,
|
68
|
+
"\\1#{edge_type_content}"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
unless destination_content.include?(node_type_content)
|
73
|
+
destination_content.sub!(
|
74
|
+
/^(\s*Rails\.application\.config\.to_prepare do\n # Register AGE types\n)/,
|
75
|
+
"\\1#{node_type_content}"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
if destination_content != original_content
|
80
|
+
File.open(types_file_path, 'w') { |file| file.write(destination_content) }
|
81
|
+
puts "modified: config/initializers/types.rb"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/tasks/install.rake
CHANGED
@@ -10,10 +10,17 @@ namespace :apache_age do
|
|
10
10
|
# run any new migrations
|
11
11
|
Rake::Task["db:migrate"].invoke
|
12
12
|
|
13
|
+
# ensure the config/database.yml file has the proper configurations
|
14
|
+
Rake::Task["apache_age:config_database"].invoke
|
15
|
+
|
13
16
|
# adjust the schema file (unfortunately rails mangles the schema file)
|
14
|
-
Rake::Task["apache_age:
|
17
|
+
Rake::Task["apache_age:config_schema"].invoke
|
15
18
|
|
16
|
-
# ensure the config/
|
17
|
-
Rake::Task["apache_age:
|
19
|
+
# ensure the config/initializers/types.rb file has the base AGE Types
|
20
|
+
Rake::Task["apache_age:config_types"].invoke
|
21
|
+
|
22
|
+
# # ensure bin/rails db:migrate is always followed with
|
23
|
+
# # `Rake::Task["apache_age:config_schema"].invoke` to ensure the schema isn't mangled
|
24
|
+
# Rake::Task["apache_age:config_migrate"].invoke
|
18
25
|
end
|
19
26
|
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.
|
4
|
+
version: 0.4.0
|
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-
|
11
|
+
date: 2024-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -85,11 +85,13 @@ files:
|
|
85
85
|
- lib/rails_age.rb
|
86
86
|
- lib/rails_age/engine.rb
|
87
87
|
- lib/rails_age/version.rb
|
88
|
+
- lib/tasks/config_database.rake
|
89
|
+
- lib/tasks/config_migrate.rake
|
90
|
+
- lib/tasks/config_schema.rake
|
91
|
+
- lib/tasks/config_types.rake
|
88
92
|
- lib/tasks/copy_migrations.rake
|
89
|
-
- lib/tasks/database_config.rake
|
90
|
-
- lib/tasks/install.original.rake
|
91
93
|
- lib/tasks/install.rake
|
92
|
-
- lib/tasks/
|
94
|
+
- lib/tasks/migrate.rake
|
93
95
|
homepage: https://github.com/marpori/rails_age
|
94
96
|
licenses:
|
95
97
|
- MIT
|
@@ -1,257 +0,0 @@
|
|
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
|