activerecord 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (86) hide show
  1. data/CHANGELOG +34 -0
  2. data/README +0 -0
  3. data/Rakefile +6 -5
  4. data/lib/active_record.rb +8 -10
  5. data/lib/active_record/association_preload.rb +17 -12
  6. data/lib/active_record/associations.rb +45 -27
  7. data/lib/active_record/associations/association_collection.rb +8 -5
  8. data/lib/active_record/associations/association_proxy.rb +2 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +0 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -3
  12. data/lib/active_record/associations/has_many_association.rb +16 -4
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -1
  14. data/lib/active_record/associations/has_one_association.rb +2 -2
  15. data/lib/active_record/associations/has_one_through_association.rb +4 -0
  16. data/lib/active_record/base.rb +33 -15
  17. data/lib/active_record/calculations.rb +20 -7
  18. data/lib/active_record/callbacks.rb +0 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -6
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +17 -10
  21. data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -0
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +53 -24
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -20
  24. data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
  25. data/lib/active_record/dirty.rb +10 -3
  26. data/lib/active_record/fixtures.rb +0 -0
  27. data/lib/active_record/locking/optimistic.rb +1 -0
  28. data/lib/active_record/migration.rb +35 -8
  29. data/lib/active_record/named_scope.rb +6 -1
  30. data/lib/active_record/observer.rb +7 -5
  31. data/lib/active_record/test_case.rb +13 -2
  32. data/lib/active_record/validations.rb +19 -9
  33. data/lib/active_record/version.rb +1 -1
  34. data/test/cases/active_schema_test_postgresql.rb +2 -2
  35. data/test/cases/adapter_test.rb +1 -1
  36. data/test/cases/associations/belongs_to_associations_test.rb +19 -0
  37. data/test/cases/associations/cascaded_eager_loading_test.rb +13 -1
  38. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  39. data/test/cases/associations/eager_test.rb +25 -1
  40. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +27 -5
  41. data/test/cases/associations/has_many_associations_test.rb +106 -4
  42. data/test/cases/associations/has_many_through_associations_test.rb +10 -0
  43. data/test/cases/associations/has_one_associations_test.rb +22 -0
  44. data/test/cases/associations/has_one_through_associations_test.rb +44 -5
  45. data/test/cases/associations/join_model_test.rb +7 -0
  46. data/test/cases/associations_test.rb +2 -2
  47. data/test/cases/attribute_methods_test.rb +10 -10
  48. data/test/cases/base_test.rb +39 -16
  49. data/test/cases/calculations_test.rb +53 -1
  50. data/test/cases/column_definition_test.rb +36 -0
  51. data/test/cases/database_statements_test.rb +12 -0
  52. data/test/cases/defaults_test.rb +1 -1
  53. data/test/cases/deprecated_finder_test.rb +0 -0
  54. data/test/cases/dirty_test.rb +94 -0
  55. data/test/cases/finder_test.rb +7 -0
  56. data/test/cases/fixtures_test.rb +0 -0
  57. data/test/cases/helper.rb +5 -5
  58. data/test/cases/inheritance_test.rb +9 -2
  59. data/test/cases/lifecycle_test.rb +54 -1
  60. data/test/cases/locking_test.rb +20 -0
  61. data/test/cases/method_scoping_test.rb +11 -1
  62. data/test/cases/migration_test.rb +147 -22
  63. data/test/cases/multiple_db_test.rb +1 -1
  64. data/test/cases/named_scope_test.rb +50 -1
  65. data/test/cases/query_cache_test.rb +4 -3
  66. data/test/cases/readonly_test.rb +0 -0
  67. data/test/cases/reflection_test.rb +3 -3
  68. data/test/cases/schema_dumper_test.rb +46 -0
  69. data/test/cases/unconnected_test.rb +0 -0
  70. data/test/cases/validations_test.rb +30 -5
  71. data/test/debug.log +358 -0
  72. data/test/fixtures/fixture_database.sqlite3 +0 -0
  73. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  74. data/test/models/author.rb +4 -0
  75. data/test/models/category.rb +1 -0
  76. data/test/models/company.rb +10 -1
  77. data/test/models/developer.rb +4 -1
  78. data/test/models/person.rb +1 -1
  79. data/test/models/post.rb +6 -1
  80. data/test/models/project.rb +1 -1
  81. data/test/models/reply.rb +0 -0
  82. data/test/models/topic.rb +1 -0
  83. data/test/schema/mysql_specific_schema.rb +2 -2
  84. data/test/schema/schema.rb +8 -0
  85. metadata +11 -5
  86. data/lib/active_record/vendor/db2.rb +0 -362
@@ -0,0 +1,12 @@
1
+ require "cases/helper"
2
+
3
+ class DatabaseStatementsTest < ActiveRecord::TestCase
4
+ def setup
5
+ @connection = ActiveRecord::Base.connection
6
+ end
7
+
8
+ def test_insert_should_return_the_inserted_id
9
+ id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
10
+ assert_not_nil id
11
+ end
12
+ end
@@ -5,7 +5,7 @@ require 'models/entrant'
5
5
  class DefaultTest < ActiveRecord::TestCase
6
6
  def test_nil_defaults_for_not_null_columns
7
7
  column_defaults =
8
- if current_adapter?(:MysqlAdapter) && Mysql.client_version < 50051
8
+ if current_adapter?(:MysqlAdapter) && (Mysql.client_version < 50051 || (50100..50122).include?(Mysql.client_version))
9
9
  { 'id' => nil, 'name' => '', 'course_id' => nil }
10
10
  else
11
11
  { 'id' => nil, 'name' => nil, 'course_id' => nil }
File without changes
@@ -2,6 +2,7 @@ require 'cases/helper'
2
2
  require 'models/topic' # For booleans
3
3
  require 'models/pirate' # For timestamps
4
4
  require 'models/parrot'
5
+ require 'models/person' # For optimistic locking
5
6
 
6
7
  class Pirate # Just reopening it, not defining it
7
8
  attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
@@ -54,6 +55,33 @@ class DirtyTest < ActiveRecord::TestCase
54
55
  end
55
56
  end
56
57
 
58
+ def test_zero_to_blank_marked_as_changed
59
+ pirate = Pirate.new
60
+ pirate.catchphrase = "Yarrrr, me hearties"
61
+ pirate.parrot_id = 1
62
+ pirate.save
63
+
64
+ # check the change from 1 to ''
65
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
66
+ pirate.parrot_id = ''
67
+ assert pirate.parrot_id_changed?
68
+ assert_equal([1, nil], pirate.parrot_id_change)
69
+ pirate.save
70
+
71
+ # check the change from nil to 0
72
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
73
+ pirate.parrot_id = 0
74
+ assert pirate.parrot_id_changed?
75
+ assert_equal([nil, 0], pirate.parrot_id_change)
76
+ pirate.save
77
+
78
+ # check the change from 0 to ''
79
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
80
+ pirate.parrot_id = ''
81
+ assert pirate.parrot_id_changed?
82
+ assert_equal([0, nil], pirate.parrot_id_change)
83
+ end
84
+
57
85
  def test_object_should_be_changed_if_any_attribute_is_changed
58
86
  pirate = Pirate.new
59
87
  assert !pirate.changed?
@@ -125,6 +153,24 @@ class DirtyTest < ActiveRecord::TestCase
125
153
  end
126
154
  end
127
155
 
156
+ def test_partial_update_with_optimistic_locking
157
+ person = Person.new(:first_name => 'foo')
158
+ old_lock_version = 1
159
+
160
+ with_partial_updates Person, false do
161
+ assert_queries(2) { 2.times { person.save! } }
162
+ Person.update_all({ :first_name => 'baz' }, :id => person.id)
163
+ end
164
+
165
+ with_partial_updates Person, true do
166
+ assert_queries(0) { 2.times { person.save! } }
167
+ assert_equal old_lock_version, person.reload.lock_version
168
+
169
+ assert_queries(1) { person.first_name = 'bar'; person.save! }
170
+ assert_not_equal old_lock_version, person.reload.lock_version
171
+ end
172
+ end
173
+
128
174
  def test_changed_attributes_should_be_preserved_if_save_failure
129
175
  pirate = Pirate.new
130
176
  pirate.parrot_id = 1
@@ -145,6 +191,54 @@ class DirtyTest < ActiveRecord::TestCase
145
191
  assert !pirate.changed?
146
192
  end
147
193
 
194
+ def test_reverted_changes_are_not_dirty
195
+ phrase = "shiver me timbers"
196
+ pirate = Pirate.create!(:catchphrase => phrase)
197
+ pirate.catchphrase = "*hic*"
198
+ assert pirate.changed?
199
+ pirate.catchphrase = phrase
200
+ assert !pirate.changed?
201
+ end
202
+
203
+ def test_reverted_changes_are_not_dirty_after_multiple_changes
204
+ phrase = "shiver me timbers"
205
+ pirate = Pirate.create!(:catchphrase => phrase)
206
+ 10.times do |i|
207
+ pirate.catchphrase = "*hic*" * i
208
+ assert pirate.changed?
209
+ end
210
+ assert pirate.changed?
211
+ pirate.catchphrase = phrase
212
+ assert !pirate.changed?
213
+ end
214
+
215
+
216
+ def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
217
+ pirate = Pirate.create!(:catchphrase => "Yar!")
218
+
219
+ pirate.parrot_id = 1
220
+ assert pirate.changed?
221
+ assert pirate.parrot_id_changed?
222
+ assert !pirate.catchphrase_changed?
223
+
224
+ pirate.parrot_id = nil
225
+ assert !pirate.changed?
226
+ assert !pirate.parrot_id_changed?
227
+ assert !pirate.catchphrase_changed?
228
+ end
229
+
230
+ def test_save_should_store_serialized_attributes_even_with_partial_updates
231
+ with_partial_updates(Topic) do
232
+ topic = Topic.create!(:content => {:a => "a"})
233
+ topic.content[:b] = "b"
234
+ #assert topic.changed? # Known bug, will fail
235
+ topic.save!
236
+ assert_equal "b", topic.content[:b]
237
+ topic.reload
238
+ assert_equal "b", topic.content[:b]
239
+ end
240
+ end
241
+
148
242
  private
149
243
  def with_partial_updates(klass, on = true)
150
244
  old = klass.partial_updates?
@@ -1,5 +1,6 @@
1
1
  require "cases/helper"
2
2
  require 'models/author'
3
+ require 'models/categorization'
3
4
  require 'models/comment'
4
5
  require 'models/company'
5
6
  require 'models/topic'
@@ -394,6 +395,12 @@ class FinderTest < ActiveRecord::TestCase
394
395
  assert_equal '1,1,1', bind('?', os)
395
396
  end
396
397
 
398
+ def test_named_bind_with_postgresql_type_casts
399
+ l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
400
+ assert_nothing_raised(&l)
401
+ assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call
402
+ end
403
+
397
404
  def test_string_sanitation
398
405
  assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
399
406
  assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
File without changes
@@ -32,13 +32,13 @@ end
32
32
  ActiveRecord::Base.connection.class.class_eval do
33
33
  IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
34
34
 
35
- def execute_with_counting(sql, name = nil, &block)
36
- $query_count ||= 0
37
- $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
38
- execute_without_counting(sql, name, &block)
35
+ def execute_with_query_record(sql, name = nil, &block)
36
+ $queries_executed ||= []
37
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
38
+ execute_without_query_record(sql, name, &block)
39
39
  end
40
40
 
41
- alias_method_chain :execute, :counting
41
+ alias_method_chain :execute, :query_record
42
42
  end
43
43
 
44
44
  # Make with_scope public for tests
@@ -191,6 +191,13 @@ class InheritanceTest < ActiveRecord::TestCase
191
191
  assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed"
192
192
  end
193
193
 
194
+ def test_eager_load_belongs_to_primary_key_quoting
195
+ con = Account.connection
196
+ assert_sql(/\(#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)\)/) do
197
+ Account.find(1, :include => :firm)
198
+ end
199
+ end
200
+
194
201
  def test_alt_eager_loading
195
202
  switch_to_alt_inheritance_column
196
203
  test_eager_load_belongs_to_something_inherited
@@ -223,11 +230,11 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
223
230
  fixtures :companies
224
231
 
225
232
  def setup
226
- Dependencies.log_activity = true
233
+ ActiveSupport::Dependencies.log_activity = true
227
234
  end
228
235
 
229
236
  def teardown
230
- Dependencies.log_activity = false
237
+ ActiveSupport::Dependencies.log_activity = false
231
238
  self.class.const_remove :FirmOnTheFly rescue nil
232
239
  Firm.const_remove :FirmOnTheFly rescue nil
233
240
  end
@@ -2,6 +2,7 @@ require "cases/helper"
2
2
  require 'models/topic'
3
3
  require 'models/developer'
4
4
  require 'models/reply'
5
+ require 'models/minimalistic'
5
6
 
6
7
  class Topic; def after_find() end end
7
8
  class Developer; def after_find() end end
@@ -44,6 +45,14 @@ class TopicObserver < ActiveRecord::Observer
44
45
  end
45
46
  end
46
47
 
48
+ class MinimalisticObserver < ActiveRecord::Observer
49
+ attr_reader :minimalistic
50
+
51
+ def after_find(minimalistic)
52
+ @minimalistic = minimalistic
53
+ end
54
+ end
55
+
47
56
  class MultiObserver < ActiveRecord::Observer
48
57
  attr_reader :record
49
58
 
@@ -65,7 +74,7 @@ class MultiObserver < ActiveRecord::Observer
65
74
  end
66
75
 
67
76
  class LifecycleTest < ActiveRecord::TestCase
68
- fixtures :topics, :developers
77
+ fixtures :topics, :developers, :minimalistics
69
78
 
70
79
  def test_before_destroy
71
80
  original_count = Topic.count
@@ -134,6 +143,50 @@ class LifecycleTest < ActiveRecord::TestCase
134
143
  assert_equal developer.name, multi_observer.record.name
135
144
  end
136
145
 
146
+ def test_after_find_can_be_observed_when_its_not_defined_on_the_model
147
+ observer = MinimalisticObserver.instance
148
+ assert_equal Minimalistic, MinimalisticObserver.observed_class
149
+
150
+ minimalistic = Minimalistic.find(1)
151
+ assert_equal minimalistic, observer.minimalistic
152
+ end
153
+
154
+ def test_after_find_can_be_observed_when_its_defined_on_the_model
155
+ observer = TopicObserver.instance
156
+ assert_equal Topic, TopicObserver.observed_class
157
+
158
+ topic = Topic.find(1)
159
+ assert_equal topic, observer.topic
160
+ end
161
+
162
+ def test_after_find_is_not_created_if_its_not_used
163
+ # use a fresh class so an observer can't have defined an
164
+ # after_find on it
165
+ model_class = Class.new(ActiveRecord::Base)
166
+ observer_class = Class.new(ActiveRecord::Observer)
167
+ observer_class.observe(model_class)
168
+
169
+ observer = observer_class.instance
170
+
171
+ assert !model_class.method_defined?(:after_find)
172
+ end
173
+
174
+ def test_after_find_is_not_clobbered_if_it_already_exists
175
+ # use a fresh observer class so we can instantiate it (Observer is
176
+ # a Singleton)
177
+ model_class = Class.new(ActiveRecord::Base) do
178
+ def after_find; end
179
+ end
180
+ original_method = model_class.instance_method(:after_find)
181
+ observer_class = Class.new(ActiveRecord::Observer) do
182
+ def after_find; end
183
+ end
184
+ observer_class.observe(model_class)
185
+
186
+ observer = observer_class.instance
187
+ assert_equal original_method, model_class.instance_method(:after_find)
188
+ end
189
+
137
190
  def test_invalid_observer
138
191
  assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
139
192
  end
@@ -29,10 +29,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
29
29
  assert_equal 0, p1.lock_version
30
30
  assert_equal 0, p2.lock_version
31
31
 
32
+ p1.first_name = 'stu'
32
33
  p1.save!
33
34
  assert_equal 1, p1.lock_version
34
35
  assert_equal 0, p2.lock_version
35
36
 
37
+ p2.first_name = 'sue'
36
38
  assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
37
39
  end
38
40
 
@@ -42,11 +44,14 @@ class OptimisticLockingTest < ActiveRecord::TestCase
42
44
  assert_equal 0, p1.lock_version
43
45
  assert_equal 0, p2.lock_version
44
46
 
47
+ p1.first_name = 'stu'
45
48
  p1.save!
46
49
  assert_equal 1, p1.lock_version
47
50
  assert_equal 0, p2.lock_version
48
51
 
52
+ p2.first_name = 'sue'
49
53
  assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
54
+ p2.first_name = 'sue2'
50
55
  assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
51
56
  end
52
57
 
@@ -54,15 +59,18 @@ class OptimisticLockingTest < ActiveRecord::TestCase
54
59
  p1 = Person.new(:first_name => 'anika')
55
60
  assert_equal 0, p1.lock_version
56
61
 
62
+ p1.first_name = 'anika2'
57
63
  p1.save!
58
64
  p2 = Person.find(p1.id)
59
65
  assert_equal 0, p1.lock_version
60
66
  assert_equal 0, p2.lock_version
61
67
 
68
+ p1.first_name = 'anika3'
62
69
  p1.save!
63
70
  assert_equal 1, p1.lock_version
64
71
  assert_equal 0, p2.lock_version
65
72
 
73
+ p2.first_name = 'sue'
66
74
  assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
67
75
  end
68
76
 
@@ -81,10 +89,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
81
89
  assert_equal 0, t1.version
82
90
  assert_equal 0, t2.version
83
91
 
92
+ t1.tps_report_number = 700
84
93
  t1.save!
85
94
  assert_equal 1, t1.version
86
95
  assert_equal 0, t2.version
87
96
 
97
+ t2.tps_report_number = 800
88
98
  assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
89
99
  end
90
100
 
@@ -93,6 +103,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
93
103
  assert_equal 0, p1.lock_version
94
104
  assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
95
105
 
106
+ p1.first_name = 'bianca2'
96
107
  p1.save!
97
108
  assert_equal 1, p1.lock_version
98
109
  assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
@@ -146,6 +157,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase
146
157
  assert ref.save
147
158
  end
148
159
 
160
+ # Useful for partial updates, don't only update the lock_version if there
161
+ # is nothing else being updated.
162
+ def test_update_without_attributes_does_not_only_update_lock_version
163
+ assert_nothing_raised do
164
+ p1 = Person.new(:first_name => 'anika')
165
+ p1.send(:update_with_lock, [])
166
+ end
167
+ end
168
+
149
169
  private
150
170
 
151
171
  def add_counter_column_to(model)
@@ -6,7 +6,7 @@ require 'models/post'
6
6
  require 'models/category'
7
7
 
8
8
  class MethodScopingTest < ActiveRecord::TestCase
9
- fixtures :developers, :projects, :comments, :posts
9
+ fixtures :developers, :projects, :comments, :posts, :developers_projects
10
10
 
11
11
  def test_set_conditions
12
12
  Developer.with_scope(:find => { :conditions => 'just a test...' }) do
@@ -87,6 +87,16 @@ class MethodScopingTest < ActiveRecord::TestCase
87
87
  assert_equal 1, scoped_developers.size
88
88
  end
89
89
 
90
+ def test_scoped_find_joins
91
+ scoped_developers = Developer.with_scope(:find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do
92
+ Developer.find(:all, :conditions => 'developers_projects.project_id = 2')
93
+ end
94
+ assert scoped_developers.include?(developers(:david))
95
+ assert !scoped_developers.include?(developers(:jamis))
96
+ assert_equal 1, scoped_developers.size
97
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
98
+ end
99
+
90
100
  def test_scoped_count_include
91
101
  # with the include, will retrieve only developers for the given project
92
102
  Developer.with_scope(:find => { :include => :projects }) do
@@ -3,6 +3,7 @@ require 'bigdecimal/util'
3
3
 
4
4
  require 'models/person'
5
5
  require 'models/topic'
6
+ require 'models/developer'
6
7
 
7
8
  require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
8
9
  require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
@@ -153,9 +154,10 @@ if ActiveRecord::Base.connection.supports_migrations?
153
154
 
154
155
  t.column :default_int, :integer
155
156
 
156
- t.column :one_int, :integer, :limit => 1
157
- t.column :four_int, :integer, :limit => 4
158
- t.column :eight_int, :integer, :limit => 8
157
+ t.column :one_int, :integer, :limit => 1
158
+ t.column :four_int, :integer, :limit => 4
159
+ t.column :eight_int, :integer, :limit => 8
160
+ t.column :eleven_int, :integer, :limit => 11
159
161
  end
160
162
  end
161
163
 
@@ -167,12 +169,20 @@ if ActiveRecord::Base.connection.supports_migrations?
167
169
  one = columns.detect { |c| c.name == "one_int" }
168
170
  four = columns.detect { |c| c.name == "four_int" }
169
171
  eight = columns.detect { |c| c.name == "eight_int" }
172
+ eleven = columns.detect { |c| c.name == "eleven_int" }
170
173
 
171
174
  if current_adapter?(:PostgreSQLAdapter)
172
175
  assert_equal 'integer', default.sql_type
173
176
  assert_equal 'smallint', one.sql_type
174
177
  assert_equal 'integer', four.sql_type
175
178
  assert_equal 'bigint', eight.sql_type
179
+ assert_equal 'integer', eleven.sql_type
180
+ elsif current_adapter?(:MysqlAdapter)
181
+ assert_match 'int(11)', default.sql_type
182
+ assert_match 'tinyint', one.sql_type
183
+ assert_match 'int', four.sql_type
184
+ assert_match 'bigint', eight.sql_type
185
+ assert_match 'int(11)', eleven.sql_type
176
186
  elsif current_adapter?(:OracleAdapter)
177
187
  assert_equal 'NUMBER(38)', default.sql_type
178
188
  assert_equal 'NUMBER(1)', one.sql_type
@@ -227,6 +237,39 @@ if ActiveRecord::Base.connection.supports_migrations?
227
237
  end
228
238
  end
229
239
 
240
+ def test_create_table_with_timestamps_should_create_datetime_columns
241
+ table_name = :testings
242
+
243
+ Person.connection.create_table table_name do |t|
244
+ t.timestamps
245
+ end
246
+ created_columns = Person.connection.columns(table_name)
247
+
248
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
249
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
250
+
251
+ assert created_at_column.null
252
+ assert updated_at_column.null
253
+ ensure
254
+ Person.connection.drop_table table_name rescue nil
255
+ end
256
+
257
+ def test_create_table_with_timestamps_should_create_datetime_columns_with_options
258
+ table_name = :testings
259
+
260
+ Person.connection.create_table table_name do |t|
261
+ t.timestamps :null => false
262
+ end
263
+ created_columns = Person.connection.columns(table_name)
264
+
265
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
266
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
267
+
268
+ assert !created_at_column.null
269
+ assert !updated_at_column.null
270
+ ensure
271
+ Person.connection.drop_table table_name rescue nil
272
+ end
230
273
 
231
274
  # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
232
275
  # column to a table without a default value.
@@ -399,10 +442,7 @@ if ActiveRecord::Base.connection.supports_migrations?
399
442
 
400
443
  ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
401
444
  Person.reset_column_information
402
- Person.create :intelligence_quotient => 300
403
- jonnyg = Person.find(:first)
404
- assert_equal 127, jonnyg.intelligence_quotient
405
- jonnyg.destroy
445
+ assert_match /tinyint/, Person.columns_hash['intelligence_quotient'].sql_type
406
446
  ensure
407
447
  ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
408
448
  end
@@ -483,6 +523,37 @@ if ActiveRecord::Base.connection.supports_migrations?
483
523
  end
484
524
  end
485
525
 
526
+ def test_rename_column_preserves_default_value_not_null
527
+ begin
528
+ default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default
529
+ assert_equal 70000, default_before
530
+ Developer.connection.rename_column "developers", "salary", "anual_salary"
531
+ Developer.reset_column_information
532
+ assert Developer.column_names.include?("anual_salary")
533
+ default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default
534
+ assert_equal 70000, default_after
535
+ ensure
536
+ Developer.connection.rename_column "developers", "anual_salary", "salary"
537
+ Developer.reset_column_information
538
+ end
539
+ end
540
+
541
+ def test_rename_nonexistent_column
542
+ ActiveRecord::Base.connection.create_table(:hats) do |table|
543
+ table.column :hat_name, :string, :default => nil
544
+ end
545
+ exception = if current_adapter?(:PostgreSQLAdapter)
546
+ ActiveRecord::StatementInvalid
547
+ else
548
+ ActiveRecord::ActiveRecordError
549
+ end
550
+ assert_raises(exception) do
551
+ Person.connection.rename_column "hats", "nonexistent", "should_fail"
552
+ end
553
+ ensure
554
+ ActiveRecord::Base.connection.drop_table(:hats)
555
+ end
556
+
486
557
  def test_rename_column_with_sql_reserved_word
487
558
  begin
488
559
  assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
@@ -662,6 +733,55 @@ if ActiveRecord::Base.connection.supports_migrations?
662
733
  Person.connection.drop_table :testings rescue nil
663
734
  end
664
735
 
736
+ def test_keeping_default_and_notnull_constaint_on_change
737
+ Person.connection.create_table :testings do |t|
738
+ t.column :title, :string
739
+ end
740
+ person_klass = Class.new(Person)
741
+ person_klass.set_table_name 'testings'
742
+
743
+ person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
744
+ person_klass.reset_column_information
745
+ assert_equal 99, person_klass.columns_hash["wealth"].default
746
+ assert_equal false, person_klass.columns_hash["wealth"].null
747
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
748
+
749
+ # change column default to see that column doesn't lose its not null definition
750
+ person_klass.connection.change_column_default "testings", "wealth", 100
751
+ person_klass.reset_column_information
752
+ assert_equal 100, person_klass.columns_hash["wealth"].default
753
+ assert_equal false, person_klass.columns_hash["wealth"].null
754
+
755
+ # rename column to see that column doesn't lose its not null and/or default definition
756
+ person_klass.connection.rename_column "testings", "wealth", "money"
757
+ person_klass.reset_column_information
758
+ assert_nil person_klass.columns_hash["wealth"]
759
+ assert_equal 100, person_klass.columns_hash["money"].default
760
+ assert_equal false, person_klass.columns_hash["money"].null
761
+
762
+ # change column
763
+ person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
764
+ person_klass.reset_column_information
765
+ assert_equal 1000, person_klass.columns_hash["money"].default
766
+ assert_equal false, person_klass.columns_hash["money"].null
767
+
768
+ # change column, make it nullable and clear default
769
+ person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
770
+ person_klass.reset_column_information
771
+ assert_nil person_klass.columns_hash["money"].default
772
+ assert_equal true, person_klass.columns_hash["money"].null
773
+
774
+ # change_column_null, make it not nullable and set null values to a default value
775
+ person_klass.connection.execute('UPDATE testings SET money = NULL')
776
+ person_klass.connection.change_column_null "testings", "money", false, 2000
777
+ person_klass.reset_column_information
778
+ assert_nil person_klass.columns_hash["money"].default
779
+ assert_equal false, person_klass.columns_hash["money"].null
780
+ assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
781
+ ensure
782
+ Person.connection.drop_table :testings rescue nil
783
+ end
784
+
665
785
  def test_change_column_default_to_null
666
786
  Person.connection.change_column_default "people", "first_name", nil
667
787
  Person.reset_column_information
@@ -799,6 +919,21 @@ if ActiveRecord::Base.connection.supports_migrations?
799
919
  assert !Reminder.table_exists?
800
920
  end
801
921
 
922
+ def test_migrator_double_up
923
+ assert_equal(0, ActiveRecord::Migrator.current_version)
924
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
925
+ assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) }
926
+ assert_equal(1, ActiveRecord::Migrator.current_version)
927
+ end
928
+
929
+ def test_migrator_double_down
930
+ assert_equal(0, ActiveRecord::Migrator.current_version)
931
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
932
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1)
933
+ assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) }
934
+ assert_equal(0, ActiveRecord::Migrator.current_version)
935
+ end
936
+
802
937
  def test_finds_migrations
803
938
  migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
804
939
  [['1', 'people_have_last_names'],
@@ -888,16 +1023,6 @@ if ActiveRecord::Base.connection.supports_migrations?
888
1023
  ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
889
1024
  assert_equal(0, ActiveRecord::Migrator.current_version)
890
1025
  end
891
-
892
- def test_migrator_run
893
- assert_equal(0, ActiveRecord::Migrator.current_version)
894
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
895
- assert_equal(0, ActiveRecord::Migrator.current_version)
896
-
897
- assert_equal(0, ActiveRecord::Migrator.current_version)
898
- ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
899
- assert_equal(0, ActiveRecord::Migrator.current_version)
900
- end
901
1026
 
902
1027
  def test_schema_migrations_table_name
903
1028
  ActiveRecord::Base.table_name_prefix = "prefix_"
@@ -1077,8 +1202,8 @@ if ActiveRecord::Base.connection.supports_migrations?
1077
1202
 
1078
1203
  def test_timestamps_creates_updated_at_and_created_at
1079
1204
  with_new_table do |t|
1080
- t.expects(:column).with(:created_at, :datetime)
1081
- t.expects(:column).with(:updated_at, :datetime)
1205
+ t.expects(:column).with(:created_at, :datetime, kind_of(Hash))
1206
+ t.expects(:column).with(:updated_at, :datetime, kind_of(Hash))
1082
1207
  t.timestamps
1083
1208
  end
1084
1209
  end
@@ -1206,10 +1331,10 @@ if ActiveRecord::Base.connection.supports_migrations?
1206
1331
  end
1207
1332
 
1208
1333
  def integer_column
1209
- if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
1210
- "integer"
1211
- else
1334
+ if current_adapter?(:MysqlAdapter)
1212
1335
  'int(11)'
1336
+ else
1337
+ 'integer'
1213
1338
  end
1214
1339
  end
1215
1340