1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
|
# frozen_string_literal: true
require "cases/migration/helper"
module ActiveRecord
class Migration
class ColumnsTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
self.use_transactional_tests = false
# FIXME: this is more of an integration test with AR::Base and the
# schema modifications. Maybe we should move this?
def test_add_rename
add_column "test_models", "girlfriend", :string
TestModel.reset_column_information
TestModel.create girlfriend: "bobette"
rename_column "test_models", "girlfriend", "exgirlfriend"
TestModel.reset_column_information
bob = TestModel.first
assert_equal "bobette", bob.exgirlfriend
end
# FIXME: another integration test. We should decouple this from the
# AR::Base implementation.
def test_rename_column_using_symbol_arguments
add_column :test_models, :first_name, :string
TestModel.create first_name: "foo"
rename_column :test_models, :first_name, :nick_name
TestModel.reset_column_information
assert_includes TestModel.column_names, "nick_name"
assert_equal ["foo"], TestModel.all.map(&:nick_name)
end
# FIXME: another integration test. We should decouple this from the
# AR::Base implementation.
def test_rename_column
add_column "test_models", "first_name", "string"
TestModel.create first_name: "foo"
rename_column "test_models", "first_name", "nick_name"
TestModel.reset_column_information
assert_includes TestModel.column_names, "nick_name"
assert_equal ["foo"], TestModel.all.map(&:nick_name)
end
def test_rename_column_preserves_default_value_not_null
add_column "test_models", "salary", :integer, default: 70000
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
assert_equal "70000", default_before
rename_column "test_models", "salary", "annual_salary"
assert_includes TestModel.column_names, "annual_salary"
default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
assert_equal "70000", default_after
end
if current_adapter?(:Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
assert_predicate connection.columns("test_models").find { |c| c.name == "id_test" }, :auto_increment?
TestModel.reset_column_information
ensure
rename_column "test_models", "id_test", "id"
end
end
def test_rename_nonexistent_column
exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
ActiveRecord::StatementInvalid
else
ActiveRecord::ActiveRecordError
end
assert_raise(exception) do
rename_column "test_models", "nonexistent", "should_fail"
end
end
def test_rename_column_with_sql_reserved_word
add_column "test_models", "first_name", :string
rename_column "test_models", "first_name", "group"
assert_includes TestModel.column_names, "group"
end
def test_rename_column_with_an_index
add_column "test_models", :hat_name, :string
add_index :test_models, :hat_name
assert_equal 1, connection.indexes("test_models").size
rename_column "test_models", "hat_name", "name"
assert_equal ["index_test_models_on_name"], connection.indexes("test_models").map(&:name)
end
def test_rename_column_with_multi_column_index
add_column "test_models", :hat_size, :integer
add_column "test_models", :hat_style, :string, limit: 100
add_index "test_models", ["hat_style", "hat_size"], unique: true
rename_column "test_models", "hat_size", "size"
assert_equal ["index_test_models_on_hat_style_and_size"], connection.indexes("test_models").map(&:name)
rename_column "test_models", "hat_style", "style"
assert_equal ["index_test_models_on_style_and_size"], connection.indexes("test_models").map(&:name)
end
def test_rename_column_does_not_rename_custom_named_index
add_column "test_models", :hat_name, :string
add_index :test_models, :hat_name, name: "idx_hat_name"
assert_equal 1, connection.indexes("test_models").size
rename_column "test_models", "hat_name", "name"
assert_equal ["idx_hat_name"], connection.indexes("test_models").map(&:name)
end
def test_remove_column_with_index
add_column "test_models", :hat_name, :string
add_index :test_models, :hat_name
assert_equal 1, connection.indexes("test_models").size
remove_column("test_models", "hat_name")
assert_equal 0, connection.indexes("test_models").size
end
def test_remove_column_with_multi_column_index
# MariaDB starting with 10.2.8
# Dropping a column that is part of a multi-column UNIQUE constraint is not permitted.
skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.database_version >= "10.2.8"
add_column "test_models", :hat_size, :integer
add_column "test_models", :hat_style, :string, limit: 100
add_index "test_models", ["hat_style", "hat_size"], unique: true
assert_equal 1, connection.indexes("test_models").size
remove_column("test_models", "hat_size")
# Every database and/or database adapter has their own behavior
# if it drops the multi-column index when any of the indexed columns dropped by remove_column.
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
assert_equal [], connection.indexes("test_models").map(&:name)
else
assert_equal ["index_test_models_on_hat_style_and_hat_size"], connection.indexes("test_models").map(&:name)
end
end
def test_change_type_of_not_null_column
change_column "test_models", "updated_at", :datetime, null: false
change_column "test_models", "updated_at", :datetime, null: false
TestModel.reset_column_information
assert_equal false, TestModel.columns_hash["updated_at"].null
ensure
change_column "test_models", "updated_at", :datetime, null: true
end
def test_change_column_nullability
add_column "test_models", "funny", :boolean
assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
change_column "test_models", "funny", :boolean, null: false, default: true
TestModel.reset_column_information
assert_not TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
change_column "test_models", "funny", :boolean, null: true
TestModel.reset_column_information
assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
end
def test_change_column
add_column "test_models", "age", :integer
add_column "test_models", "approved", :boolean, default: true
old_columns = connection.columns(TestModel.table_name)
assert old_columns.find { |c| c.name == "age" && c.type == :integer }
change_column "test_models", "age", :string
new_columns = connection.columns(TestModel.table_name)
assert_not new_columns.find { |c| c.name == "age" && c.type == :integer }
assert new_columns.find { |c| c.name == "age" && c.type == :string }
old_columns = connection.columns(TestModel.table_name)
assert old_columns.find { |c|
default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == "approved" && c.type == :boolean && default == true
}
change_column :test_models, :approved, :boolean, default: false
new_columns = connection.columns(TestModel.table_name)
assert_not new_columns.find { |c|
default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == "approved" && c.type == :boolean && default == true
}
assert new_columns.find { |c|
default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == "approved" && c.type == :boolean && default == false
}
change_column :test_models, :approved, :boolean, default: true
end
def test_change_column_with_nil_default
add_column "test_models", "contributor", :boolean, default: true
assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil
TestModel.reset_column_information
assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_to_drop_default_with_null_false
add_column "test_models", "contributor", :boolean, default: true, null: false
assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil, null: false
TestModel.reset_column_information
assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_with_new_default
add_column "test_models", "administrator", :boolean, default: true
assert_predicate TestModel.new, :administrator?
change_column "test_models", "administrator", :boolean, default: false
TestModel.reset_column_information
assert_not_predicate TestModel.new, :administrator?
end
def test_change_column_with_custom_index_name
add_column "test_models", "category", :string
add_index :test_models, :category, name: "test_models_categories_idx"
assert_equal ["test_models_categories_idx"], connection.indexes("test_models").map(&:name)
change_column "test_models", "category", :string, null: false, default: "article"
assert_equal ["test_models_categories_idx"], connection.indexes("test_models").map(&:name)
end
def test_change_column_with_long_index_name
table_name_prefix = "test_models_"
long_index_name = table_name_prefix + ("x" * (connection.allowed_index_name_length - table_name_prefix.length))
add_column "test_models", "category", :string
add_index :test_models, :category, name: long_index_name
change_column "test_models", "category", :string, null: false, default: "article"
assert_equal [long_index_name], connection.indexes("test_models").map(&:name)
end
def test_change_column_default
add_column "test_models", "first_name", :string
connection.change_column_default "test_models", "first_name", "Tester"
assert_equal "Tester", TestModel.new.first_name
end
def test_change_column_default_to_null
add_column "test_models", "first_name", :string
connection.change_column_default "test_models", "first_name", nil
assert_nil TestModel.new.first_name
end
def test_change_column_default_with_from_and_to
add_column "test_models", "first_name", :string
connection.change_column_default "test_models", "first_name", from: nil, to: "Tester"
assert_equal "Tester", TestModel.new.first_name
end
def test_remove_column_no_second_parameter_raises_exception
assert_raise(ArgumentError) { connection.remove_column("funny") }
end
def test_removing_and_renaming_column_preserves_custom_primary_key
connection.create_table "my_table", primary_key: "my_table_id", force: true do |t|
t.integer "col_one"
t.string "col_two", limit: 128, null: false
end
remove_column("my_table", "col_two")
rename_column("my_table", "col_one", "col_three")
assert_equal "my_table_id", connection.primary_key("my_table")
ensure
connection.drop_table(:my_table) rescue nil
end
def test_column_with_index
connection.create_table "my_table", force: true do |t|
t.string :item_number, index: true
end
assert connection.index_exists?("my_table", :item_number, name: :index_my_table_on_item_number)
ensure
connection.drop_table(:my_table) rescue nil
end
def test_add_column_without_column_name
e = assert_raise ArgumentError do
connection.create_table "my_table", force: true do |t|
t.timestamp
end
end
assert_equal "Missing column name(s) for timestamp", e.message
ensure
connection.drop_table :my_table, if_exists: true
end
end
end
end
|