rails_age 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ module ApacheAge
2
+ module Entities
3
+ class Entity
4
+ class << self
5
+ def find_by(attributes)
6
+ where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
7
+ handle_find(where_clause)
8
+ end
9
+
10
+ def find(id)
11
+ where_clause = "id(find) = #{id}"
12
+ handle_find(where_clause)
13
+ end
14
+
15
+ private
16
+
17
+ def age_graph = 'age_schema'
18
+
19
+ def handle_find(where_clause)
20
+ # try to find a vertex
21
+ match_node = '(find)'
22
+ cypher_sql = find_sql(match_node, where_clause)
23
+ age_response = execute_find(cypher_sql)
24
+
25
+ if age_response.nil?
26
+ # if not a vertex try to find an edge
27
+ match_edge = '()-[find]->()'
28
+ cypher_sql = find_sql(match_edge, where_clause)
29
+ age_response = execute_find(cypher_sql)
30
+ return nil if age_response.nil?
31
+ end
32
+
33
+ instantiate_result(age_response)
34
+ end
35
+
36
+ def execute_find(cypher_sql)
37
+ age_result = ActiveRecord::Base.connection.execute(cypher_sql)
38
+ return nil if age_result.values.first.nil?
39
+
40
+ age_result
41
+ end
42
+
43
+ def instantiate_result(age_response)
44
+ age_type = age_response.values.first.first.split('::').last
45
+ json_string = age_response.values.first.first.split('::').first
46
+ json_data = JSON.parse(json_string)
47
+
48
+ age_label = json_data['label']
49
+ attribs = json_data.except('label', 'properties')
50
+ .merge(json_data['properties'])
51
+ .symbolize_keys
52
+
53
+ "#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
54
+ end
55
+
56
+ def find_sql(match_clause, where_clause)
57
+ <<-SQL
58
+ SELECT *
59
+ FROM cypher('#{age_graph}', $$
60
+ MATCH #{match_clause}
61
+ WHERE #{where_clause}
62
+ RETURN find
63
+ $$) as (found agtype);
64
+ SQL
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ module ApacheAge
2
+ module Entities
3
+ module Vertex
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Model
8
+ include ActiveModel::Dirty
9
+ include ActiveModel::Attributes
10
+
11
+ attribute :id, :integer
12
+
13
+ extend ApacheAge::Entities::ClassMethods
14
+ include ApacheAge::Entities::CommonMethods
15
+ end
16
+
17
+ def age_type = 'vertex'
18
+
19
+ # AgeSchema::Nodes::Company.create(company_name: 'Bedrock Quarry')
20
+ # SELECT *
21
+ # FROM cypher('age_schema', $$
22
+ # CREATE (company:Company {company_name: 'Bedrock Quarry'})
23
+ # RETURN company
24
+ # $$) as (Company agtype);
25
+ def create_sql
26
+ alias_name = age_alias || age_label.downcase
27
+ <<-SQL
28
+ SELECT *
29
+ FROM cypher('#{age_graph}', $$
30
+ CREATE (#{alias_name}#{self})
31
+ RETURN #{alias_name}
32
+ $$) as (#{age_label} agtype);
33
+ SQL
34
+ end
35
+
36
+ # So far just properties of string type with '' around them
37
+ def update_sql
38
+ alias_name = age_alias || age_label.downcase
39
+ set_caluse =
40
+ age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
41
+ <<-SQL
42
+ SELECT *
43
+ FROM cypher('#{age_graph}', $$
44
+ MATCH (#{alias_name}:#{age_label})
45
+ WHERE id(#{alias_name}) = #{id}
46
+ SET #{set_caluse}
47
+ RETURN #{alias_name}
48
+ $$) as (#{age_label} agtype);
49
+ SQL
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ # lib/apache_age/types/age_type_generator.rb
2
+ # Automatically generates ActiveModel::Type classes
3
+ # Dynamically builds this (as a concrete example):
4
+ # module ApacheAge
5
+ # module Types
6
+ # class CompanyType < ActiveModel::Type::Value
7
+ # def cast(value)
8
+ # case value
9
+ # when Nodes::Company
10
+ # value
11
+ # when Hash
12
+ # Nodes::Company.new(value)
13
+ # else
14
+ # nil
15
+ # end
16
+ # end
17
+ # def serialize(value)
18
+ # value.is_a?(Nodes::Company) ? value.attributes : nil
19
+ # end
20
+ # end
21
+ # end
22
+ # end
23
+ module ApacheAge
24
+ module Types
25
+ class AgeTypeGenerator
26
+ def self.create_type_for(klass)
27
+ Class.new(ActiveModel::Type::Value) do
28
+ define_method(:cast) do |value|
29
+ case value
30
+ when klass
31
+ value
32
+ when Hash
33
+ klass.new(value)
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
39
+ define_method(:serialize) do |value|
40
+ value.is_a?(klass) ? value.attributes : nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ # Usage (within an Age Model)
2
+ # validates_with UniqueEdgeValidator, attributes: [:employee_role, :start_node, :end_node]
3
+ # validates_with UniqueEdgeValidator, attributes: [:start_id, :employee_role, :end_id]
4
+ #
5
+ module ApacheAge
6
+ module Validators
7
+ class UniqueEdgeValidator < ActiveModel::Validator
8
+ def validate(record)
9
+ allowed_keys = record.age_properties.keys
10
+ attributes = options[:attributes] || []
11
+
12
+ edge_attribs =
13
+ attributes
14
+ .map { |attr| [attr, record.send(attr)] }.to_h
15
+ .symbolize_keys
16
+ .slice(*allowed_keys)
17
+
18
+ possible_end_keys = [:end_id, 'end_id', :end_node, 'end_node']
19
+ end_query =
20
+ if possible_end_keys.any? { |key| attributes.include?(key) }
21
+ end_query = query_node(record.end_node)
22
+ edge_attribs[:end_id] = end_query&.id
23
+ end_query
24
+ end
25
+
26
+ possible_start_keys = [:start_id, 'start_id', :start_node, 'start_node']
27
+ start_query =
28
+ if possible_start_keys.any? { |key| attributes.include?(key) }
29
+ start_query = query_node(record.start_node)
30
+ edge_attribs[:start_id] = start_query&.id
31
+ start_query
32
+ end
33
+ return if attributes.blank? && (end_query.blank? || start_query.blank?)
34
+
35
+ query = record.class.find_by(edge_attribs.compact)
36
+ return if query.blank? || (query.id == record.id)
37
+
38
+ record.errors.add(:base, 'attribute combination not unique')
39
+ record.errors.add(:end_node, 'attribute combination not unique')
40
+ record.errors.add(:start_node, 'attribute combination not unique')
41
+ attributes.each { record.errors.add(_1, 'attribute combination not unique') }
42
+ end
43
+
44
+ private
45
+
46
+ def query_node(node)
47
+ return nil if node.blank?
48
+
49
+ node.persisted? ? node.class.find(node.id) : node.class.find_by(node.age_properties)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ # Usage (within an Age Model)
2
+ # validates_with UniqueVertexValidator, attributes: [:first_name, :last_name, :gender]
3
+
4
+ # lib/apache_age/validators/unique_vertex_validator.rb
5
+ module ApacheAge
6
+ module Validators
7
+ class UniqueVertexValidator < ActiveModel::Validator
8
+ def validate(record)
9
+ allowed_keys = record.age_properties.keys
10
+ attributes = options[:attributes]
11
+ return if attributes.blank?
12
+
13
+ record_attribs =
14
+ attributes
15
+ .map { |attr| [attr, record.send(attr)] }
16
+ .to_h.symbolize_keys
17
+ .slice(*allowed_keys)
18
+ query = record.class.find_by(record_attribs)
19
+
20
+ # if no match is found or if it finds itself, it's valid
21
+ return if query.blank? || (query.id == record.id)
22
+
23
+ record.errors.add(:base, 'attribute combination not unique')
24
+ attributes.each { record.errors.add(_1, 'attribute combination not unique') }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module ApacheAge
2
+ module VertexTypeValidator
3
+ def vertex_attribute(attribute_name, type_symbol, klass)
4
+ attribute attribute_name, type_symbol
5
+
6
+ validate do
7
+ value = send(attribute_name)
8
+ unless value.is_a?(klass)
9
+ errors.add(attribute_name, "must be a #{klass.name}")
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = "0.1.0"
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/rails_age.rb CHANGED
@@ -1,14 +1,17 @@
1
- require "rails_age/version"
2
- require "rails_age/engine"
1
+ require 'rails_age/version'
2
+ require 'rails_age/engine'
3
3
 
4
4
  module RailsAge
5
5
  # Your code goes here...
6
6
  end
7
7
 
8
8
  module ApacheAge
9
- require "apache_age/class_methods"
10
- require "apache_age/common_methods"
11
- require "apache_age/edge"
12
- require "apache_age/entity"
13
- require "apache_age/vertex"
9
+ require 'apache_age/entities/class_methods'
10
+ require 'apache_age/entities/common_methods'
11
+ require 'apache_age/entities/edge'
12
+ require 'apache_age/entities/entity'
13
+ require 'apache_age/entities/vertex'
14
+ require 'apache_age/types/age_type_generator'
15
+ require 'apache_age/validators/unique_edge_validator'
16
+ require 'apache_age/validators/unique_vertex_validator'
14
17
  end
@@ -0,0 +1,210 @@
1
+ # lib/tasks/install.rake
2
+ # Usage: `rake apache_age:install`
3
+ #
4
+ namespace :apache_age do
5
+ desc "Copy migrations from rails_age to application and update schema"
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__)
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..."
74
+ 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
+
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
206
+
207
+ # Write the modified lines back to the file
208
+ File.open(db_config_file, 'w') { |file| file.write(lines.join) }
209
+ end
210
+ 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.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill Tihen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-21 00:00:00.000000000 Z
11
+ date: 2024-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,29 +16,36 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.3.2
19
+ version: '7.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: 7.1.3.2
29
+ version: '7.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9.0'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rspec-rails
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - ">="
37
+ - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
39
+ version: '6.0'
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
- - - ">="
44
+ - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '0'
41
- description: Apache AGE plugin for Rails 7.1
46
+ version: '6.0'
47
+ description: This plugin integrates Apache AGE for PostgreSQL with Rails 7.x, providing
48
+ tools and helpers for working with graph databases within a Rails application.
42
49
  email:
43
50
  - btihen@gmail.com
44
51
  executables: []
@@ -58,23 +65,28 @@ files:
58
65
  - app/models/rails_age/application_record.rb
59
66
  - app/views/layouts/rails_age/application.html.erb
60
67
  - config/routes.rb
61
- - db/migrate/20240521062349_configure_apache_age.rb
68
+ - db/migrate/20240521062349_add_apache_age.rb
62
69
  - db/schema.rb
63
- - lib/apache_age/class_methods.rb
64
- - lib/apache_age/common_methods.rb
65
- - lib/apache_age/edge.rb
66
- - lib/apache_age/entity.rb
67
- - lib/apache_age/vertex.rb
70
+ - lib/apache_age/entities/class_methods.rb
71
+ - lib/apache_age/entities/common_methods.rb
72
+ - lib/apache_age/entities/edge.rb
73
+ - lib/apache_age/entities/entity.rb
74
+ - lib/apache_age/entities/vertex.rb
75
+ - lib/apache_age/types/age_type_generator.rb
76
+ - lib/apache_age/validators/unique_edge_validator.rb
77
+ - lib/apache_age/validators/unique_vertex_validator.rb
78
+ - lib/apache_age/validators/vertex_type_validator.rb
68
79
  - lib/rails_age.rb
69
80
  - lib/rails_age/engine.rb
70
81
  - lib/rails_age/version.rb
82
+ - lib/tasks/install.rake
71
83
  - lib/tasks/rails_age_tasks.rake
72
84
  homepage: https://github.com/marpori/rails_age
73
85
  licenses:
74
86
  - MIT
75
87
  metadata:
76
88
  homepage_uri: https://github.com/marpori/rails_age
77
- source_code_uri: https://github.com/marpori/rails_age
89
+ source_code_uri: https://github.com/marpori/rails_age/blob/main
78
90
  changelog_uri: https://github.com/marpori/rails_age/blob/main/CHANGELOG.md
79
91
  post_install_message:
80
92
  rdoc_options: []
@@ -84,15 +96,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
96
  requirements:
85
97
  - - ">="
86
98
  - !ruby/object:Gem::Version
87
- version: '0'
99
+ version: '3.0'
88
100
  required_rubygems_version: !ruby/object:Gem::Requirement
89
101
  requirements:
90
102
  - - ">="
91
103
  - !ruby/object:Gem::Version
92
104
  version: '0'
93
105
  requirements: []
94
- rubygems_version: 3.5.9
106
+ rubygems_version: 3.5.10
95
107
  signing_key:
96
108
  specification_version: 4
97
- summary: Apache AGE plugin for Rails 7.1
109
+ summary: Apache AGE plugin for Rails 7.x
98
110
  test_files: []