declare_schema 0.1.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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.dependabot/config.yml +10 -0
  3. data/.github/workflows/gem_release.yml +38 -0
  4. data/.gitignore +14 -0
  5. data/.jenkins/Jenkinsfile +72 -0
  6. data/.jenkins/ruby_build_pod.yml +19 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +189 -0
  9. data/.ruby-version +1 -0
  10. data/Appraisals +14 -0
  11. data/CHANGELOG.md +11 -0
  12. data/Gemfile +24 -0
  13. data/Gemfile.lock +203 -0
  14. data/LICENSE.txt +22 -0
  15. data/README.md +11 -0
  16. data/Rakefile +56 -0
  17. data/bin/declare_schema +11 -0
  18. data/declare_schema.gemspec +25 -0
  19. data/gemfiles/.bundle/config +2 -0
  20. data/gemfiles/rails_4.gemfile +25 -0
  21. data/gemfiles/rails_5.gemfile +25 -0
  22. data/gemfiles/rails_6.gemfile +25 -0
  23. data/lib/declare_schema.rb +44 -0
  24. data/lib/declare_schema/command.rb +65 -0
  25. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +28 -0
  26. data/lib/declare_schema/extensions/module.rb +36 -0
  27. data/lib/declare_schema/field_declaration_dsl.rb +40 -0
  28. data/lib/declare_schema/model.rb +242 -0
  29. data/lib/declare_schema/model/field_spec.rb +162 -0
  30. data/lib/declare_schema/model/index_spec.rb +175 -0
  31. data/lib/declare_schema/railtie.rb +12 -0
  32. data/lib/declare_schema/version.rb +5 -0
  33. data/lib/generators/declare_schema/migration/USAGE +47 -0
  34. data/lib/generators/declare_schema/migration/migration_generator.rb +184 -0
  35. data/lib/generators/declare_schema/migration/migrator.rb +567 -0
  36. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +9 -0
  37. data/lib/generators/declare_schema/model/USAGE +19 -0
  38. data/lib/generators/declare_schema/model/model_generator.rb +12 -0
  39. data/lib/generators/declare_schema/model/templates/model_injection.rb.erb +25 -0
  40. data/lib/generators/declare_schema/support/eval_template.rb +21 -0
  41. data/lib/generators/declare_schema/support/model.rb +64 -0
  42. data/lib/generators/declare_schema/support/thor_shell.rb +39 -0
  43. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +28 -0
  44. data/spec/spec_helper.rb +28 -0
  45. data/test/api.rdoctest +136 -0
  46. data/test/doc-only.rdoctest +76 -0
  47. data/test/generators.rdoctest +60 -0
  48. data/test/interactive_primary_key.rdoctest +56 -0
  49. data/test/migration_generator.rdoctest +846 -0
  50. data/test/migration_generator_comments.rdoctestDISABLED +74 -0
  51. data/test/prepare_testapp.rb +15 -0
  52. data/test_responses.txt +2 -0
  53. metadata +109 -0
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeclareSchema
4
+ module Model
5
+ class FieldSpec
6
+ class UnknownSqlTypeError < RuntimeError; end
7
+
8
+ MYSQL_TINYTEXT_LIMIT = 0xff
9
+ MYSQL_TEXT_LIMIT = 0xffff
10
+ MYSQL_MEDIUMTEXT_LIMIT = 0xff_ffff
11
+ MYSQL_LONGTEXT_LIMIT = 0xffff_ffff
12
+
13
+ MYSQL_TEXT_LIMITS_ASCENDING = [MYSQL_TINYTEXT_LIMIT, MYSQL_TEXT_LIMIT, MYSQL_MEDIUMTEXT_LIMIT, MYSQL_LONGTEXT_LIMIT].freeze
14
+
15
+ class << self
16
+ # method for easy stubbing in tests
17
+ def mysql_text_limits?
18
+ if defined?(@mysql_text_limits)
19
+ @mysql_text_limits
20
+ else
21
+ @mysql_text_limits = ActiveRecord::Base.connection.class.name.match?(/mysql/i)
22
+ end
23
+ end
24
+
25
+ def round_up_mysql_text_limit(limit)
26
+ MYSQL_TEXT_LIMITS_ASCENDING.find do |mysql_supported_text_limit|
27
+ if limit <= mysql_supported_text_limit
28
+ mysql_supported_text_limit
29
+ end
30
+ end or raise ArgumentError, "limit of #{limit} is too large for MySQL"
31
+ end
32
+ end
33
+
34
+ attr_reader :model, :name, :type, :position, :options
35
+
36
+ def initialize(model, name, type, options = {})
37
+ # Invoca change - searching for the primary key was causing an additional database read on every model load. Assume
38
+ # "id" which works for invoca.
39
+ # raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
40
+ name == "id" and raise ArgumentError, "you cannot provide a field spec for the primary key"
41
+
42
+ @model = model
43
+ @name = name.to_sym
44
+ @type = type.is_a?(String) ? type.to_sym : type
45
+ position_option = options.delete(:position)
46
+ @options = options
47
+
48
+ case type
49
+ when :text
50
+ @options[:default] and raise "default may not be given for :text field #{model}##{@name}"
51
+ if self.class.mysql_text_limits?
52
+ @options[:limit] = self.class.round_up_mysql_text_limit(@options[:limit] || MYSQL_LONGTEXT_LIMIT)
53
+ end
54
+ when :string
55
+ @options[:limit] or raise "limit must be given for :string field #{model}##{@name}: #{@options.inspect}; do you want 255?"
56
+ end
57
+ @position = position_option || model.field_specs.length
58
+ end
59
+
60
+ TYPE_SYNONYMS = { timestamp: :datetime }.freeze
61
+
62
+ SQLITE_COLUMN_CLASS =
63
+ begin
64
+ ActiveRecord::ConnectionAdapters::SQLiteColumn
65
+ rescue NameError
66
+ NilClass
67
+ end
68
+
69
+ def sql_type
70
+ @options[:sql_type] || begin
71
+ if native_type?(type)
72
+ type
73
+ else
74
+ field_class = DeclareSchema.to_class(type)
75
+ field_class && field_class::COLUMN_TYPE or raise UnknownSqlTypeError, "#{type.inspect} for #{model}.#{@name}"
76
+ end
77
+ end
78
+ end
79
+
80
+ def sql_options
81
+ @options.except(:ruby_default, :validates)
82
+ end
83
+
84
+ def limit
85
+ @options[:limit] || native_types[sql_type][:limit]
86
+ end
87
+
88
+ def precision
89
+ @options[:precision]
90
+ end
91
+
92
+ def scale
93
+ @options[:scale]
94
+ end
95
+
96
+ def null
97
+ !:null.in?(@options) || @options[:null]
98
+ end
99
+
100
+ def default
101
+ @options[:default]
102
+ end
103
+
104
+ def comment
105
+ @options[:comment]
106
+ end
107
+
108
+ def same_type?(col_spec)
109
+ type = sql_type
110
+ normalized_type = TYPE_SYNONYMS[type] || type
111
+ normalized_col_spec_type = TYPE_SYNONYMS[col_spec.type] || col_spec.type
112
+ normalized_type == normalized_col_spec_type
113
+ end
114
+
115
+ def different_to?(col_spec)
116
+ !same_type?(col_spec) ||
117
+ # we should be able to use col_spec.comment, but col_spec has
118
+ # a nil table_name for some strange reason.
119
+ (model.table_exists? &&
120
+ ActiveRecord::Base.respond_to?(:column_comment) &&
121
+ !(col_comment = ActiveRecord::Base.column_comment(col_spec.name, model.table_name)).nil? &&
122
+ col_comment != comment
123
+ ) ||
124
+ begin
125
+ native_type = native_types[type]
126
+ check_attributes = [:null, :default]
127
+ check_attributes += [:precision, :scale] if sql_type == :decimal && !col_spec.is_a?(SQLITE_COLUMN_CLASS) # remove when rails fixes https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2872
128
+ check_attributes -= [:default] if sql_type == :text && col_spec.class.name =~ /mysql/i
129
+ check_attributes << :limit if sql_type.in?([:string, :binary, :varbinary, :integer, :enum]) ||
130
+ (sql_type == :text && self.class.mysql_text_limits?)
131
+ check_attributes.any? do |k|
132
+ if k == :default
133
+ case Rails::VERSION::MAJOR
134
+ when 4
135
+ col_spec.type_cast_from_database(col_spec.default) != col_spec.type_cast_from_database(default)
136
+ else
137
+ cast_type = ActiveRecord::Base.connection.lookup_cast_type_from_column(col_spec) or raise "cast_type not found for #{col_spec.inspect}"
138
+ cast_type.deserialize(col_spec.default) != cast_type.deserialize(default)
139
+ end
140
+ else
141
+ col_value = col_spec.send(k)
142
+ if col_value.nil? && native_type
143
+ col_value = native_type[k]
144
+ end
145
+ col_value != send(k)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def native_type?(type)
154
+ type.to_sym != :primary_key && native_types.has_key?(type)
155
+ end
156
+
157
+ def native_types
158
+ Generators::DeclareSchema::Migration::Migrator.native_types
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeclareSchema
4
+ module Model
5
+ class IndexSpec
6
+ include Comparable
7
+
8
+ attr_reader :table, :fields, :explicit_name, :name, :unique, :where
9
+
10
+ class IndexNameTooLongError < RuntimeError; end
11
+
12
+ PRIMARY_KEY_NAME = "PRIMARY_KEY"
13
+ MYSQL_INDEX_NAME_MAX_LENGTH = 64
14
+
15
+ def initialize(model, fields, options = {})
16
+ @model = model
17
+ @table = options.delete(:table_name) || model.table_name
18
+ @fields = Array.wrap(fields).map(&:to_s)
19
+ @explicit_name = options[:name] unless options.delete(:allow_equivalent)
20
+ @name = options.delete(:name) || model.connection.index_name(table, column: @fields).gsub(/index.*_on_/, 'on_')
21
+ @unique = options.delete(:unique) || name == PRIMARY_KEY_NAME || false
22
+
23
+ if @name.length > MYSQL_INDEX_NAME_MAX_LENGTH
24
+ raise IndexNameTooLongError, "Index '#{@name}' exceeds MySQL limit of #{MYSQL_INDEX_NAME_MAX_LENGTH} characters. Give it a shorter name."
25
+ end
26
+
27
+ if (where = options[:where])
28
+ @where = where.start_with?('(') ? where : "(#{where})"
29
+ end
30
+ end
31
+
32
+ class << self
33
+ # extract IndexSpecs from an existing table
34
+ def for_model(model, old_table_name = nil)
35
+ t = old_table_name || model.table_name
36
+ connection = model.connection.dup
37
+ # TODO: Change below to use prepend
38
+ class << connection # defeat Rails code that skips the primary keys by changing their name to PRIMARY_KEY_NAME
39
+ def each_hash(result)
40
+ super do |hash|
41
+ if hash[:Key_name] == "PRIMARY"
42
+ hash[:Key_name] = PRIMARY_KEY_NAME
43
+ end
44
+ yield hash
45
+ end
46
+ end
47
+ end
48
+ connection.indexes(t).map do |i|
49
+ new(model, i.columns, name: i.name, unique: i.unique, where: i.where, table_name: old_table_name) unless model.ignore_indexes.include?(i.name)
50
+ end.compact
51
+ end
52
+ end
53
+
54
+ def primary_key?
55
+ name == PRIMARY_KEY_NAME
56
+ end
57
+
58
+ def to_add_statement(new_table_name, existing_primary_key = nil)
59
+ if primary_key? && !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
60
+ to_add_primary_key_statement(new_table_name, existing_primary_key)
61
+ else
62
+ r = +"add_index #{new_table_name.to_sym.inspect}, #{fields.map(&:to_sym).inspect}"
63
+ r += ", unique: true" if unique
64
+ r += ", where: '#{where}'" if where.present?
65
+ r += ", name: '#{name}'"
66
+ r
67
+ end
68
+ end
69
+
70
+ def to_add_primary_key_statement(new_table_name, existing_primary_key)
71
+ drop = "DROP PRIMARY KEY, " if existing_primary_key
72
+ statement = "ALTER TABLE #{new_table_name} #{drop}ADD PRIMARY KEY (#{fields.join(', ')})"
73
+ "execute #{statement.inspect}"
74
+ end
75
+
76
+ def to_key
77
+ @key ||= [table, fields, name, unique, where].map(&:to_s)
78
+ end
79
+
80
+ def settings
81
+ @settings ||= [table, fields, unique].map(&:to_s)
82
+ end
83
+
84
+ def hash
85
+ to_key.hash
86
+ end
87
+
88
+ def <=>(rhs)
89
+ to_key <=> rhs.to_key
90
+ end
91
+
92
+ def equivalent?(rhs)
93
+ settings == rhs.settings
94
+ end
95
+
96
+ def with_name(new_name)
97
+ self.class.new(@model, @fields, table_name: @table_name, index_name: @index_name, unique: @unique, name: new_name)
98
+ end
99
+
100
+ alias eql? ==
101
+ end
102
+
103
+ class ForeignKeySpec
104
+ include Comparable
105
+
106
+ attr_reader :constraint_name, :model, :foreign_key, :options, :on_delete_cascade
107
+
108
+ def initialize(model, foreign_key, options = {})
109
+ @model = model
110
+ @foreign_key = foreign_key.presence
111
+ @options = options
112
+
113
+ @child_table = model.table_name # unless a table rename, which would happen when a class is renamed??
114
+ @parent_table_name = options[:parent_table]
115
+ @foreign_key_name = options[:foreign_key] || self.foreign_key
116
+ @index_name = options[:index_name] || model.connection.index_name(model.table_name, column: foreign_key)
117
+ @constraint_name = options[:constraint_name] || @index_name || ''
118
+ @on_delete_cascade = options[:dependent] == :delete
119
+
120
+ # Empty constraint lets mysql generate the name
121
+ end
122
+
123
+ class << self
124
+ def for_model(model, old_table_name)
125
+ show_create_table = model.connection.select_rows("show create table #{model.connection.quote_table_name(old_table_name)}").first.last
126
+ constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
127
+
128
+ constraints.map do |fkc|
129
+ options = {}
130
+ name, foreign_key, parent_table = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
131
+ options[:constraint_name] = name
132
+ options[:parent_table] = parent_table
133
+ options[:foreign_key] = foreign_key
134
+ options[:dependent] = :delete if fkc['ON DELETE CASCADE']
135
+
136
+ new(model, foreign_key, options)
137
+ end
138
+ end
139
+ end
140
+
141
+ def parent_table_name
142
+ @parent_table_name ||=
143
+ options[:class_name]&.is_a?(Class) &&
144
+ options[:class_name].respond_to?(:table_name) &&
145
+ options[:class_name]&.table_name
146
+ @parent_table_name ||=
147
+ options[:class_name]&.constantize &&
148
+ options[:class_name].constantize.respond_to?(:table_name) &&
149
+ options[:class_name].constantize.table_name ||
150
+ foreign_key.gsub(/_id/, '').camelize.constantize.table_name
151
+ end
152
+
153
+ attr_writer :parent_table_name
154
+
155
+ def to_add_statement(_ = true)
156
+ statement = "ALTER TABLE #{@child_table} ADD CONSTRAINT #{@constraint_name} FOREIGN KEY #{@index_name}(#{@foreign_key_name}) REFERENCES #{parent_table_name}(id) #{'ON DELETE CASCADE' if on_delete_cascade}"
157
+ "execute #{statement.inspect}"
158
+ end
159
+
160
+ def to_key
161
+ @key ||= [@child_table, parent_table_name, @foreign_key_name, @on_delete_cascade].map(&:to_s)
162
+ end
163
+
164
+ def hash
165
+ to_key.hash
166
+ end
167
+
168
+ def <=>(rhs)
169
+ to_key <=> rhs.to_key
170
+ end
171
+
172
+ alias eql? ==
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'declare_schema'
4
+ require 'rails'
5
+
6
+ module DeclareSchema
7
+ class Railtie < Rails::Railtie
8
+ ActiveSupport.on_load(:active_record) do
9
+ require 'declare_schema/extensions/active_record/fields_declaration'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeclareSchema
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,47 @@
1
+ Description:
2
+
3
+ This generator compares your existing schema against the
4
+ schema declared inside your fields declarations in your
5
+ models.
6
+
7
+ If the generator finds differences, it will display the
8
+ migration it has created, and ask you if you wish to
9
+ [g]enerate migration, generate and [m]igrate now or [c]ancel?
10
+ Enter "g" to just generate the migration but do not run it.
11
+ Enter "m" to generate the migration and run it, or press "c"
12
+ to do nothing.
13
+
14
+ The generator will then prompt you for the migration name,
15
+ supplying a numbered default name.
16
+
17
+ The generator is conservative and will prompt you to resolve
18
+ any ambiguities.
19
+
20
+ Examples:
21
+
22
+ $ rails generate declare_schema:migration
23
+
24
+ ---------- Up Migration ----------
25
+ create_table :foos do |t|
26
+ t.datetime :created_at
27
+ t.datetime :updated_at
28
+ end
29
+ ----------------------------------
30
+
31
+ ---------- Down Migration --------
32
+ drop_table :foos
33
+ ----------------------------------
34
+ What now: [g]enerate migration, generate and [m]igrate now or [c]ancel? m
35
+
36
+ Migration filename:
37
+ (you can type spaces instead of '_' -- every little helps)
38
+ Filename [declare_schema_migration_2]: create_foo
39
+ exists db/migrate
40
+ create db/migrate/20091023183838_create_foo.rb
41
+ (in /work/foo)
42
+ == CreateFoo: migrating ======================================================
43
+ -- create_table(:yos)
44
+ -> 0.0856s
45
+ == CreateFoo: migrated (0.0858s) =============================================
46
+
47
+
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/migration'
4
+ require 'rails/generators/active_record'
5
+ require 'generators/declare_schema/support/thor_shell'
6
+
7
+ module DeclareSchema
8
+ class MigrationGenerator < Rails::Generators::Base
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ argument :name, type: :string, optional: true
12
+
13
+ include Rails::Generators::Migration
14
+ include DeclareSchema::Support::ThorShell
15
+
16
+ class << self
17
+ # the Rails::Generators::Migration.next_migration_number gives a NotImplementedError
18
+ # in Rails 3.0.0.beta4, so we need to implement the logic of ActiveRecord.
19
+ # For other ORMs we will wait for the rails implementation
20
+ # see http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/a507ce419076cda2
21
+ def next_migration_number(dirname)
22
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
23
+ end
24
+
25
+ def banner
26
+ "rails generate declare_schema:migration #{arguments.map(&:usage).join(' ')} [options]"
27
+ end
28
+ end
29
+
30
+ class_option :drop,
31
+ aliases: '-d',
32
+ type: :boolean,
33
+ desc: "Don't prompt with 'drop or rename' - just drop everything"
34
+
35
+ class_option :default_name,
36
+ aliases: '-n',
37
+ type: :boolean,
38
+ desc: "Don't prompt for a migration name - just pick one"
39
+
40
+ class_option :generate,
41
+ aliases: '-g',
42
+ type: :boolean,
43
+ desc: "Don't prompt for action - generate the migration"
44
+
45
+ class_option :migrate,
46
+ aliases: '-m',
47
+ type: :boolean,
48
+ desc: "Don't prompt for action - generate and migrate"
49
+
50
+ def migrate
51
+ return if migrations_pending?
52
+
53
+ generator = Generators::DeclareSchema::Migration::Migrator.new(->(c, d, k, p) { extract_renames!(c, d, k, p) })
54
+ up, down = generator.generate
55
+
56
+ if up.blank?
57
+ say "Database and models match -- nothing to change"
58
+ return
59
+ end
60
+
61
+ say "\n---------- Up Migration ----------"
62
+ say up
63
+ say "----------------------------------"
64
+
65
+ say "\n---------- Down Migration --------"
66
+ say down
67
+ say "----------------------------------"
68
+
69
+ action = options[:generate] && 'g' ||
70
+ options[:migrate] && 'm' ||
71
+ choose("\nWhat now: [g]enerate migration, generate and [m]igrate now or [c]ancel?", /^(g|m|c)$/)
72
+
73
+ if action != 'c'
74
+ if name.blank? && !options[:default_name]
75
+ final_migration_name = choose("\nMigration filename: [<enter>=#{migration_name}|<custom_name>]:", /^[a-z0-9_ ]*$/, migration_name).strip.gsub(' ', '_')
76
+ end
77
+ final_migration_name = migration_name if final_migration_name.blank?
78
+
79
+ up.gsub!("\n", "\n ")
80
+ up.gsub!(/ +\n/, "\n")
81
+ down.gsub!("\n", "\n ")
82
+ down.gsub!(/ +\n/, "\n")
83
+
84
+ @up = up
85
+ @down = down
86
+ @migration_class_name = final_migration_name.camelize
87
+
88
+ migration_template 'migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb"
89
+ if action == 'm'
90
+ case Rails::VERSION::MAJOR
91
+ when 4
92
+ rake('db:migrate')
93
+ else
94
+ rails_command('db:migrate')
95
+ end
96
+ end
97
+ end
98
+ rescue ::DeclareSchema::Model::FieldSpec::UnknownSqlTypeError => ex
99
+ say "Invalid field type: #{ex}"
100
+ end
101
+
102
+ private
103
+
104
+ def migrations_pending?
105
+ migrations = case Rails::VERSION::MAJOR
106
+ when 4
107
+ ActiveRecord::Migrator.migrations('db/migrate')
108
+ when 5
109
+ ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths).migrations
110
+ else
111
+ ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration).migrations
112
+ end
113
+ pending_migrations = case Rails::VERSION::MAJOR
114
+ when 4, 5
115
+ ActiveRecord::Migrator.new(:up, migrations).pending_migrations
116
+ else
117
+ ActiveRecord::Migrator.new(:up, migrations, ActiveRecord::SchemaMigration).pending_migrations
118
+ end
119
+
120
+ if pending_migrations.any?
121
+ say "You have #{pending_migrations.size} pending migration#{'s' if pending_migrations.size > 1}:"
122
+ pending_migrations.each do |pending_migration|
123
+ say format(' %4d %s', pending_migration.version, pending_migration.name)
124
+ end
125
+ true
126
+ else
127
+ false
128
+ end
129
+ end
130
+
131
+ def extract_renames!(to_create, to_drop, kind_str, name_prefix = "")
132
+ to_rename = {}
133
+
134
+ unless options[:drop]
135
+
136
+ rename_to_choices = to_create
137
+ to_drop.dup.each do |t|
138
+ loop do
139
+ if rename_to_choices.empty?
140
+ say "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
141
+ resp = ask("Enter 'drop #{t}' to confirm or press enter to keep:")
142
+ if resp.strip == "drop #{t}"
143
+ break
144
+ elsif resp.strip.empty?
145
+ to_drop.delete(t)
146
+ break
147
+ else
148
+ next
149
+ end
150
+ else
151
+ say "\nDROP, RENAME or KEEP?: #{kind_str} #{name_prefix}#{t}"
152
+ say "Rename choices: #{to_create * ', '}"
153
+ resp = ask "Enter either 'drop #{t}' or one of the rename choices or press enter to keep:"
154
+ resp = resp.strip
155
+
156
+ if resp == "drop #{t}"
157
+ # Leave things as they are
158
+ break
159
+ else
160
+ resp.gsub!(' ', '_')
161
+ to_drop.delete(t)
162
+ if resp.in?(rename_to_choices)
163
+ to_rename[t] = resp
164
+ to_create.delete(resp)
165
+ rename_to_choices.delete(resp)
166
+ break
167
+ elsif resp.empty?
168
+ break
169
+ else
170
+ next
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ to_rename
178
+ end
179
+
180
+ def migration_name
181
+ name || Generators::DeclareSchema::Migration::Migrator.default_migration_name
182
+ end
183
+ end
184
+ end