migrant 1.1.2 → 1.2.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.
- 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
|