activerecord 2.3.5 → 2.3.6

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 (90) hide show
  1. data/CHANGELOG +33 -0
  2. data/Rakefile +1 -1
  3. data/examples/performance.sql +85 -0
  4. data/lib/active_record.rb +1 -2
  5. data/lib/active_record/association_preload.rb +9 -2
  6. data/lib/active_record/associations.rb +48 -38
  7. data/lib/active_record/associations/association_collection.rb +15 -11
  8. data/lib/active_record/associations/association_proxy.rb +16 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +11 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -10
  11. data/lib/active_record/associations/has_many_association.rb +5 -0
  12. data/lib/active_record/associations/has_many_through_association.rb +5 -5
  13. data/lib/active_record/associations/has_one_association.rb +10 -1
  14. data/lib/active_record/attribute_methods.rb +5 -1
  15. data/lib/active_record/autosave_association.rb +66 -35
  16. data/lib/active_record/base.rb +77 -36
  17. data/lib/active_record/batches.rb +13 -9
  18. data/lib/active_record/calculations.rb +6 -3
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +64 -10
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -0
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +31 -1
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -66
  28. data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
  29. data/lib/active_record/dirty.rb +2 -2
  30. data/lib/active_record/fixtures.rb +1 -0
  31. data/lib/active_record/locking/optimistic.rb +34 -1
  32. data/lib/active_record/migration.rb +5 -0
  33. data/lib/active_record/nested_attributes.rb +64 -52
  34. data/lib/active_record/reflection.rb +66 -1
  35. data/lib/active_record/schema.rb +5 -1
  36. data/lib/active_record/schema_dumper.rb +3 -0
  37. data/lib/active_record/serializers/json_serializer.rb +1 -1
  38. data/lib/active_record/validations.rb +13 -1
  39. data/lib/active_record/version.rb +1 -1
  40. data/test/cases/active_schema_test_mysql.rb +22 -0
  41. data/test/cases/associations/belongs_to_associations_test.rb +13 -0
  42. data/test/cases/associations/eager_load_nested_include_test.rb +8 -7
  43. data/test/cases/associations/eager_test.rb +7 -1
  44. data/test/cases/associations/has_many_associations_test.rb +26 -0
  45. data/test/cases/associations/inverse_associations_test.rb +566 -0
  46. data/test/cases/associations_test.rb +10 -0
  47. data/test/cases/autosave_association_test.rb +86 -10
  48. data/test/cases/base_test.rb +29 -0
  49. data/test/cases/batches_test.rb +20 -0
  50. data/test/cases/calculations_test.rb +2 -3
  51. data/test/cases/encoding_test.rb +6 -0
  52. data/test/cases/finder_test.rb +19 -3
  53. data/test/cases/fixtures_test.rb +5 -0
  54. data/test/cases/json_serialization_test.rb +14 -0
  55. data/test/cases/locking_test.rb +48 -3
  56. data/test/cases/migration_test.rb +115 -0
  57. data/test/cases/modules_test.rb +28 -0
  58. data/test/cases/named_scope_test.rb +1 -1
  59. data/test/cases/nested_attributes_test.rb +239 -7
  60. data/test/cases/query_cache_test.rb +7 -1
  61. data/test/cases/reflection_test.rb +47 -7
  62. data/test/cases/schema_test_postgresql.rb +2 -2
  63. data/test/cases/validations_i18n_test.rb +6 -36
  64. data/test/cases/validations_test.rb +33 -1
  65. data/test/cases/yaml_serialization_test.rb +11 -0
  66. data/test/fixtures/faces.yml +11 -0
  67. data/test/fixtures/fixture_database.sqlite +0 -0
  68. data/test/fixtures/fixture_database.sqlite3 +0 -0
  69. data/test/fixtures/fixture_database_2.sqlite +0 -0
  70. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  71. data/test/fixtures/interests.yml +33 -0
  72. data/test/fixtures/men.yml +5 -0
  73. data/test/fixtures/zines.yml +5 -0
  74. data/test/models/author.rb +3 -0
  75. data/test/models/bird.rb +6 -0
  76. data/test/models/company_in_module.rb +17 -0
  77. data/test/models/event_author.rb +5 -0
  78. data/test/models/face.rb +7 -0
  79. data/test/models/interest.rb +5 -0
  80. data/test/models/invoice.rb +4 -0
  81. data/test/models/line_item.rb +3 -0
  82. data/test/models/man.rb +9 -0
  83. data/test/models/parrot.rb +6 -0
  84. data/test/models/pirate.rb +10 -0
  85. data/test/models/ship.rb +10 -1
  86. data/test/models/ship_part.rb +3 -1
  87. data/test/models/zine.rb +3 -0
  88. data/test/schema/schema.rb +41 -0
  89. metadata +37 -11
  90. data/lib/active_record/i18n_interpolation_deprecation.rb +0 -26
@@ -64,6 +64,16 @@ class AssociationsTest < ActiveRecord::TestCase
64
64
  assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
65
65
  assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
66
66
  end
67
+
68
+ def test_force_reload_is_uncached
69
+ firm = Firm.create!("name" => "A New Firm, Inc")
70
+ client = Client.create!("name" => "TheClient.com", :firm => firm)
71
+ ActiveRecord::Base.cache do
72
+ firm.clients.each {}
73
+ assert_queries(0) { assert_not_nil firm.clients.each {} }
74
+ assert_queries(1) { assert_not_nil firm.clients(true).each {} }
75
+ end
76
+ end
67
77
 
68
78
  def test_storing_in_pstore
69
79
  require "tmpdir"
@@ -3,6 +3,8 @@ require 'models/bird'
3
3
  require 'models/company'
4
4
  require 'models/customer'
5
5
  require 'models/developer'
6
+ require 'models/invoice'
7
+ require 'models/line_item'
6
8
  require 'models/order'
7
9
  require 'models/parrot'
8
10
  require 'models/person'
@@ -30,11 +32,40 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
30
32
  assert base.valid_keys_for_has_and_belongs_to_many_association.include?(:autosave)
31
33
  end
32
34
 
35
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
36
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
37
+ end
38
+
39
+ def test_should_not_add_the_same_callbacks_multiple_times_for_belongs_to
40
+ assert_no_difference_when_adding_callbacks_twice_for Ship, :pirate
41
+ end
42
+
43
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_many
44
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :birds
45
+ end
46
+
47
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_and_belongs_to_many
48
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
49
+ end
50
+
33
51
  private
34
52
 
35
53
  def base
36
54
  ActiveRecord::Base
37
55
  end
56
+
57
+ def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
58
+ reflection = model.reflect_on_association(association_name)
59
+ assert_no_difference "callbacks_for_model(#{model.name}).length" do
60
+ model.send(:add_autosave_association_callbacks, reflection)
61
+ end
62
+ end
63
+
64
+ def callbacks_for_model(model)
65
+ model.instance_variables.grep(/_callbacks$/).map do |ivar|
66
+ model.instance_variable_get(ivar)
67
+ end.flatten
68
+ end
38
69
  end
39
70
 
40
71
  class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
@@ -663,23 +694,18 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
663
694
 
664
695
  define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
665
696
  2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
666
- before = @pirate.send(association_name).map { |c| c }
697
+ before = @pirate.send(association_name).map { |c| c.mark_for_destruction ; c }
667
698
 
668
- # Stub the save method of the first child to destroy and the second to raise an exception
669
- class << before.first
670
- def save(*args)
671
- super
672
- destroy
673
- end
674
- end
699
+ # Stub the destroy method of the the second child to raise an exception
675
700
  class << before.last
676
- def save(*args)
701
+ def destroy(*args)
677
702
  super
678
703
  raise 'Oh noes!'
679
704
  end
680
705
  end
681
706
 
682
707
  assert_raise(RuntimeError) { assert !@pirate.save }
708
+ assert before.first.frozen? # the first child was indeed destroyed
683
709
  assert_equal before, @pirate.reload.send(association_name)
684
710
  end
685
711
 
@@ -787,6 +813,18 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
787
813
  end
788
814
  end
789
815
 
816
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
817
+ pirate = Pirate.new(:catchphrase => 'Arr')
818
+ ship = pirate.build_ship(:name => 'The Vile Insanity')
819
+ ship.cancel_save_from_callback = true
820
+
821
+ assert_no_difference 'Pirate.count' do
822
+ assert_no_difference 'Ship.count' do
823
+ assert !pirate.save
824
+ end
825
+ end
826
+ end
827
+
790
828
  def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
791
829
  before = [@pirate.catchphrase, @pirate.ship.name]
792
830
 
@@ -865,6 +903,18 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
865
903
  end
866
904
  end
867
905
 
906
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
907
+ ship = Ship.new(:name => 'The Vile Insanity')
908
+ pirate = ship.build_pirate(:catchphrase => 'Arr')
909
+ pirate.cancel_save_from_callback = true
910
+
911
+ assert_no_difference 'Ship.count' do
912
+ assert_no_difference 'Pirate.count' do
913
+ assert !ship.save
914
+ end
915
+ end
916
+ end
917
+
868
918
  def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
869
919
  before = [@ship.pirate.catchphrase, @ship.name]
870
920
 
@@ -880,7 +930,6 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
880
930
  end
881
931
 
882
932
  assert_raise(RuntimeError) { assert !@ship.save }
883
- # TODO: Why does using reload on @ship looses the associated pirate?
884
933
  assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
885
934
  end
886
935
 
@@ -957,6 +1006,26 @@ module AutosaveAssociationOnACollectionAssociationTests
957
1006
  end
958
1007
  end
959
1008
 
1009
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update
1010
+ @pirate.catchphrase = 'Changed'
1011
+ @child_1.name = 'Changed'
1012
+ @child_1.cancel_save_from_callback = true
1013
+
1014
+ assert !@pirate.save
1015
+ assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase
1016
+ assert_equal "Posideons Killer", @child_1.reload.name
1017
+
1018
+ new_pirate = Pirate.new(:catchphrase => 'Arr')
1019
+ new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley')
1020
+ new_child.cancel_save_from_callback = true
1021
+
1022
+ assert_no_difference 'Pirate.count' do
1023
+ assert_no_difference "#{new_child.class.name}.count" do
1024
+ assert !new_pirate.save
1025
+ end
1026
+ end
1027
+ end
1028
+
960
1029
  def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
961
1030
  before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
962
1031
  new_names = ['Grace OMalley', 'Privateers Greed']
@@ -1140,3 +1209,10 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
1140
1209
  assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrots)
1141
1210
  end
1142
1211
  end
1212
+
1213
+ class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
1214
+ def test_autosave_with_touch_should_not_raise_system_stack_error
1215
+ invoice = Invoice.create
1216
+ assert_nothing_raised { invoice.line_items.create(:amount => 10) }
1217
+ end
1218
+ end
@@ -1,6 +1,7 @@
1
1
  require "cases/helper"
2
2
  require 'models/post'
3
3
  require 'models/author'
4
+ require 'models/event_author'
4
5
  require 'models/topic'
5
6
  require 'models/reply'
6
7
  require 'models/category'
@@ -627,6 +628,16 @@ class BasicsTest < ActiveRecord::TestCase
627
628
  assert_equal -2, Topic.find(2).replies_count
628
629
  end
629
630
 
631
+ def test_reset_counters
632
+ assert_equal 1, Topic.find(1).replies_count
633
+
634
+ Topic.increment_counter("replies_count", 1)
635
+ assert_equal 2, Topic.find(1).replies_count
636
+
637
+ Topic.reset_counters(1, :replies)
638
+ assert_equal 1, Topic.find(1).replies_count
639
+ end
640
+
630
641
  def test_update_counter
631
642
  category = categories(:general)
632
643
  assert_nil category.categorizations_count
@@ -1005,6 +1016,18 @@ class BasicsTest < ActiveRecord::TestCase
1005
1016
  assert_equal "changed", post.body
1006
1017
  end
1007
1018
 
1019
+ def test_multiparameter_attribute_assignment_via_association_proxy
1020
+ multiparameter_date_attribute = {
1021
+ "ends_on(1i)" => "2004", "ends_on(2i)" => "6", "ends_on(3i)" => "24",
1022
+ "ends_on(4i)" => "16", "ends_on(5i)" => "24", "ends_on(6i)" => "00"
1023
+ }
1024
+
1025
+ author = Author.create(:name => "dhh")
1026
+ event = author.events.create(multiparameter_date_attribute)
1027
+
1028
+ assert_equal Time.local(2004,6,24,16,24,0),event.ends_on
1029
+ end
1030
+
1008
1031
  def test_multiparameter_attributes_on_date
1009
1032
  attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
1010
1033
  topic = Topic.find(1)
@@ -1622,6 +1645,12 @@ class BasicsTest < ActiveRecord::TestCase
1622
1645
  assert_equal t1.title, t2.title
1623
1646
  end
1624
1647
 
1648
+ def test_reload_with_exclusive_scope
1649
+ dev = DeveloperCalledDavid.first
1650
+ dev.update_attributes!( :name => "NotDavid" )
1651
+ assert_equal dev, dev.reload
1652
+ end
1653
+
1625
1654
  def test_define_attr_method_with_value
1626
1655
  k = Class.new( ActiveRecord::Base )
1627
1656
  k.send(:define_attr_method, :table_name, "foo")
@@ -58,4 +58,24 @@ class EachTest < ActiveRecord::TestCase
58
58
  Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
59
59
  end
60
60
  end
61
+
62
+ def test_find_in_batches_doesnt_clog_conditions
63
+ Post.find_in_batches(:conditions => {:id => posts(:welcome).id}) do
64
+ assert_nothing_raised { Post.find(posts(:thinking).id) }
65
+ end
66
+ end
67
+
68
+ def test_each_should_raise_if_select_is_set_without_id
69
+ assert_raise(RuntimeError) do
70
+ Post.find_each(:select => :title, :batch_size => 1) { |post| post }
71
+ end
72
+ end
73
+
74
+ def test_each_should_execute_if_id_is_in_select
75
+ assert_queries(4) do
76
+ Post.find_each(:select => "id, title, type", :batch_size => 2) do |post|
77
+ assert_kind_of Post, post
78
+ end
79
+ end
80
+ end
61
81
  end
@@ -23,8 +23,7 @@ class CalculationsTest < ActiveRecord::TestCase
23
23
 
24
24
  def test_should_average_field
25
25
  value = Account.average(:credit_limit)
26
- assert_kind_of BigDecimal, value
27
- assert_equal BigDecimal.new('53.0'), value
26
+ assert_equal 53.0, value
28
27
  end
29
28
 
30
29
  def test_should_return_nil_as_average
@@ -298,7 +297,7 @@ class CalculationsTest < ActiveRecord::TestCase
298
297
  end
299
298
 
300
299
  def test_should_sum_expression
301
- assert_equal '636', Account.sum("2 * credit_limit")
300
+ assert_equal 636, Account.sum("2 * credit_limit").to_i
302
301
  end
303
302
 
304
303
  def test_count_with_from_option
@@ -0,0 +1,6 @@
1
+ require "cases/helper"
2
+
3
+ class EncodingTest < ActiveRecord::TestCase
4
+ def test_uses_default_external_encoding
5
+ end
6
+ end
@@ -518,8 +518,8 @@ class FinderTest < ActiveRecord::TestCase
518
518
  end
519
519
 
520
520
  def test_string_sanitation
521
- assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
522
- assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
521
+ assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
522
+ assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
523
523
  end
524
524
 
525
525
  def test_count
@@ -838,7 +838,7 @@ class FinderTest < ActiveRecord::TestCase
838
838
  assert c.new_record?
839
839
  end
840
840
 
841
- def test_find_or_create_from_one_attribute_should_set_not_attribute_even_when_protected
841
+ def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
842
842
  c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
843
843
  assert_equal "Fortune 1000", c.name
844
844
  assert_not_equal 1000, c.rating
@@ -846,6 +846,22 @@ class FinderTest < ActiveRecord::TestCase
846
846
  assert !c.new_record?
847
847
  end
848
848
 
849
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
850
+ c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
851
+ assert_equal "Fortune 1000", c.name
852
+ assert_equal 1000, c.rating
853
+ assert c.valid?
854
+ assert c.new_record?
855
+ end
856
+
857
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
858
+ c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
859
+ assert_equal "Fortune 1000", c.name
860
+ assert_equal 1000, c.rating
861
+ assert c.valid?
862
+ assert !c.new_record?
863
+ end
864
+
849
865
  def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
850
866
  c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
851
867
  assert_equal "Fortune 1000", c.name
@@ -254,6 +254,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase
254
254
  assert_nil @unknown
255
255
  end
256
256
 
257
+ def test_visibility_of_accessor_method
258
+ assert_equal false, respond_to?(:topics, false), "should be private method"
259
+ assert_equal true, respond_to?(:topics, true), "confirm to respond surely"
260
+ end
261
+
257
262
  def test_accessor_methods
258
263
  assert_equal "The First Topic", topics(:first).title
259
264
  assert_equal "Jamis", developers(:jamis).name
@@ -43,6 +43,20 @@ class JsonSerializationTest < ActiveRecord::TestCase
43
43
  Contact.include_root_in_json = false
44
44
  end
45
45
 
46
+ def test_should_include_root_in_json
47
+ Contact.include_root_in_json = true
48
+ json = @contact.to_json(:root => 'json_contact')
49
+
50
+ assert_match %r{^\{"json_contact":\{}, json
51
+ assert_match %r{"name":"Konata Izumi"}, json
52
+ assert_match %r{"age":16}, json
53
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
54
+ assert_match %r{"awesome":true}, json
55
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
56
+ ensure
57
+ Contact.include_root_in_json = false
58
+ end
59
+
46
60
  def test_should_encode_all_encodable_attributes
47
61
  json = @contact.to_json
48
62
 
@@ -38,6 +38,25 @@ class OptimisticLockingTest < ActiveRecord::TestCase
38
38
  assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
39
39
  end
40
40
 
41
+ # See Lighthouse ticket #1966
42
+ def test_lock_destroy
43
+ p1 = Person.find(1)
44
+ p2 = Person.find(1)
45
+ assert_equal 0, p1.lock_version
46
+ assert_equal 0, p2.lock_version
47
+
48
+ p1.first_name = 'stu'
49
+ p1.save!
50
+ assert_equal 1, p1.lock_version
51
+ assert_equal 0, p2.lock_version
52
+
53
+ assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
54
+
55
+ assert p1.destroy
56
+ assert_equal true, p1.frozen?
57
+ assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
58
+ end
59
+
41
60
  def test_lock_repeating
42
61
  p1 = Person.find(1)
43
62
  p2 = Person.find(1)
@@ -150,6 +169,32 @@ class OptimisticLockingTest < ActiveRecord::TestCase
150
169
  end
151
170
  end
152
171
  end
172
+
173
+ # See Lighthouse ticket #1966
174
+ def test_destroy_dependents
175
+ # Establish dependent relationship between People and LegacyThing
176
+ add_counter_column_to(Person, 'legacy_things_count')
177
+ LegacyThing.connection.add_column LegacyThing.table_name, 'person_id', :integer
178
+ LegacyThing.reset_column_information
179
+ LegacyThing.class_eval do
180
+ belongs_to :person, :counter_cache => true
181
+ end
182
+ Person.class_eval do
183
+ has_many :legacy_things, :dependent => :destroy
184
+ end
185
+
186
+ # Make sure that counter incrementing doesn't cause problems
187
+ p1 = Person.new(:first_name => 'fjord')
188
+ p1.save!
189
+ t = LegacyThing.new(:person => p1)
190
+ t.save!
191
+ p1.reload
192
+ assert_equal 1, p1.legacy_things_count
193
+ assert p1.destroy
194
+ assert_equal true, p1.frozen?
195
+ assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
196
+ assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
197
+ end
153
198
 
154
199
  def test_quote_table_name
155
200
  ref = references(:michael_magician)
@@ -168,11 +213,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase
168
213
 
169
214
  private
170
215
 
171
- def add_counter_column_to(model)
172
- model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
216
+ def add_counter_column_to(model, col='test_count')
217
+ model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0
173
218
  model.reset_column_information
174
219
  # OpenBase does not set a value to existing rows when adding a not null default column
175
- model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter)
220
+ model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter)
176
221
  end
177
222
 
178
223
  def remove_counter_column_from(model)
@@ -92,6 +92,14 @@ if ActiveRecord::Base.connection.supports_migrations?
92
92
  assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
93
93
  assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
94
94
  assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
95
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
96
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
97
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) }
98
+ assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
99
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) }
100
+ assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
101
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) }
102
+ assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
95
103
  end
96
104
 
97
105
  # quoting
@@ -111,6 +119,40 @@ if ActiveRecord::Base.connection.supports_migrations?
111
119
  end
112
120
  end
113
121
 
122
+ def test_add_index_length_limit
123
+ good_index_name = 'x' * Person.connection.index_name_length
124
+ too_long_index_name = good_index_name + 'x'
125
+ assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => too_long_index_name) }
126
+ assert !Person.connection.index_exists?("people", too_long_index_name, false)
127
+ assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) }
128
+ assert Person.connection.index_exists?("people", good_index_name, false)
129
+ end
130
+
131
+ def test_remove_nonexistent_index
132
+ # we do this by name, so OpenBase is a wash as noted above
133
+ unless current_adapter?(:OpenBaseAdapter)
134
+ assert_nothing_raised { Person.connection.remove_index("people", "no_such_index") }
135
+ end
136
+ end
137
+
138
+ def test_rename_index
139
+ unless current_adapter?(:OpenBaseAdapter)
140
+ # keep the names short to make Oracle and similar behave
141
+ Person.connection.add_index('people', [:first_name], :name => 'old_idx')
142
+ assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') }
143
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
144
+ assert !Person.connection.index_exists?('people', 'old_idx', false)
145
+ assert Person.connection.index_exists?('people', 'new_idx', true)
146
+ end
147
+ end
148
+
149
+ def test_double_add_index
150
+ unless current_adapter?(:OpenBaseAdapter)
151
+ Person.connection.add_index('people', [:first_name], :name => 'some_idx')
152
+ assert_nothing_raised { Person.connection.add_index('people', [:first_name], :name => 'some_idx') }
153
+ end
154
+ end
155
+
114
156
  def testing_table_with_only_foo_attribute
115
157
  Person.connection.create_table :testings, :id => false do |t|
116
158
  t.column :foo, :string
@@ -311,6 +353,13 @@ if ActiveRecord::Base.connection.supports_migrations?
311
353
  Person.connection.drop_table table_name rescue nil
312
354
  end
313
355
 
356
+ def test_create_table_without_a_block
357
+ table_name = :testings
358
+ Person.connection.create_table table_name
359
+ ensure
360
+ Person.connection.drop_table table_name rescue nil
361
+ end
362
+
314
363
  # Sybase, and SQLite3 will not allow you to add a NOT NULL
315
364
  # column to a table without a default value.
316
365
  unless current_adapter?(:SybaseAdapter, :SQLiteAdapter)
@@ -516,6 +565,53 @@ if ActiveRecord::Base.connection.supports_migrations?
516
565
  assert !Person.column_methods_hash.include?(:last_name)
517
566
  end
518
567
 
568
+ if current_adapter?(:MysqlAdapter)
569
+ def testing_table_for_positioning
570
+ Person.connection.create_table :testings, :id => false do |t|
571
+ t.column :first, :integer
572
+ t.column :second, :integer
573
+ t.column :third, :integer
574
+ end
575
+
576
+ yield Person.connection
577
+ ensure
578
+ Person.connection.drop_table :testings rescue nil
579
+ end
580
+ protected :testing_table_for_positioning
581
+
582
+ def test_column_positioning
583
+ testing_table_for_positioning do |conn|
584
+ assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
585
+ end
586
+ end
587
+
588
+ def test_add_column_with_positioning
589
+ testing_table_for_positioning do |conn|
590
+ conn.add_column :testings, :new_col, :integer
591
+ assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
592
+ end
593
+ testing_table_for_positioning do |conn|
594
+ conn.add_column :testings, :new_col, :integer, :first => true
595
+ assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
596
+ end
597
+ testing_table_for_positioning do |conn|
598
+ conn.add_column :testings, :new_col, :integer, :after => :first
599
+ assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
600
+ end
601
+ end
602
+
603
+ def test_change_column_with_positioning
604
+ testing_table_for_positioning do |conn|
605
+ conn.change_column :testings, :second, :integer, :first => true
606
+ assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
607
+ end
608
+ testing_table_for_positioning do |conn|
609
+ conn.change_column :testings, :second, :integer, :after => :third
610
+ assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
611
+ end
612
+ end
613
+ end
614
+
519
615
  def test_add_rename
520
616
  Person.delete_all
521
617
 
@@ -1023,6 +1119,25 @@ if ActiveRecord::Base.connection.supports_migrations?
1023
1119
  load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb")
1024
1120
  end
1025
1121
 
1122
+ def test_target_version_zero_should_run_only_once
1123
+ # migrate up to 1
1124
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
1125
+
1126
+ # migrate down to 0
1127
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
1128
+
1129
+ # now unload the migrations that have been defined
1130
+ PeopleHaveLastNames.unloadable
1131
+ ActiveSupport::Dependencies.remove_unloadable_constants!
1132
+
1133
+ # migrate down to 0 again
1134
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
1135
+
1136
+ assert !defined? PeopleHaveLastNames
1137
+ ensure
1138
+ load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb")
1139
+ end
1140
+
1026
1141
  def test_migrator_interleaved_migrations
1027
1142
  ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
1028
1143