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 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