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 CHANGED
@@ -11,4 +11,5 @@ group :development do
11
11
  gem "sqlite3"
12
12
  gem "simplecov"
13
13
  gem "terminal-table"
14
+ gem "term-ansicolor"
14
15
  end
@@ -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.2
1
+ 1.2.0
@@ -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
- <% @added_columns.each do |field, options| %>
16
- remove_column :<%= @table_name %>, :<%= field %>
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
- @changed_columns << [field_name, options, db_schema[field_name]]
36
+ change_column(field_name, options, db_schema[field_name])
37
37
  else
38
- @added_columns << [field_name, options]
38
+ add_column(field_name, options)
39
39
  end
40
40
  end
41
41
  rescue DataType::DangerousMigration
42
- puts "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)"
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], ['indexed', @new_indexes]].reject { |v| v[1].empty? }.collect { |v| "_#{v[0]}_"+v[1].collect(&:first).join('_') }.join('_and')
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
- puts "Wrote #{filename}..."
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 = {}
@@ -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
- !(@columns.blank? && @indexes.blank?)
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
@@ -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
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{migrant}
8
- s.version = "1.1.2"
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}
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
 
@@ -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)
@@ -0,0 +1,5 @@
1
+ class Chameleon < ActiveRecord::Base
2
+ structure do
3
+ spots
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ class CreateChameleons < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :chameleons do |t|
4
+ t.string :spots
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :chameleons
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class ChangedChameleonsDeletedIncompatibleSpot < ActiveRecord::Migration
2
+ def self.up
3
+ remove_column :chameleons, :incompatible_spot
4
+ end
5
+
6
+ def self.down
7
+ add_column :chameleons, :incompatible_spot, :integer, :limit=>nil
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class ChangedChameleonsDeletedSpots < ActiveRecord::Migration
2
+ def self.up
3
+ remove_column :chameleons, :spots
4
+ end
5
+
6
+ def self.down
7
+ add_column :chameleons, :spots, :string, :limit=>255
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class ChangedChameleonsRenamedOldSpots < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :chameleons, :old_spots, :new_spots
4
+ end
5
+
6
+ def self.down
7
+ rename_column :chameleons, :new_spots, :old_spots
8
+ end
9
+ 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
- version: 1.1.2
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-12 00:00:00 +00:00
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: 146286865
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