migrate_factory_rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 00c651b60e4e45116ef9e831d30ba52a9e972609ce347b0675dc63c97148135a
4
+ data.tar.gz: 4844f07501e4fc303686f888fc7a3859ded076024799c49eaa0745cbf641def1
5
+ SHA512:
6
+ metadata.gz: f124a2f9a3dd54df5c161b5c214458d6e02a3aa50c9f01b516d89c25f3c6c60e8eddde09ef3e782d535403afb21165eb513904c72692ca45c66df04cd5d018b9
7
+ data.tar.gz: e655960d2a1e788a12d768dabc8ae3aa24acc040374782a87f2cf71a6f090a8095c0923a2299de7c1045f014b6ae534e28c539923e7dfcc281289962d3ae6d22
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-01-08
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Diogo Fernandes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Rails Migrate Factory
2
+ Completely rebuild your migrations to agilize setup and simplify schema
3
+
4
+ ## Install
5
+
6
+ `gem install migrate_factory_rails`
7
+
8
+ or add to your `Gemfile`
9
+
10
+ ```ruby
11
+ group :development do
12
+ gem 'migrate_factory_rails'
13
+ end
14
+ ```
15
+
16
+ ## How use
17
+
18
+ **Be careful, rebuild do not cover all situations yet then may miss some migration**
19
+ You can use two commands currently:
20
+
21
+ - `rake rebuild:migrations` -> Will rebuild all your migrations and save on `tmp/rebuild`, then you can validate if it worths without lose any data.
22
+ - `rake rebuild:cleanup` -> Will cleanup all data on `tmp/rebuild`.
23
+
24
+ ## Goals
25
+
26
+ My goal here was the challenge to rebuild every migrations from Rails, matching as close as possible the final `schema.rb` meanwhile reducing strongly the amount of migrations. Specially on old projects, there will be a lot ot migrations, sometimes for a single column or index which extends the setup for a few seconds. If is worth or not to use, feel free to discuss.
27
+
28
+ ## Author
29
+
30
+ * [Diogo](https://github.com/dfop02)
@@ -0,0 +1,4 @@
1
+ require 'migrate_factory_rails'
2
+
3
+ path = File.expand_path(__dir__)
4
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| import f }
@@ -0,0 +1,133 @@
1
+ class MigrateFactoryRails::Builder
2
+ DATABASE_TYPES = %w[
3
+ string text binary boolean date datetime time decimal timestamp
4
+ index float integer references has_attached_file attachment
5
+ ].freeze
6
+
7
+ def initialize(*table_name, **options)
8
+ build_database_type_methods
9
+ sanitized_table_name = sanitize_table_names(table_name)
10
+
11
+ @join_table = sanitized_table_name.length > 1
12
+ @table_name = @join_table ? sanitized_table_name.map(&:to_s).join('_') : sanitized_table_name.first.to_s
13
+ @table_opt = options
14
+ @table = { "#{@table_name}": {} }
15
+ end
16
+
17
+ attr_reader :table_name, :table
18
+
19
+ def print_columns(space: 6)
20
+ table_columns.map do |column_name, column_options|
21
+ type = column_options.delete(:type)
22
+ next if type == 'index' || type.nil?
23
+
24
+ options = column_options.map do |ck, cv|
25
+ next if ck == 'index'
26
+
27
+ if type == 'string' && cv == ''
28
+ "#{ck}: ''"
29
+ elsif type == 'string' && ck == :default
30
+ "#{ck}: '#{cv}'"
31
+ elsif cv.is_a?(Symbol)
32
+ "#{ck}: #{cv.inspect}"
33
+ else
34
+ "#{ck}: #{cv}"
35
+ end
36
+ end.compact.join(', ')
37
+
38
+ printed_column = "t.#{type}#{valid_column_name(column_name)}"
39
+ printed_column += ", #{options}" if options.present?
40
+ printed_column
41
+ end.compact.join("\n#{' ' * space}")
42
+ end
43
+
44
+ def print_indexes(space: 2)
45
+ indexed_columns = table_columns.select { |_, c_opt| c_opt[:type] == 'index' || c_opt[:index] }
46
+ return '' if indexed_columns.empty?
47
+
48
+ spaces = ' ' * space
49
+ indexed_columns.map do |column_name, _|
50
+ "add_index :#{table_name},#{valid_column_name(column_name)}"
51
+ end.join("\n#{spaces}").insert(0, "\n\n#{spaces}")
52
+ end
53
+
54
+ def print_table_name_and_options
55
+ return print_table_name if @table_opt.blank?
56
+
57
+ "#{print_table_name}, #{@table_opt.map { |key, value| print_options_key_value(key, value) }.join(', ') }"
58
+ end
59
+
60
+ def print_table_name
61
+ return ":#{table_name.join(', :')}" if @join_table
62
+
63
+ ":#{table_name}"
64
+ end
65
+
66
+ def print_options_key_value(key, value)
67
+ value = value.inspect if value.is_a?(Symbol)
68
+ "#{key}: #{value}"
69
+ end
70
+
71
+ def print_table_type
72
+ @join_table ? 'create_join_table' : 'create_table'
73
+ end
74
+
75
+ def build_database_type_methods
76
+ DATABASE_TYPES.each do |name|
77
+ define_singleton_method(name) do |*column_names, **column_options|
78
+ column_names.each do |col|
79
+ if __method__.to_s == 'references'
80
+ table_columns["#{col}_id".to_sym] = column_options.merge(type: :integer)
81
+ next
82
+ end
83
+
84
+ table_columns[col] = column_options.merge(type: __method__.to_s)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def change_column_default(table_name, column_name, default)
91
+ @table[table_name.to_sym][column_name.to_sym][:default] = default.is_a?(Hash) ? default[:to] : default
92
+ end
93
+
94
+ def change_column_null(table_name, column_name, null)
95
+ @table[table_name.to_sym][column_name.to_sym][:null] = null
96
+ end
97
+
98
+ def timestamps(**column_options)
99
+ table_columns[:timestamps] = column_options.merge(type: __method__.to_s)
100
+ end
101
+
102
+ def column(column_name, type, **column_options)
103
+ table_columns[column_name] = column_options.merge(type: type.to_s)
104
+ end
105
+
106
+ def remove_column(column_name)
107
+ table_columns.delete(column_name.to_sym)
108
+ end
109
+
110
+ def add_index(column_name, **column_options)
111
+ table_columns[column_name][:index] = column_options.present? ? column_options : true
112
+ end
113
+
114
+ def remove_index(column_name)
115
+ table_columns[column_name].delete(:index) if table_columns[column_name][:index].present?
116
+ end
117
+
118
+ private
119
+
120
+ def table_columns
121
+ @table[@table_name.to_sym]
122
+ end
123
+
124
+ def valid_column_name(column_name)
125
+ return '' if column_name.to_sym == :timestamps
126
+
127
+ " :#{column_name}"
128
+ end
129
+
130
+ def sanitize_table_names(table_names)
131
+ table_names.select { |tn| tn.is_a?(Symbol) }
132
+ end
133
+ end
@@ -0,0 +1,35 @@
1
+ require 'migrate_factory_rails/ui'
2
+
3
+ class MigrateFactoryException < StandardError
4
+ def initialize(table_name, process, backtrace)
5
+ super
6
+ @table_name = table_name
7
+ @process = process
8
+ @backtrace = backtrace
9
+ log_exception
10
+ end
11
+
12
+ def message
13
+ "\nOn table #{@table_name}, I do not know how deal with:\n#{@process}"
14
+ end
15
+
16
+ def print_msg(msg, color = nil)
17
+ MigrateFactoryRails::UI.print_message(msg, color)
18
+ end
19
+
20
+ def log_exception
21
+ print_separator
22
+
23
+ print_msg("🚨🚨🚨 #{self.class} 🚨🚨🚨", :red)
24
+ print_msg(message, :red)
25
+ print_msg("\nBacktrace:\n#{@backtrace}")
26
+
27
+ print_separator
28
+ end
29
+
30
+ def print_separator
31
+ puts "\n"
32
+ ENV['COLUMNS'].to_i.times { print '-' }
33
+ puts "\n"
34
+ end
35
+ end
@@ -0,0 +1,152 @@
1
+ require 'migrate_factory_rails/builder'
2
+ require 'migrate_factory_rails/exceptions'
3
+ require 'migrate_factory_rails/ui'
4
+
5
+ class MigrateFactoryRails::Factory
6
+ def initialize(migrate_content)
7
+ @migrate_content = migrate_content
8
+ @builder = create_table_from_migrate_content
9
+ @table_name = @builder&.table_name if @builder.present?
10
+ build_collection_methods
11
+ end
12
+
13
+ attr_reader :table_name
14
+
15
+ def create_table(table_name, **options)
16
+ @builder = MigrateFactoryRails::Builder.new(table_name, options)
17
+ yield @builder
18
+ @builder
19
+ end
20
+
21
+ def create_join_table(*table_name, **options)
22
+ @builder = MigrateFactoryRails::Builder.new(table_name, options)
23
+ yield @builder
24
+ @builder
25
+ end
26
+
27
+ def add_column(_table_name, column_name, type, **column_options)
28
+ @builder.send(type.to_s, *column_name, **column_options)
29
+ end
30
+
31
+ def remove_column(_table_name, column_name, _type = nil)
32
+ @builder.remove_column(column_name)
33
+ end
34
+
35
+ def change_column_default(table_name, column_name, default)
36
+ @builder.change_column_default(table_name, column_name, default)
37
+ end
38
+
39
+ def change_column_null(table_name, column_name, null)
40
+ @builder.change_column_null(table_name, column_name, null)
41
+ end
42
+
43
+ def add_attachment(table_name, *column_name, **column_options)
44
+ add_column(table_name, *column_name, :attachment, **column_options)
45
+ end
46
+
47
+ def remove_attachment(table_name, column_name, **_column_options)
48
+ remove_column(table_name, column_name, :attachment)
49
+ end
50
+
51
+ def add_index(_table_name, column_name, **column_options)
52
+ @builder.add_index(column_name, **column_options)
53
+ end
54
+
55
+ def remove_index(_table_name, column_name, **_column_options)
56
+ @builder.remove_index(column_name)
57
+ end
58
+
59
+ def collect_info_from_children_migrate_content(children_content)
60
+ collect_column(children_content) if has_add_or_remove_column?(children_content)
61
+ collect_index(children_content) if has_add_or_remove_index?(children_content)
62
+ collect_attachment(children_content) if has_add_or_remove_attachment?(children_content)
63
+ collect_change_column(children_content) if has_change_column?(children_content)
64
+ end
65
+
66
+ def rebuild_file_migration(file)
67
+ file.write(generate_new_file)
68
+ end
69
+
70
+ private
71
+
72
+ def table_name_from_migrate_content
73
+ @migrate_content[/create_table[\s+|(]:(.*?)\)?\s+/, 1]
74
+ end
75
+
76
+ def create_table_from_migrate_content
77
+ return eval(@migrate_content[/create_table(.*?)\W+end\W?$/m]) if has_create_table?(@migrate_content)
78
+ return eval(@migrate_content[/create_join_table(.*?)\W+end\W?$/m]) if has_create_join_table?(@migrate_content)
79
+
80
+ ''
81
+ rescue StandardError => e
82
+ MigrateFactoryRails::UI.print_message("\nError while rebuilding this table:\n", :red)
83
+ puts @migrate_content[/create_table(.*?)\W+end\W?$/m].squeeze.gsub(/ end/, 'end')
84
+ raise e
85
+ end
86
+
87
+ def has_create_table?(content)
88
+ content[/create_table(.*?)\W+end\W?$/m].present?
89
+ end
90
+
91
+ def has_create_join_table?(content)
92
+ content[/create_join_table(.*?)\W+end\W?$/m].present?
93
+ end
94
+
95
+ def build_collection_methods
96
+ %w[column index attachment].each do |action|
97
+ define_singleton_method("has_add_or_remove_#{action}?") do |children_content|
98
+ children_content[/(add|remove)_#{action}\s+:#{@table_name}/].present?
99
+ end
100
+
101
+ define_singleton_method("collect_#{action}") do |children_content|
102
+ block_pattern = /def\s+(change|up|self\.up)(.*?)\s+end/xm
103
+ command_pattern = /(?:add|remove)_(?:#{action})\s+:#{@table_name}.*/
104
+
105
+ columns_to_process = children_content.scan(block_pattern).flat_map do |_, body|
106
+ # Clean block to collect actions:
107
+ # Remove comment lines
108
+ # Remove extra spaces
109
+ # Add newline before all actions
110
+ # Remove only the first newline
111
+ body = body.gsub(/#\s*.*/, '').gsub(/\s+/, ' ').gsub(/((?:add|remove)_(?:\w+))/, "\n\\1").sub(/\s+\n/, '')
112
+ body.scan(command_pattern).map do |command|
113
+ # Define the regex pattern to match everything up to the first occurrence of a class/module name followed by '::' or '.'
114
+ pattern = /.*?(?=[A-Z][a-zA-Z0-9_]*.?:?)/
115
+ command = command.match(pattern).to_s if command.match(pattern)
116
+ command.squish
117
+ end
118
+ end
119
+
120
+ begin
121
+ columns_to_process.each do |process|
122
+ eval(process)
123
+ end
124
+ rescue StandardError => e
125
+ raise MigrateFactoryException.new(@table_name, process, e.backtrace[0, 10].join("\n"))
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def has_change_column?(children_content)
132
+ children_content[/change_column_(null|default).*/].present?
133
+ end
134
+
135
+ def collect_change_column(children_content)
136
+ columns_to_process = children_content.to_enum(:scan, /change_column_(null|default)\s+:#{@table_name}.*/).map { Regexp.last_match.to_s }
137
+ columns_to_process.each { |process| eval(process) }
138
+ end
139
+
140
+ def generate_new_file
141
+ rails_version = Rails.version[/\d\.\d/]
142
+ <<~END
143
+ class Create#{@table_name.classify.pluralize} < ActiveRecord::Migration[#{rails_version}]
144
+ def change
145
+ #{@builder.print_table_type}(#{@builder.print_table_name_and_options}) do |t|
146
+ #{@builder.print_columns}
147
+ end
148
+ end#{@builder.print_indexes}
149
+ end
150
+ END
151
+ end
152
+ end
@@ -0,0 +1,13 @@
1
+ require 'migrate_factory_rails'
2
+ require 'rails'
3
+
4
+ module MigrateFactoryRails
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :migrate_factory
7
+
8
+ rake_tasks do
9
+ path = File.expand_path(__dir__)
10
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ require 'migrate_factory_rails/factory'
2
+
3
+ namespace :rebuild do
4
+ desc 'Rebuild your migrations for agilize setup'
5
+ task :migrations, [:keep_last_n_migrations] => [:environment] do |_task, args|
6
+ include MigrateFactoryRails
7
+
8
+ args.with_defaults(keep_last_n_migrations: 5)
9
+
10
+ rebuild_path = Rails.root.join('tmp/rebuild/')
11
+ FileUtils.mkdir_p(rebuild_path)
12
+
13
+ begin
14
+ mgt_files = Dir.glob(Rails.root.join('db/migrate/*'))
15
+
16
+ if args.keep_last_n_migrations > mgt_files.length
17
+ invalid_text = "Invalid 'keep_last_n_migrations' options because your project has less than #{args.keep_last_n_migrations} migrations."
18
+ abort invalid_text
19
+ end
20
+
21
+ mgt_total_files = mgt_files[0..-args.keep_last_n_migrations].length
22
+ puts 'Starting rebuild migrations...'
23
+
24
+ mgt_files.each_with_index do |migrate_file, index|
25
+ factory_service = Factory.new(File.read(migrate_file))
26
+ next if factory_service.table_name.blank?
27
+
28
+ print "Working on #{factory_service.table_name} table..."
29
+ File.open(Rails.root.join("tmp/rebuild/#{factory_service.table_name}.rb"), 'a+') do |f|
30
+ mgt_files[index..-args.keep_last_n_migrations].each do |migrate_children|
31
+ factory_service.collect_info_from_children_migrate_content(File.read(migrate_children))
32
+ end
33
+
34
+ factory_service.rebuild_file_migration(f)
35
+ end
36
+
37
+ print "Done!\n"
38
+ end
39
+ rescue StandardError
40
+ @error = true
41
+ ensure
42
+ new_total_files = Dir.glob(Rails.root.join('tmp/rebuild/*')).length
43
+ abort 'Something goes wrong and the script abort' if new_total_files.zero? || @error
44
+
45
+ puts "\nStatistics:\n"
46
+ puts "Ignoring last #{args.keep_last_n_migrations} migrations, we had #{mgt_total_files} migrates reduced to #{new_total_files} migrates."
47
+ puts "Total #{((1-(new_total_files/mgt_total_files.to_f))*100).to_i}% reduction in migrations.."
48
+ end
49
+ end
50
+
51
+ desc 'Cleanup rebuild tasks folder'
52
+ task :cleanup do
53
+ rebuild_path = Rails.root.join('tmp/rebuild/')
54
+ print 'Cleaning...'
55
+ rebuild_path.children.each(&:unlink) if rebuild_path.exist?
56
+ print "Done!\n"
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ class MigrateFactoryRails::UI
2
+ class << self
3
+ def print_message(message, color = nil)
4
+ if color.nil?
5
+ puts message
6
+ return
7
+ end
8
+
9
+ puts color_msg(color.to_sym, message)
10
+ end
11
+
12
+ private
13
+
14
+ def color_msg(color, msg)
15
+ case color
16
+ when :black
17
+ "\033[30m#{msg}\033[0m"
18
+ when :red
19
+ "\033[31m#{msg}\033[0m"
20
+ when :green
21
+ "\033[32m#{msg}\033[0m"
22
+ when :brown
23
+ "\033[33m#{msg}\033[0m"
24
+ when :blue
25
+ "\033[34m#{msg}\033[0m"
26
+ when :magenta
27
+ "\033[35m#{msg}\033[0m"
28
+ when :cyan
29
+ "\033[36m#{msg}\033[0m"
30
+ when :gray
31
+ "\033[37m#{msg}\033[0m"
32
+ else
33
+ msg
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module MigrateFactoryRails
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'migrate_factory_rails/version'
2
+ require_relative 'migrate_factory_rails/factory'
3
+
4
+ module MigrateFactoryRails
5
+ class Error < StandardError; end
6
+ require 'migrate_factory_rails/railtie' if defined?(Rails)
7
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: migrate_factory_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Diogo Fernandes
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: '7.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Completely rebuild your migrations to agilize setup and simplify schema.
48
+ email:
49
+ - diogofernandesop@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files:
53
+ - README.md
54
+ - CHANGELOG.md
55
+ - LICENSE
56
+ files:
57
+ - CHANGELOG.md
58
+ - LICENSE
59
+ - README.md
60
+ - lib/migrate_factory_rails.rb
61
+ - lib/migrate_factory_rails/Rakefile.rb
62
+ - lib/migrate_factory_rails/builder.rb
63
+ - lib/migrate_factory_rails/exceptions.rb
64
+ - lib/migrate_factory_rails/factory.rb
65
+ - lib/migrate_factory_rails/railtie.rb
66
+ - lib/migrate_factory_rails/tasks/rebuild/migrations.rake
67
+ - lib/migrate_factory_rails/ui.rb
68
+ - lib/migrate_factory_rails/version.rb
69
+ homepage: https://github.com/dfop02/migrate_factory_rails
70
+ licenses:
71
+ - MIT
72
+ metadata:
73
+ allowed_push_host: https://rubygems.org
74
+ bug_tracker_uri: https://github.com/dfop02/migrate_factory_rails/issues
75
+ wiki_uri: https://github.com/dfop02/migrate_factory_rails/wiki
76
+ changelog_uri: https://github.com/dfop02/migrate_factory_rails/blob/1.0.0/Changelog.md
77
+ homepage_uri: https://github.com/dfop02/migrate_factory_rails
78
+ documentation_uri: https://github.com/dfop02/migrate_factory_rails
79
+ source_code_uri: https://github.com/dfop02/migrate_factory_rails
80
+ post_install_message:
81
+ rdoc_options:
82
+ - "--charset=UTF-8"
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.3.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.3.7
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Rebuild your Rails migrations
100
+ test_files: []