rails_age 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa956147280cd1ef2125eb39f3dd7867eadafde15806d95b90dab30d1290b135
4
- data.tar.gz: a5b3a61d2fcf3026f9503b1bb4a9c6e60c6317f92266501f2890c2a02af1a01d
3
+ metadata.gz: dc953f22e786b1ce4280d8ced0bcb6ca768f89773c49a4ad0d7ddd3e3505e91e
4
+ data.tar.gz: fff1d2809ce6f439196bfbc8a50b7990453d17def541829ef332ee2711a7fd3a
5
5
  SHA512:
6
- metadata.gz: a2dbb2a72e8f64056bf6531fab29da715bdc89d1748dce2fe92a44de11b5a8c74dbc354c18a903472c996c90e01f7947c921819d3cded09b4ffbfe1cbd0da33c
7
- data.tar.gz: d5265daf4c4a1928bdd8f7f08a1efd884f8c4ce5da38d56321f58f6f507905a24d7587d2f92bacfe5a4141e500f7f81060726a2723368b443d8892bf2e66a9b3
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.1 - 2024-xx-xx
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
- * refactored into multiple independent tasks?
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 add the db/schema.rb to your git repository because `rails db:migrate` will inappropriately modify the schema file. However, you can run `bin/rails apache_age:install` at any time to repair the schema file if needed.
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 "plpgsql"
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: I like to add the schema.rb to git so that it is easy to revert the unwanted changes and keep the desired changes.
126
- ALSO note that running: `bin/rails apache_age:install` will check and non-destructively repair any config files at any time (however a git commit before hand as a backup is a good idea incase something goes wrong!)
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 MR and tests and I will review it.
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 full sample app can be found [here](https://github.com/marpori/rails_age_demo_app) the summary usage is described below.
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 :gender, :first_name, :last_name, :given_name, :nick_name,
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 "plpgsql"
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
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -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
@@ -2,209 +2,18 @@
2
2
  # Usage: `rake apache_age:install`
3
3
  #
4
4
  namespace :apache_age do
5
- desc "Copy migrations from rails_age to application and update schema"
5
+ desc "Install & configure Apache Age within Rails (updates migrations, schema & database.yml)"
6
6
  task :install => :environment do
7
- source_schema = File.expand_path('../../../db/schema.rb', __FILE__)
8
- destination_schema = File.expand_path("#{Rails.root}/db/schema.rb", __FILE__)
7
+ # copy our migrations to the application (if needed)
8
+ Rake::Task["apache_age:copy_migrations"].invoke
9
9
 
10
- # ensure we have a schema file
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
- def create_new_schema(last_migration_version, destination_schema, source_schema)
90
- if File.exist?(source_schema) && File.exist?(destination_schema)
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
- # Write the modified lines back to the file
208
- File.open(db_config_file, 'w') { |file| file.write(lines.join) }
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.0
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-05-28 00:00:00.000000000 Z
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/rails_age_tasks.rake
92
+ - lib/tasks/schema_config.rake
84
93
  homepage: https://github.com/marpori/rails_age
85
94
  licenses:
86
95
  - MIT
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :rails_age do
3
- # # Task goes here
4
- # end