migrant 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/VERSION +1 -1
- data/lib/dsl/data_type.rb +5 -0
- data/lib/generators/templates/change_migration.erb +23 -3
- data/lib/migrant/migration_generator.rb +102 -10
- data/lib/migrant/model_extensions.rb +6 -2
- data/lib/migrant/schema.rb +10 -2
- data/lib/tasks/db.rake +12 -0
- data/migrant.gemspec +21 -4
- data/test/helper.rb +17 -1
- data/test/rails_app/app/models/chameleon.rb +5 -0
- data/test/test_migration_generator.rb +59 -1
- data/test/verified_output/migrations/added_incompatible_spot_and_deleted_spots.rb +11 -0
- data/test/verified_output/migrations/chameleons_added_new_longer_spots_and_moved_new_spots.rb +15 -0
- data/test/verified_output/migrations/create_chameleons.rb +11 -0
- data/test/verified_output/migrations/deleted_incompatible_spot.rb +9 -0
- data/test/verified_output/migrations/deleted_spots.rb +9 -0
- data/test/verified_output/migrations/renamed_old_spots.rb +9 -0
- metadata +77 -12
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -71,6 +71,7 @@ GEM
|
|
71
71
|
simplecov-html (~> 0.4.0)
|
72
72
|
simplecov-html (0.4.3)
|
73
73
|
sqlite3 (1.3.3)
|
74
|
+
term-ansicolor (1.0.5)
|
74
75
|
terminal-table (1.4.2)
|
75
76
|
thor (0.14.6)
|
76
77
|
thoughtbot-shoulda (2.11.1)
|
@@ -90,6 +91,7 @@ DEPENDENCIES
|
|
90
91
|
rails (>= 3.0.0)
|
91
92
|
simplecov
|
92
93
|
sqlite3
|
94
|
+
term-ansicolor
|
93
95
|
terminal-table
|
94
96
|
thoughtbot-shoulda
|
95
97
|
turn
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/dsl/data_type.rb
CHANGED
@@ -21,6 +21,11 @@ module DataType
|
|
21
21
|
def column
|
22
22
|
{:type => :string}.merge(@options)
|
23
23
|
end
|
24
|
+
|
25
|
+
def ==(compared_column)
|
26
|
+
# Ideally we should compare attributes, but unfortunately not all drivers report enough statistics for this
|
27
|
+
column[:type] == compared_column[:type]
|
28
|
+
end
|
24
29
|
|
25
30
|
def mock
|
26
31
|
@value || self.class.default_mock
|
@@ -6,17 +6,37 @@ class <%= @activity.camelize.gsub(/\s/, '') %> < ActiveRecord::Migration
|
|
6
6
|
<% @changed_columns.each do |field, options, old_options| %>
|
7
7
|
change_column :<%= @table_name %>, :<%= field %>, :<%= options.delete(:type) %><%= (options.blank?)? '': ", "+options.inspect[1..-2] %>
|
8
8
|
<% end -%>
|
9
|
+
<% @transferred_columns.each do |source, target| %>
|
10
|
+
puts "-- copy data from :<%= source %> to :<%= target %>"
|
11
|
+
<%= @table_name.classify %>.update_all("<%= target %> = <%= source %>")
|
12
|
+
<% end -%>
|
13
|
+
<% @renamed_columns.each do |old_name, new_name| %>
|
14
|
+
rename_column :<%= @table_name %>, :<%= old_name %>, :<%= new_name %>
|
15
|
+
<% end -%>
|
16
|
+
<% @deleted_columns.each do |field, options| %>
|
17
|
+
remove_column :<%= @table_name %>, :<%= field %>
|
18
|
+
<% end -%>
|
9
19
|
<% @indexes.each do |index, options| %>
|
10
20
|
add_index :<%= @table_name %>, <%= index.inspect %>
|
11
21
|
<% end -%>
|
12
22
|
end
|
13
23
|
|
14
24
|
def self.down
|
15
|
-
<% @
|
16
|
-
|
17
|
-
<% end -%>
|
25
|
+
<% @deleted_columns.each do |field, options| %>
|
26
|
+
add_column :<%= @table_name %>, :<%= field %>, :<%= options.delete(:type) %><%= (options.blank?)? '': ", "+options.inspect[1..-2] %>
|
27
|
+
<% end -%>
|
28
|
+
<% @renamed_columns.each do |old_name, new_name| %>
|
29
|
+
rename_column :<%= @table_name %>, :<%= new_name %>, :<%= old_name %>
|
30
|
+
<% end -%>
|
31
|
+
<% @transferred_columns.each do |source, target| %>
|
32
|
+
puts "-- copy data from :<%= target %> to :<%= source %>"
|
33
|
+
<%= @table_name.classify %>.update_all("<%= source %> = <%= target %>")
|
34
|
+
<% end -%>
|
18
35
|
<% @changed_columns.each do |field, options, old_options| %>
|
19
36
|
change_column :<%= @table_name %>, :<%= field %>, :<%= old_options.delete(:type) %><%= (old_options.blank?)? '': ", "+old_options.inspect[1..-2] %>
|
20
37
|
<% end -%>
|
38
|
+
<% @added_columns.each do |field, options| %>
|
39
|
+
remove_column :<%= @table_name %>, :<%= field %>
|
40
|
+
<% end -%>
|
21
41
|
end
|
22
42
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'erubis'
|
2
|
+
require 'term/ansicolor'
|
2
3
|
|
3
4
|
module Migrant
|
4
5
|
class MigrationGenerator
|
5
|
-
TABS = ' ' # Tabs to spaces * 2
|
6
|
-
NEWLINE = "\n "
|
7
6
|
def run
|
8
7
|
# Ensure db/migrate path exists before starting
|
9
8
|
FileUtils.mkdir_p(Rails.root.join('db', 'migrate'))
|
9
|
+
@possible_irreversible_migrations = false
|
10
10
|
|
11
11
|
migrator = ActiveRecord::Migrator.new(:up, migrations_path)
|
12
12
|
|
@@ -24,25 +24,52 @@ module Migrant
|
|
24
24
|
next if model.schema.nil? || !model.schema.requires_migration? # Skips inherited schemas (such as models with STI)
|
25
25
|
model.reset_column_information # db:migrate doesn't do this
|
26
26
|
@table_name = model.table_name
|
27
|
+
@changed_columns, @added_columns, @deleted_columns, @renamed_columns, @transferred_columns = [], [], [], [], []
|
27
28
|
|
28
29
|
if model.table_exists?
|
29
30
|
# Structure ActiveRecord::Base's column information so we can compare it directly to the schema
|
30
31
|
db_schema = Hash[*model.columns.collect {|c| [c.name.to_sym, Hash[*[:type, :limit].map { |type| [type, c.send(type)] }.flatten] ] }.flatten]
|
31
|
-
@changed_columns, @added_columns = [], []
|
32
32
|
model.schema.columns.to_a.sort { |a,b| a.to_s <=> b.to_s }.each do |field_name, data_type|
|
33
33
|
begin
|
34
34
|
if (options = data_type.structure_changes_from(db_schema[field_name]))
|
35
35
|
if db_schema[field_name]
|
36
|
-
|
36
|
+
change_column(field_name, options, db_schema[field_name])
|
37
37
|
else
|
38
|
-
|
38
|
+
add_column(field_name, options)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
rescue DataType::DangerousMigration
|
42
|
-
|
42
|
+
log "Cannot generate migration automatically for #{model.table_name}, this would involve possible data loss on column: #{field_name}\nOld structure: #{db_schema[field_name].inspect}. New structure: #{data_type.column.inspect}\nPlease create and run this migration yourself (with the appropriate data integrity checks)", :error
|
43
43
|
return false
|
44
44
|
end
|
45
45
|
end
|
46
|
+
|
47
|
+
# Removed rows
|
48
|
+
unless model.schema.partial?
|
49
|
+
db_schema.reject { |field_name, options| field_name.to_s == model.primary_key || model.schema.columns.keys.include?(field_name) }.each do |removed_field_name, options|
|
50
|
+
case ask_user("#{model}: '#{removed_field_name}' is no longer in use.", (@added_columns.blank?)? %W{Destroy Ignore} : %W{Destroy Move Ignore})
|
51
|
+
when 'Destroy' then delete_column(removed_field_name, db_schema[removed_field_name])
|
52
|
+
when 'Move' then
|
53
|
+
target = ask_user("Move '#{removed_field_name}' to:", @added_columns.collect(&:first))
|
54
|
+
target_column = model.schema.columns[target]
|
55
|
+
begin
|
56
|
+
target_column.structure_changes_from(db_schema[removed_field_name])
|
57
|
+
move_column(removed_field_name, target, db_schema[removed_field_name], target_column)
|
58
|
+
rescue DataType::DangerousMigration
|
59
|
+
case ask_user("Unable to safely move '#{removed_field_name}' to '#{target}'. Keep the original column for now?", %W{Yes No}, true)
|
60
|
+
when 'No' then delete_column(removed_field_name, db_schema[removed_field_name])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
destroyed_columns = @deleted_columns.reject { |field, options| @transferred_columns.collect(&:first).include?(field) }
|
67
|
+
unless destroyed_columns.blank?
|
68
|
+
if ask_user("#{model}: '#{destroyed_columns.collect(&:first).join(', ')}' and associated data will be DESTROYED in all environments. Continue?", %W{Yes No}, true) == 'No'
|
69
|
+
log "Okay, not removing anything for now."
|
70
|
+
@deleted_columns = []
|
71
|
+
end
|
72
|
+
end
|
46
73
|
|
47
74
|
# For adapters that can report indexes, add as necessary
|
48
75
|
if ActiveRecord::Base.connection.respond_to?(:indexes)
|
@@ -52,10 +79,11 @@ module Migrant
|
|
52
79
|
@new_indexes = @indexes.reject { |index, options| @changed_columns.detect { |c| c.first == index } || @added_columns.detect { |c| c.first == index } }
|
53
80
|
end
|
54
81
|
|
55
|
-
next if @changed_columns.empty? && @added_columns.empty? && @indexes.empty? # Nothing to do for this table
|
82
|
+
next if @changed_columns.empty? && @added_columns.empty? && @renamed_columns.empty? && @transferred_columns.empty? && @deleted_columns.empty? && @indexes.empty? # Nothing to do for this table
|
56
83
|
|
57
84
|
# Example: changed_table_added_something_and_modified_something
|
58
|
-
@activity = 'changed_'+model.table_name+[['added', @added_columns], ['modified', @changed_columns], ['
|
85
|
+
@activity = 'changed_'+model.table_name+[['added', @added_columns], ['modified', @changed_columns], ['deleted', destroyed_columns],
|
86
|
+
['moved', @transferred_columns], ['renamed', @renamed_columns], ['indexed', @new_indexes]].reject { |v| v[1].empty? }.collect { |v| "_#{v[0]}_"+v[1].collect(&:first).join('_') }.join('_and')
|
59
87
|
@activity = @activity.split('_')[0..2].join('_') if @activity.length >= 240 # Most filesystems will raise Errno::ENAMETOOLONG otherwise
|
60
88
|
|
61
89
|
render('change_migration')
|
@@ -69,16 +97,80 @@ module Migrant
|
|
69
97
|
|
70
98
|
filename = "#{migrations_path}/#{next_migration_number}_#{@activity}.rb"
|
71
99
|
File.open(filename, 'w') { |migration| migration.write(@output) }
|
72
|
-
|
100
|
+
log "Wrote #{filename}..."
|
73
101
|
end
|
102
|
+
|
103
|
+
if @possible_irreversible_migrations
|
104
|
+
log "*** One or more move operations were performed, which potentially could cause data loss on db:rollback. \n*** Please review your migrations before committing!", :warning
|
105
|
+
end
|
106
|
+
|
74
107
|
true
|
75
108
|
end
|
76
109
|
|
77
110
|
private
|
111
|
+
def add_column(name, options)
|
112
|
+
@added_columns << [name, options]
|
113
|
+
end
|
114
|
+
|
115
|
+
def change_column(name, new_schema, old_schema)
|
116
|
+
@changed_columns << [name, new_schema, old_schema]
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_column(name, current_structure)
|
120
|
+
@deleted_columns << [name, current_structure]
|
121
|
+
end
|
122
|
+
|
123
|
+
def move_column(old_name, new_name, old_schema, new_schema)
|
124
|
+
if new_schema == old_schema
|
125
|
+
@renamed_columns << [old_name, new_name]
|
126
|
+
@added_columns.reject! { |a| a.first == new_name } # Don't add the column too
|
127
|
+
else
|
128
|
+
@possible_irreversible_migrations = true
|
129
|
+
@transferred_columns << [old_name, new_name] # Still need to add the column, just transfer the data afterwards
|
130
|
+
delete_column(old_name, old_schema)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
78
134
|
def migrations_path
|
79
135
|
Rails.root.join(ActiveRecord::Migrator.migrations_path)
|
80
136
|
end
|
81
|
-
|
137
|
+
|
138
|
+
include Term::ANSIColor
|
139
|
+
def ask_user(message, choices, warning=false)
|
140
|
+
mappings = choices.uniq.inject({}) do |mappings, choice|
|
141
|
+
choice_string = choice.to_s
|
142
|
+
choice_string.length.times do |i|
|
143
|
+
mappings.merge!(choice_string[i..i] => choice) and break unless mappings.keys.include?(choice_string[i..i])
|
144
|
+
end
|
145
|
+
mappings.merge!(choice_string => choice) unless mappings.values.include?(choice)
|
146
|
+
mappings
|
147
|
+
end
|
148
|
+
|
149
|
+
begin
|
150
|
+
message = "> #{message} [#{mappings.collect { |shortcut, choice| choice.to_s.sub(shortcut, '('+shortcut+')') }.join(' / ')}]: "
|
151
|
+
if warning
|
152
|
+
STDOUT.print red, bold, message, reset
|
153
|
+
else
|
154
|
+
STDOUT.print bold, message, reset
|
155
|
+
end
|
156
|
+
STDOUT.flush
|
157
|
+
input = STDIN.gets.downcase
|
158
|
+
end until (choice = mappings.detect { |shortcut, choice| [shortcut.downcase,choice.to_s.downcase].include?(input.downcase.strip) })
|
159
|
+
choice.last
|
160
|
+
end
|
161
|
+
|
162
|
+
def log(message, type=:info)
|
163
|
+
STDOUT.puts(
|
164
|
+
case type
|
165
|
+
when :error
|
166
|
+
[red, bold, message, reset]
|
167
|
+
when :warning
|
168
|
+
[yellow, message, reset]
|
169
|
+
else
|
170
|
+
message
|
171
|
+
end
|
172
|
+
)
|
173
|
+
end
|
82
174
|
# See ActiveRecord::Generators::Migration
|
83
175
|
# Only generating a migration to each second is a problem.. because we generate everything in the same second
|
84
176
|
# So we have to add further "pretend" seconds. This WILL cause problems.
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Migrant
|
2
2
|
module ModelExtensions
|
3
3
|
attr_accessor :schema
|
4
|
-
def structure(&block)
|
4
|
+
def structure(type=nil, &block)
|
5
5
|
# Using instance_*evil* to get the neater DSL on the models.
|
6
6
|
# So, my_field in the structure block actually calls Migrant::Schema.my_field
|
7
7
|
|
8
8
|
if self.superclass == ActiveRecord::Base
|
9
9
|
@schema ||= Schema.new
|
10
10
|
@schema.add_associations(self.reflect_on_all_associations)
|
11
|
-
@schema.define_structure(&block)
|
11
|
+
@schema.define_structure(type, &block)
|
12
12
|
|
13
13
|
@schema.validations.each do |field, validation_options|
|
14
14
|
validations = (validation_options.class == Array)? validation_options : [validation_options]
|
@@ -28,6 +28,10 @@ module Migrant
|
|
28
28
|
def no_structure
|
29
29
|
structure {}
|
30
30
|
end
|
31
|
+
|
32
|
+
def reset_structure!
|
33
|
+
@schema = nil
|
34
|
+
end
|
31
35
|
|
32
36
|
def mock(attributes={}, recursive=true)
|
33
37
|
attribs = {}
|
data/lib/migrant/schema.rb
CHANGED
@@ -18,10 +18,13 @@ module Migrant
|
|
18
18
|
@columns = Hash.new
|
19
19
|
@indexes = Array.new
|
20
20
|
@validations = Hash.new
|
21
|
+
@type = :default
|
21
22
|
end
|
22
23
|
|
23
|
-
def define_structure(&block)
|
24
|
+
def define_structure(type, &block)
|
24
25
|
@validations = Hash.new
|
26
|
+
@type = type if type
|
27
|
+
|
25
28
|
# Runs method_missing on columns given in the model "structure" DSL
|
26
29
|
@proxy.translate_fancy_dsl(&block) if block_given?
|
27
30
|
end
|
@@ -43,9 +46,14 @@ module Migrant
|
|
43
46
|
end
|
44
47
|
|
45
48
|
def requires_migration?
|
46
|
-
|
49
|
+
true
|
47
50
|
end
|
48
51
|
|
52
|
+
# If the user defines structure(:partial), irreversible changes are ignored (removing a column, for example)
|
53
|
+
def partial?
|
54
|
+
@type == :partial
|
55
|
+
end
|
56
|
+
|
49
57
|
def column_migrations
|
50
58
|
@columns.collect {|field, data| [field, data.column] } # All that needs to be migrated
|
51
59
|
end
|
data/lib/tasks/db.rake
CHANGED
@@ -12,5 +12,17 @@ namespace :db do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
desc "Provides a shortcut to rolling back and discarding the last migration"
|
17
|
+
task :downgrade => :environment do
|
18
|
+
Rake::Task['db:rollback'].invoke
|
19
|
+
Dir.chdir(Rails.root.join('db', 'migrate')) do
|
20
|
+
last_migration = Dir.glob('*.rb').sort.last and
|
21
|
+
File.unlink(last_migration) and
|
22
|
+
puts "Removed #{Dir.pwd}/#{last_migration}."
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::Task['db:test:clone'].invoke unless ENV['RAILS_ENV']
|
26
|
+
end
|
15
27
|
end
|
16
28
|
|
data/migrant.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{migrant}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Pascal Houliston"]
|
12
|
-
s.date = %q{2011-04-
|
12
|
+
s.date = %q{2011-04-23}
|
13
13
|
s.description = %q{Easier schema management for Rails that compliments your domain model.}
|
14
14
|
s.email = %q{101pascal@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -52,6 +52,7 @@ Gem::Specification.new do |s|
|
|
52
52
|
"test/rails_app/app/models/business.rb",
|
53
53
|
"test/rails_app/app/models/business_category.rb",
|
54
54
|
"test/rails_app/app/models/category.rb",
|
55
|
+
"test/rails_app/app/models/chameleon.rb",
|
55
56
|
"test/rails_app/app/models/customer.rb",
|
56
57
|
"test/rails_app/app/models/user.rb",
|
57
58
|
"test/rails_app/app/views/layouts/application.html.erb",
|
@@ -83,15 +84,21 @@ Gem::Specification.new do |s|
|
|
83
84
|
"test/test_migration_generator.rb",
|
84
85
|
"test/test_validations.rb",
|
85
86
|
"test/test_zzz_performance.rb",
|
87
|
+
"test/verified_output/migrations/added_incompatible_spot_and_deleted_spots.rb",
|
86
88
|
"test/verified_output/migrations/business_id.rb",
|
89
|
+
"test/verified_output/migrations/chameleons_added_new_longer_spots_and_moved_new_spots.rb",
|
87
90
|
"test/verified_output/migrations/create_business_categories.rb",
|
88
91
|
"test/verified_output/migrations/create_businesses.rb",
|
89
92
|
"test/verified_output/migrations/create_categories.rb",
|
93
|
+
"test/verified_output/migrations/create_chameleons.rb",
|
90
94
|
"test/verified_output/migrations/create_reviews.rb",
|
91
95
|
"test/verified_output/migrations/create_users.rb",
|
92
96
|
"test/verified_output/migrations/created_at.rb",
|
97
|
+
"test/verified_output/migrations/deleted_incompatible_spot.rb",
|
98
|
+
"test/verified_output/migrations/deleted_spots.rb",
|
93
99
|
"test/verified_output/migrations/estimated_value_notes.rb",
|
94
|
-
"test/verified_output/migrations/landline.rb"
|
100
|
+
"test/verified_output/migrations/landline.rb",
|
101
|
+
"test/verified_output/migrations/renamed_old_spots.rb"
|
95
102
|
]
|
96
103
|
s.homepage = %q{http://github.com/pascalh1011/migrant}
|
97
104
|
s.require_paths = ["lib"]
|
@@ -105,6 +112,7 @@ Gem::Specification.new do |s|
|
|
105
112
|
"test/rails_app/app/models/business.rb",
|
106
113
|
"test/rails_app/app/models/business_category.rb",
|
107
114
|
"test/rails_app/app/models/category.rb",
|
115
|
+
"test/rails_app/app/models/chameleon.rb",
|
108
116
|
"test/rails_app/app/models/customer.rb",
|
109
117
|
"test/rails_app/app/models/user.rb",
|
110
118
|
"test/rails_app/config/application.rb",
|
@@ -126,15 +134,21 @@ Gem::Specification.new do |s|
|
|
126
134
|
"test/test_migration_generator.rb",
|
127
135
|
"test/test_validations.rb",
|
128
136
|
"test/test_zzz_performance.rb",
|
137
|
+
"test/verified_output/migrations/added_incompatible_spot_and_deleted_spots.rb",
|
129
138
|
"test/verified_output/migrations/business_id.rb",
|
139
|
+
"test/verified_output/migrations/chameleons_added_new_longer_spots_and_moved_new_spots.rb",
|
130
140
|
"test/verified_output/migrations/create_business_categories.rb",
|
131
141
|
"test/verified_output/migrations/create_businesses.rb",
|
132
142
|
"test/verified_output/migrations/create_categories.rb",
|
143
|
+
"test/verified_output/migrations/create_chameleons.rb",
|
133
144
|
"test/verified_output/migrations/create_reviews.rb",
|
134
145
|
"test/verified_output/migrations/create_users.rb",
|
135
146
|
"test/verified_output/migrations/created_at.rb",
|
147
|
+
"test/verified_output/migrations/deleted_incompatible_spot.rb",
|
148
|
+
"test/verified_output/migrations/deleted_spots.rb",
|
136
149
|
"test/verified_output/migrations/estimated_value_notes.rb",
|
137
|
-
"test/verified_output/migrations/landline.rb"
|
150
|
+
"test/verified_output/migrations/landline.rb",
|
151
|
+
"test/verified_output/migrations/renamed_old_spots.rb"
|
138
152
|
]
|
139
153
|
|
140
154
|
if s.respond_to? :specification_version then
|
@@ -150,6 +164,7 @@ Gem::Specification.new do |s|
|
|
150
164
|
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
151
165
|
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
152
166
|
s.add_development_dependency(%q<terminal-table>, [">= 0"])
|
167
|
+
s.add_development_dependency(%q<term-ansicolor>, [">= 0"])
|
153
168
|
else
|
154
169
|
s.add_dependency(%q<rails>, [">= 3.0.0"])
|
155
170
|
s.add_dependency(%q<faker>, [">= 0"])
|
@@ -160,6 +175,7 @@ Gem::Specification.new do |s|
|
|
160
175
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
161
176
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
162
177
|
s.add_dependency(%q<terminal-table>, [">= 0"])
|
178
|
+
s.add_dependency(%q<term-ansicolor>, [">= 0"])
|
163
179
|
end
|
164
180
|
else
|
165
181
|
s.add_dependency(%q<rails>, [">= 3.0.0"])
|
@@ -171,6 +187,7 @@ Gem::Specification.new do |s|
|
|
171
187
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
172
188
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
173
189
|
s.add_dependency(%q<terminal-table>, [">= 0"])
|
190
|
+
s.add_dependency(%q<term-ansicolor>, [">= 0"])
|
174
191
|
end
|
175
192
|
end
|
176
193
|
|
data/test/helper.rb
CHANGED
@@ -10,7 +10,7 @@ SimpleCov.adapters.define 'migrant' do
|
|
10
10
|
add_group 'Core Extensions', '/lib/migrant'
|
11
11
|
add_group 'Schema Data Types', '/lib/datatype'
|
12
12
|
end
|
13
|
-
SimpleCov.start 'migrant'
|
13
|
+
#SimpleCov.start 'migrant'
|
14
14
|
ENV['RAILS_ENV'] = 'test'
|
15
15
|
|
16
16
|
require 'rubygems'
|
@@ -47,6 +47,22 @@ class Profiler
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
# Mock some stubs on STDIN's eigenclass so we can fake user input
|
51
|
+
class << STDIN
|
52
|
+
# Simple mock for simulating user inputs
|
53
|
+
def _mock_responses(*responses)
|
54
|
+
@_responses ||= []
|
55
|
+
@_responses += responses
|
56
|
+
end
|
57
|
+
|
58
|
+
def gets
|
59
|
+
raise "STDIN.gets() called but no mocks to return. Did you set them up with _mock_responses()?" if @_responses.blank?
|
60
|
+
@_responses.slice!(0).tap do |response|
|
61
|
+
STDOUT.puts "{ANSWERED WITH: #{response}}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
50
66
|
# Reset database
|
51
67
|
db_path = File.join(File.dirname(__FILE__), 'rails_app', 'db', 'test.sqlite3')
|
52
68
|
File.delete(db_path) if File.exists?(db_path)
|
@@ -21,7 +21,7 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
21
21
|
to_test = File.open(migration_file, 'r') { |r| r.read}.strip
|
22
22
|
File.open(File.join(File.dirname(__FILE__), 'verified_output', 'migrations', template+'.rb'), 'r') do |file|
|
23
23
|
while (line = file.gets)
|
24
|
-
assert_not_nil(to_test.match(line.strip), "Generated migration #{migration_file} missing line: #{line}")
|
24
|
+
assert_not_nil(to_test.gsub(/[\.\(\)]/, '').match(line.gsub(/[\.\(\)]/, '').strip), "Generated migration #{migration_file} missing line: #{line.gsub(/[\.\(\)]/, '').strip}")
|
25
25
|
end
|
26
26
|
end
|
27
27
|
rake_migrate
|
@@ -152,6 +152,64 @@ class TestMigrationGenerator < Test::Unit::TestCase
|
|
152
152
|
generate_migrations
|
153
153
|
rake_migrate
|
154
154
|
end
|
155
|
+
|
156
|
+
should "remove columns when requested and confirmed by the user" do
|
157
|
+
Chameleon.structure
|
158
|
+
Chameleon.reset_structure!
|
159
|
+
Chameleon.no_structure
|
160
|
+
|
161
|
+
STDIN._mock_responses('D', 'y')
|
162
|
+
run_against_template('deleted_incompatible_spot')
|
163
|
+
end
|
164
|
+
|
165
|
+
should "not remove columns when the user does not confirm" do
|
166
|
+
Chameleon.reset_structure!
|
167
|
+
Chameleon.no_structure
|
168
|
+
|
169
|
+
STDIN._mock_responses('D', 'n')
|
170
|
+
generate_migrations
|
171
|
+
rake_migrate
|
172
|
+
Chameleon.structure do
|
173
|
+
spots
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
should "successfully rename a column missing from the schema to a new column specified by the user" do
|
178
|
+
Chameleon.structure do
|
179
|
+
old_spots
|
180
|
+
end
|
181
|
+
generate_migrations
|
182
|
+
rake_migrate
|
183
|
+
Chameleon.reset_structure!
|
184
|
+
Chameleon.structure do
|
185
|
+
new_spots
|
186
|
+
end
|
187
|
+
STDIN._mock_responses('M', 'new_spots')
|
188
|
+
run_against_template('renamed_old_spots')
|
189
|
+
end
|
190
|
+
|
191
|
+
should "transfer data to an new incompatible column if the operation is safe" do
|
192
|
+
Chameleon.reset_column_information
|
193
|
+
Chameleon.create!(:new_spots => "22")
|
194
|
+
Chameleon.reset_structure!
|
195
|
+
Chameleon.structure do
|
196
|
+
new_longer_spots "100", :as => :text
|
197
|
+
end
|
198
|
+
STDIN._mock_responses('M', 'new_longer_spots', 'M')
|
199
|
+
run_against_template('chameleons_added_new_longer_spots_and_moved_new_spots')
|
200
|
+
Chameleon.reset_column_information
|
201
|
+
assert_equal(Chameleon.first.new_longer_spots, "22")
|
202
|
+
end
|
203
|
+
|
204
|
+
should "remove any column if a user elects to when a column can't be moved due to incompatible types" do
|
205
|
+
Chameleon.reset_structure!
|
206
|
+
Chameleon.structure do
|
207
|
+
incompatible_spot 5
|
208
|
+
end
|
209
|
+
|
210
|
+
STDIN._mock_responses('M', 'incompatible_spot', 'N', 'Y')
|
211
|
+
run_against_template('added_incompatible_spot_and_deleted_spots')
|
212
|
+
end
|
155
213
|
end
|
156
214
|
end
|
157
215
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ChangedChameleonsAddedIncompatibleSpotAndDeletedSpots < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :chameleons, :incompatible_spot, :integer
|
4
|
+
remove_column :chameleons, :spots
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
add_column :chameleons, :spots, :string, :limit=>255
|
9
|
+
remove_column :chameleons, :incompatible_spot
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ChangedChameleonsAddedNewLongerSpotsAndMovedNewSpots < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :chameleons, :new_longer_spots, :text
|
4
|
+
puts "-- copy data from :new_spots to :new_longer_spots"
|
5
|
+
Chameleon.update_all("new_longer_spots = new_spots")
|
6
|
+
remove_column :chameleons, :new_spots
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
add_column :chameleons, :new_spots, :string, :limit=>255
|
11
|
+
puts "-- copy data from :new_longer_spots to :new_spots"
|
12
|
+
Chameleon.update_all("new_spots = new_longer_spots")
|
13
|
+
remove_column :chameleons, :new_longer_spots
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: migrant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
4
5
|
prerelease:
|
5
|
-
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 1.2.0
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- Pascal Houliston
|
@@ -10,7 +15,7 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-23 00:00:00 +00:00
|
14
19
|
default_executable:
|
15
20
|
dependencies:
|
16
21
|
- !ruby/object:Gem::Dependency
|
@@ -20,9 +25,14 @@ dependencies:
|
|
20
25
|
requirements:
|
21
26
|
- - ">="
|
22
27
|
- !ruby/object:Gem::Version
|
28
|
+
hash: 7
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 0
|
32
|
+
- 0
|
23
33
|
version: 3.0.0
|
24
|
-
type: :runtime
|
25
34
|
prerelease: false
|
35
|
+
type: :runtime
|
26
36
|
version_requirements: *id001
|
27
37
|
- !ruby/object:Gem::Dependency
|
28
38
|
name: faker
|
@@ -31,9 +41,12 @@ dependencies:
|
|
31
41
|
requirements:
|
32
42
|
- - ">="
|
33
43
|
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
34
47
|
version: "0"
|
35
|
-
type: :runtime
|
36
48
|
prerelease: false
|
49
|
+
type: :runtime
|
37
50
|
version_requirements: *id002
|
38
51
|
- !ruby/object:Gem::Dependency
|
39
52
|
name: thoughtbot-shoulda
|
@@ -42,9 +55,12 @@ dependencies:
|
|
42
55
|
requirements:
|
43
56
|
- - ">="
|
44
57
|
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
45
61
|
version: "0"
|
46
|
-
type: :development
|
47
62
|
prerelease: false
|
63
|
+
type: :development
|
48
64
|
version_requirements: *id003
|
49
65
|
- !ruby/object:Gem::Dependency
|
50
66
|
name: ansi
|
@@ -53,9 +69,12 @@ dependencies:
|
|
53
69
|
requirements:
|
54
70
|
- - ">="
|
55
71
|
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
56
75
|
version: "0"
|
57
|
-
type: :development
|
58
76
|
prerelease: false
|
77
|
+
type: :development
|
59
78
|
version_requirements: *id004
|
60
79
|
- !ruby/object:Gem::Dependency
|
61
80
|
name: jeweler
|
@@ -64,9 +83,12 @@ dependencies:
|
|
64
83
|
requirements:
|
65
84
|
- - ">="
|
66
85
|
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
67
89
|
version: "0"
|
68
|
-
type: :development
|
69
90
|
prerelease: false
|
91
|
+
type: :development
|
70
92
|
version_requirements: *id005
|
71
93
|
- !ruby/object:Gem::Dependency
|
72
94
|
name: turn
|
@@ -75,9 +97,12 @@ dependencies:
|
|
75
97
|
requirements:
|
76
98
|
- - ">="
|
77
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
78
103
|
version: "0"
|
79
|
-
type: :development
|
80
104
|
prerelease: false
|
105
|
+
type: :development
|
81
106
|
version_requirements: *id006
|
82
107
|
- !ruby/object:Gem::Dependency
|
83
108
|
name: sqlite3
|
@@ -86,9 +111,12 @@ dependencies:
|
|
86
111
|
requirements:
|
87
112
|
- - ">="
|
88
113
|
- !ruby/object:Gem::Version
|
114
|
+
hash: 3
|
115
|
+
segments:
|
116
|
+
- 0
|
89
117
|
version: "0"
|
90
|
-
type: :development
|
91
118
|
prerelease: false
|
119
|
+
type: :development
|
92
120
|
version_requirements: *id007
|
93
121
|
- !ruby/object:Gem::Dependency
|
94
122
|
name: simplecov
|
@@ -97,9 +125,12 @@ dependencies:
|
|
97
125
|
requirements:
|
98
126
|
- - ">="
|
99
127
|
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
100
131
|
version: "0"
|
101
|
-
type: :development
|
102
132
|
prerelease: false
|
133
|
+
type: :development
|
103
134
|
version_requirements: *id008
|
104
135
|
- !ruby/object:Gem::Dependency
|
105
136
|
name: terminal-table
|
@@ -108,10 +139,27 @@ dependencies:
|
|
108
139
|
requirements:
|
109
140
|
- - ">="
|
110
141
|
- !ruby/object:Gem::Version
|
142
|
+
hash: 3
|
143
|
+
segments:
|
144
|
+
- 0
|
111
145
|
version: "0"
|
112
|
-
type: :development
|
113
146
|
prerelease: false
|
147
|
+
type: :development
|
114
148
|
version_requirements: *id009
|
149
|
+
- !ruby/object:Gem::Dependency
|
150
|
+
name: term-ansicolor
|
151
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 3
|
157
|
+
segments:
|
158
|
+
- 0
|
159
|
+
version: "0"
|
160
|
+
prerelease: false
|
161
|
+
type: :development
|
162
|
+
version_requirements: *id010
|
115
163
|
description: Easier schema management for Rails that compliments your domain model.
|
116
164
|
email: 101pascal@gmail.com
|
117
165
|
executables: []
|
@@ -157,6 +205,7 @@ files:
|
|
157
205
|
- test/rails_app/app/models/business.rb
|
158
206
|
- test/rails_app/app/models/business_category.rb
|
159
207
|
- test/rails_app/app/models/category.rb
|
208
|
+
- test/rails_app/app/models/chameleon.rb
|
160
209
|
- test/rails_app/app/models/customer.rb
|
161
210
|
- test/rails_app/app/models/user.rb
|
162
211
|
- test/rails_app/app/views/layouts/application.html.erb
|
@@ -188,15 +237,21 @@ files:
|
|
188
237
|
- test/test_migration_generator.rb
|
189
238
|
- test/test_validations.rb
|
190
239
|
- test/test_zzz_performance.rb
|
240
|
+
- test/verified_output/migrations/added_incompatible_spot_and_deleted_spots.rb
|
191
241
|
- test/verified_output/migrations/business_id.rb
|
242
|
+
- test/verified_output/migrations/chameleons_added_new_longer_spots_and_moved_new_spots.rb
|
192
243
|
- test/verified_output/migrations/create_business_categories.rb
|
193
244
|
- test/verified_output/migrations/create_businesses.rb
|
194
245
|
- test/verified_output/migrations/create_categories.rb
|
246
|
+
- test/verified_output/migrations/create_chameleons.rb
|
195
247
|
- test/verified_output/migrations/create_reviews.rb
|
196
248
|
- test/verified_output/migrations/create_users.rb
|
197
249
|
- test/verified_output/migrations/created_at.rb
|
250
|
+
- test/verified_output/migrations/deleted_incompatible_spot.rb
|
251
|
+
- test/verified_output/migrations/deleted_spots.rb
|
198
252
|
- test/verified_output/migrations/estimated_value_notes.rb
|
199
253
|
- test/verified_output/migrations/landline.rb
|
254
|
+
- test/verified_output/migrations/renamed_old_spots.rb
|
200
255
|
has_rdoc: true
|
201
256
|
homepage: http://github.com/pascalh1011/migrant
|
202
257
|
licenses: []
|
@@ -211,7 +266,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
211
266
|
requirements:
|
212
267
|
- - ">="
|
213
268
|
- !ruby/object:Gem::Version
|
214
|
-
hash:
|
269
|
+
hash: 3
|
215
270
|
segments:
|
216
271
|
- 0
|
217
272
|
version: "0"
|
@@ -220,6 +275,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
275
|
requirements:
|
221
276
|
- - ">="
|
222
277
|
- !ruby/object:Gem::Version
|
278
|
+
hash: 3
|
279
|
+
segments:
|
280
|
+
- 0
|
223
281
|
version: "0"
|
224
282
|
requirements: []
|
225
283
|
|
@@ -236,6 +294,7 @@ test_files:
|
|
236
294
|
- test/rails_app/app/models/business.rb
|
237
295
|
- test/rails_app/app/models/business_category.rb
|
238
296
|
- test/rails_app/app/models/category.rb
|
297
|
+
- test/rails_app/app/models/chameleon.rb
|
239
298
|
- test/rails_app/app/models/customer.rb
|
240
299
|
- test/rails_app/app/models/user.rb
|
241
300
|
- test/rails_app/config/application.rb
|
@@ -257,12 +316,18 @@ test_files:
|
|
257
316
|
- test/test_migration_generator.rb
|
258
317
|
- test/test_validations.rb
|
259
318
|
- test/test_zzz_performance.rb
|
319
|
+
- test/verified_output/migrations/added_incompatible_spot_and_deleted_spots.rb
|
260
320
|
- test/verified_output/migrations/business_id.rb
|
321
|
+
- test/verified_output/migrations/chameleons_added_new_longer_spots_and_moved_new_spots.rb
|
261
322
|
- test/verified_output/migrations/create_business_categories.rb
|
262
323
|
- test/verified_output/migrations/create_businesses.rb
|
263
324
|
- test/verified_output/migrations/create_categories.rb
|
325
|
+
- test/verified_output/migrations/create_chameleons.rb
|
264
326
|
- test/verified_output/migrations/create_reviews.rb
|
265
327
|
- test/verified_output/migrations/create_users.rb
|
266
328
|
- test/verified_output/migrations/created_at.rb
|
329
|
+
- test/verified_output/migrations/deleted_incompatible_spot.rb
|
330
|
+
- test/verified_output/migrations/deleted_spots.rb
|
267
331
|
- test/verified_output/migrations/estimated_value_notes.rb
|
268
332
|
- test/verified_output/migrations/landline.rb
|
333
|
+
- test/verified_output/migrations/renamed_old_spots.rb
|