rails_age 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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