declare_schema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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