activerecord 1.11.1 → 1.12.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 (102) hide show
  1. data/CHANGELOG +198 -0
  2. data/lib/active_record.rb +19 -14
  3. data/lib/active_record/acts/list.rb +8 -6
  4. data/lib/active_record/acts/tree.rb +33 -10
  5. data/lib/active_record/aggregations.rb +1 -7
  6. data/lib/active_record/associations.rb +151 -82
  7. data/lib/active_record/associations/association_collection.rb +25 -0
  8. data/lib/active_record/associations/association_proxy.rb +9 -8
  9. data/lib/active_record/associations/belongs_to_association.rb +19 -5
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
  11. data/lib/active_record/associations/has_many_association.rb +6 -14
  12. data/lib/active_record/associations/has_one_association.rb +5 -3
  13. data/lib/active_record/base.rb +344 -130
  14. data/lib/active_record/callbacks.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
  16. data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
  17. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
  18. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
  21. data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
  23. data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
  27. data/lib/active_record/fixtures.rb +47 -24
  28. data/lib/active_record/migration.rb +34 -5
  29. data/lib/active_record/observer.rb +32 -2
  30. data/lib/active_record/query_cache.rb +12 -11
  31. data/lib/active_record/schema.rb +58 -0
  32. data/lib/active_record/schema_dumper.rb +84 -0
  33. data/lib/active_record/transactions.rb +1 -3
  34. data/lib/active_record/validations.rb +40 -26
  35. data/lib/active_record/vendor/mysql.rb +6 -0
  36. data/lib/active_record/version.rb +9 -0
  37. data/rakefile +5 -16
  38. data/test/abstract_unit.rb +6 -11
  39. data/test/adapter_test.rb +58 -0
  40. data/test/ar_schema_test.rb +33 -0
  41. data/test/association_callbacks_test.rb +14 -0
  42. data/test/associations_go_eager_test.rb +56 -14
  43. data/test/associations_test.rb +245 -25
  44. data/test/base_test.rb +205 -34
  45. data/test/binary_test.rb +25 -42
  46. data/test/callbacks_test.rb +75 -0
  47. data/test/conditions_scoping_test.rb +136 -0
  48. data/test/connections/native_mysql/connection.rb +0 -4
  49. data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
  50. data/test/copy_table_sqlite.rb +64 -0
  51. data/test/deprecated_associations_test.rb +7 -6
  52. data/test/deprecated_finder_test.rb +3 -3
  53. data/test/finder_test.rb +33 -3
  54. data/test/fixtures/accounts.yml +5 -0
  55. data/test/fixtures/categories_ordered.yml +7 -0
  56. data/test/fixtures/category.rb +11 -1
  57. data/test/fixtures/comment.rb +22 -2
  58. data/test/fixtures/comments.yml +6 -0
  59. data/test/fixtures/companies.yml +15 -0
  60. data/test/fixtures/company.rb +24 -1
  61. data/test/fixtures/db_definitions/db2.drop.sql +5 -1
  62. data/test/fixtures/db_definitions/db2.sql +15 -1
  63. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  64. data/test/fixtures/db_definitions/mysql.sql +17 -2
  65. data/test/fixtures/db_definitions/oci.drop.sql +37 -5
  66. data/test/fixtures/db_definitions/oci.sql +47 -4
  67. data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
  68. data/test/fixtures/db_definitions/oci2.sql +2 -2
  69. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  70. data/test/fixtures/db_definitions/postgresql.sql +33 -4
  71. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  72. data/test/fixtures/db_definitions/sqlite.sql +16 -2
  73. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  74. data/test/fixtures/db_definitions/sqlserver.sql +16 -2
  75. data/test/fixtures/developer.rb +1 -1
  76. data/test/fixtures/flowers.jpg +0 -0
  77. data/test/fixtures/keyboard.rb +3 -0
  78. data/test/fixtures/mixins.yml +11 -1
  79. data/test/fixtures/order.rb +4 -0
  80. data/test/fixtures/post.rb +4 -0
  81. data/test/fixtures/posts.yml +7 -0
  82. data/test/fixtures/project.rb +1 -0
  83. data/test/fixtures/subject.rb +4 -0
  84. data/test/fixtures/subscriber.rb +2 -4
  85. data/test/fixtures/topics.yml +2 -2
  86. data/test/fixtures_test.rb +79 -7
  87. data/test/inheritance_test.rb +2 -2
  88. data/test/lifecycle_test.rb +14 -6
  89. data/test/migration_test.rb +164 -6
  90. data/test/mixin_test.rb +78 -2
  91. data/test/pk_test.rb +25 -1
  92. data/test/readonly_test.rb +31 -0
  93. data/test/reflection_test.rb +4 -1
  94. data/test/schema_dumper_test.rb +19 -0
  95. data/test/schema_test_postgresql.rb +3 -2
  96. data/test/synonym_test_oci.rb +17 -0
  97. data/test/threaded_connections_test.rb +2 -1
  98. data/test/transactions_test.rb +109 -10
  99. data/test/validations_test.rb +70 -42
  100. metadata +25 -5
  101. data/test/fixtures/associations.png +0 -0
  102. data/test/thread_safety_test.rb +0 -36
@@ -5,12 +5,16 @@ require 'fixtures/company'
5
5
  require 'fixtures/topic'
6
6
  require 'fixtures/reply'
7
7
  require 'fixtures/computer'
8
+ require 'fixtures/customer'
9
+ require 'fixtures/order'
10
+ require 'fixtures/post'
11
+ require 'fixtures/author'
8
12
 
9
13
  # Can't declare new classes in test case methods, so tests before that
10
14
  bad_collection_keys = false
11
15
  begin
12
16
  class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
13
- rescue ActiveRecord::ActiveRecordError
17
+ rescue ArgumentError
14
18
  bad_collection_keys = true
15
19
  end
16
20
  raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys
@@ -19,7 +23,7 @@ raise "ActiveRecord should have barked on bad collection keys" unless bad_collec
19
23
  class AssociationsTest < Test::Unit::TestCase
20
24
  fixtures :accounts, :companies, :developers, :projects, :developers_projects,
21
25
  :computers
22
-
26
+
23
27
  def test_force_reload
24
28
  firm = Firm.new("name" => "A New Firm, Inc")
25
29
  firm.save
@@ -68,6 +72,11 @@ class HasOneAssociationsTest < Test::Unit::TestCase
68
72
  assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
69
73
  end
70
74
 
75
+ def test_proxy_assignment
76
+ company = companies(:first_firm)
77
+ assert_nothing_raised { company.account = company.account }
78
+ end
79
+
71
80
  def test_triple_equality
72
81
  assert Account === companies(:first_firm).account
73
82
  end
@@ -123,10 +132,11 @@ class HasOneAssociationsTest < Test::Unit::TestCase
123
132
  end
124
133
 
125
134
  def test_dependence
135
+ num_accounts = Account.count
126
136
  firm = Firm.find(1)
127
137
  assert !firm.account.nil?
128
- firm.destroy
129
- assert_equal 1, Account.count
138
+ firm.destroy
139
+ assert_equal num_accounts - 1, Account.count
130
140
  end
131
141
 
132
142
  def test_succesful_build_association
@@ -253,11 +263,15 @@ end
253
263
  class HasManyAssociationsTest < Test::Unit::TestCase
254
264
  fixtures :accounts, :companies, :developers, :projects,
255
265
  :developers_projects, :topics
256
-
266
+
267
+ def setup
268
+ Client.destroyed_client_ids.clear
269
+ end
270
+
257
271
  def force_signal37_to_load_all_clients_of_firm
258
272
  companies(:first_firm).clients_of_firm.each {|f| }
259
273
  end
260
-
274
+
261
275
  def test_counting
262
276
  assert_equal 2, Firm.find(:first).clients.count
263
277
  end
@@ -312,7 +326,6 @@ class HasManyAssociationsTest < Test::Unit::TestCase
312
326
  else
313
327
  assert false, "belongs_to failed unless check"
314
328
  end
315
-
316
329
  end
317
330
 
318
331
  def test_find_ids
@@ -418,6 +431,7 @@ class HasManyAssociationsTest < Test::Unit::TestCase
418
431
  firm = Firm.find(1)
419
432
  assert !(firm.clients_of_firm << c = Client.new)
420
433
  assert c.new_record?
434
+ assert !firm.valid?
421
435
  assert !firm.save
422
436
  assert c.new_record?
423
437
  end
@@ -429,7 +443,7 @@ class HasManyAssociationsTest < Test::Unit::TestCase
429
443
  new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
430
444
  assert c.new_record?
431
445
  assert !c.valid?
432
- assert new_firm.valid?
446
+ assert !new_firm.valid?
433
447
  assert !new_firm.save
434
448
  assert c.new_record?
435
449
  assert new_firm.new_record?
@@ -495,21 +509,63 @@ class HasManyAssociationsTest < Test::Unit::TestCase
495
509
  force_signal37_to_load_all_clients_of_firm
496
510
  companies(:first_firm).clients_of_firm.create("name" => "Another Client")
497
511
  assert_equal 2, companies(:first_firm).clients_of_firm.size
498
- #companies(:first_firm).clients_of_firm.clear
499
512
  companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
500
513
  assert_equal 0, companies(:first_firm).clients_of_firm.size
501
514
  assert_equal 0, companies(:first_firm).clients_of_firm(true).size
502
515
  end
503
516
 
504
- def test_deleting_a_association_collection
505
- force_signal37_to_load_all_clients_of_firm
506
- companies(:first_firm).clients_of_firm.create("name" => "Another Client")
507
- assert_equal 2, companies(:first_firm).clients_of_firm.size
508
- companies(:first_firm).clients_of_firm.clear
509
- assert_equal 0, companies(:first_firm).clients_of_firm.size
510
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
517
+ def test_clearing_an_association_collection
518
+ firm = companies(:first_firm)
519
+ client_id = firm.clients_of_firm.first.id
520
+ assert_equal 1, firm.clients_of_firm.size
521
+
522
+ firm.clients_of_firm.clear
523
+
524
+ assert_equal 0, firm.clients_of_firm.size
525
+ assert_equal 0, firm.clients_of_firm(true).size
526
+ assert_equal [], Client.destroyed_client_ids[firm.id]
527
+
528
+ # Should not be destroyed since the association is not dependent.
529
+ assert_nothing_raised do
530
+ assert Client.find(client_id).firm.nil?
531
+ end
532
+ end
533
+
534
+ def test_clearing_a_dependent_association_collection
535
+ firm = companies(:first_firm)
536
+ client_id = firm.dependent_clients_of_firm.first.id
537
+ assert_equal 1, firm.dependent_clients_of_firm.size
538
+
539
+ # :dependent means destroy is called on each client
540
+ firm.dependent_clients_of_firm.clear
541
+
542
+ assert_equal 0, firm.dependent_clients_of_firm.size
543
+ assert_equal 0, firm.dependent_clients_of_firm(true).size
544
+ assert_equal [client_id], Client.destroyed_client_ids[firm.id]
545
+
546
+ # Should be destroyed since the association is dependent.
547
+ assert Client.find_by_id(client_id).nil?
511
548
  end
512
549
 
550
+ def test_clearing_an_exclusively_dependent_association_collection
551
+ firm = companies(:first_firm)
552
+ client_id = firm.exclusively_dependent_clients_of_firm.first.id
553
+ assert_equal 1, firm.exclusively_dependent_clients_of_firm.size
554
+
555
+ assert_equal [], Client.destroyed_client_ids[firm.id]
556
+
557
+ # :exclusively_dependent means each client is deleted directly from
558
+ # the database without looping through them calling destroy.
559
+ firm.exclusively_dependent_clients_of_firm.clear
560
+
561
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
562
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
563
+ assert_equal [3], Client.destroyed_client_ids[firm.id]
564
+
565
+ # Should be destroyed since the association is exclusively dependent.
566
+ assert Client.find_by_id(client_id).nil?
567
+ end
568
+
513
569
  def test_deleting_a_item_which_is_not_in_the_collection
514
570
  force_signal37_to_load_all_clients_of_firm
515
571
  summit = Client.find_first("name = 'Summit'")
@@ -579,9 +635,26 @@ class HasManyAssociationsTest < Test::Unit::TestCase
579
635
  end
580
636
 
581
637
  def test_dependence_on_account
582
- assert_equal 2, Account.count
638
+ num_accounts = Account.count
583
639
  companies(:first_firm).destroy
584
- assert_equal 1, Account.count
640
+ assert_equal num_accounts - 1, Account.count
641
+ end
642
+
643
+
644
+ def test_depends_and_nullify
645
+ num_accounts = Account.count
646
+ num_companies = Company.count
647
+
648
+ core = companies(:rails_core)
649
+ assert_equal accounts(:rails_core_account), core.account
650
+ assert_equal [companies(:leetsoft), companies(:jadedpixel)], core.companies
651
+ core.destroy
652
+ assert_nil accounts(:rails_core_account).reload.firm_id
653
+ assert_nil companies(:leetsoft).reload.client_of
654
+ assert_nil companies(:jadedpixel).reload.client_of
655
+
656
+
657
+ assert_equal num_accounts, Account.count
585
658
  end
586
659
 
587
660
  def test_included_in_collection
@@ -603,7 +676,8 @@ class HasManyAssociationsTest < Test::Unit::TestCase
603
676
  assert firm.save, "Could not save firm"
604
677
  firm.reload
605
678
  assert_equal 1, firm.clients.length
606
- end
679
+ end
680
+
607
681
 
608
682
  def test_replace_with_new
609
683
  firm = Firm.find(:first)
@@ -636,7 +710,7 @@ end
636
710
 
637
711
  class BelongsToAssociationsTest < Test::Unit::TestCase
638
712
  fixtures :accounts, :companies, :developers, :projects, :topics,
639
- :developers_projects
713
+ :developers_projects, :computers, :authors, :posts
640
714
 
641
715
  def test_belongs_to
642
716
  Client.find(3).firm.name
@@ -644,6 +718,11 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
644
718
  assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
645
719
  end
646
720
 
721
+ def test_proxy_assignment
722
+ account = Account.find(1)
723
+ assert_nothing_raised { account.firm = account.firm }
724
+ end
725
+
647
726
  def test_type_mismatch
648
727
  assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
649
728
  assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
@@ -660,6 +739,9 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
660
739
  citibank = Account.create("credit_limit" => 10)
661
740
  apple = citibank.create_firm("name" => "Apple")
662
741
  assert_equal apple, citibank.firm
742
+ citibank.save
743
+ citibank.reload
744
+ assert_equal apple, citibank.firm
663
745
  end
664
746
 
665
747
  def test_building_the_belonging_object
@@ -750,7 +832,7 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
750
832
  end
751
833
 
752
834
  def test_field_name_same_as_foreign_key
753
- computer = Computer.find 1
835
+ computer = Computer.find(1)
754
836
  assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
755
837
  end
756
838
 
@@ -770,6 +852,111 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
770
852
  apple.clients.to_s
771
853
  assert_equal 1, apple.clients.size, "Should not use the cached number, but go to the database"
772
854
  end
855
+
856
+ def test_store_two_association_with_one_save
857
+ num_orders = Order.count
858
+ num_customers = Customer.count
859
+ order = Order.new
860
+
861
+ customer1 = order.billing = Customer.new
862
+ customer2 = order.shipping = Customer.new
863
+ assert order.save
864
+ assert_equal customer1, order.billing
865
+ assert_equal customer2, order.shipping
866
+
867
+ order.reload
868
+
869
+ assert_equal customer1, order.billing
870
+ assert_equal customer2, order.shipping
871
+
872
+ assert_equal num_orders +1, Order.count
873
+ assert_equal num_customers +2, Customer.count
874
+ end
875
+
876
+
877
+ def test_store_association_in_two_relations_with_one_save
878
+ num_orders = Order.count
879
+ num_customers = Customer.count
880
+ order = Order.new
881
+
882
+ customer = order.billing = order.shipping = Customer.new
883
+ assert order.save
884
+ assert_equal customer, order.billing
885
+ assert_equal customer, order.shipping
886
+
887
+ order.reload
888
+
889
+ assert_equal customer, order.billing
890
+ assert_equal customer, order.shipping
891
+
892
+ assert_equal num_orders +1, Order.count
893
+ assert_equal num_customers +1, Customer.count
894
+ end
895
+
896
+ def test_store_association_in_two_relations_with_one_save_in_existing_object
897
+ num_orders = Order.count
898
+ num_customers = Customer.count
899
+ order = Order.create
900
+
901
+ customer = order.billing = order.shipping = Customer.new
902
+ assert order.save
903
+ assert_equal customer, order.billing
904
+ assert_equal customer, order.shipping
905
+
906
+ order.reload
907
+
908
+ assert_equal customer, order.billing
909
+ assert_equal customer, order.shipping
910
+
911
+ assert_equal num_orders +1, Order.count
912
+ assert_equal num_customers +1, Customer.count
913
+ end
914
+
915
+ def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
916
+ num_orders = Order.count
917
+ num_customers = Customer.count
918
+ order = Order.create
919
+
920
+ customer = order.billing = order.shipping = Customer.new
921
+ assert order.save
922
+ assert_equal customer, order.billing
923
+ assert_equal customer, order.shipping
924
+
925
+ order.reload
926
+
927
+ customer = order.billing = order.shipping = Customer.new
928
+
929
+ assert order.save
930
+ order.reload
931
+
932
+ assert_equal customer, order.billing
933
+ assert_equal customer, order.shipping
934
+
935
+ assert_equal num_orders +1, Order.count
936
+ assert_equal num_customers +2, Customer.count
937
+ end
938
+
939
+
940
+ def test_association_assignment_sticks
941
+ post = Post.find(:first)
942
+
943
+ author1, author2 = Author.find(:all, :limit => 2)
944
+ assert_not_nil author1
945
+ assert_not_nil author2
946
+
947
+ # make sure the association is loaded
948
+ post.author
949
+
950
+ # set the association by id, directly
951
+ post.author_id = author2.id
952
+
953
+ # save and reload
954
+ post.save!
955
+ post.reload
956
+
957
+ # the author id of the post should be the id we set
958
+ assert_equal post.author_id, author2.id
959
+ end
773
960
 
774
961
  end
775
962
 
@@ -883,6 +1070,26 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
883
1070
  assert_equal 2, aredridel.projects(true).size
884
1071
  end
885
1072
 
1073
+ def test_adding_uses_default_values_on_join_table
1074
+ ac = projects(:action_controller)
1075
+ assert !developers(:jamis).projects.include?(ac)
1076
+ developers(:jamis).projects << ac
1077
+
1078
+ assert developers(:jamis, :reload).projects.include?(ac)
1079
+ project = developers(:jamis).projects.detect { |p| p == ac }
1080
+ assert_equal 1, project.access_level.to_i
1081
+ end
1082
+
1083
+ def test_adding_uses_explicit_values_on_join_table
1084
+ ac = projects(:action_controller)
1085
+ assert !developers(:jamis).projects.include?(ac)
1086
+ developers(:jamis).projects.push_with_attributes(ac, :access_level => 3)
1087
+
1088
+ assert developers(:jamis, :reload).projects.include?(ac)
1089
+ project = developers(:jamis).projects.detect { |p| p == ac }
1090
+ assert_equal 3, project.access_level.to_i
1091
+ end
1092
+
886
1093
  def test_habtm_adding_before_save
887
1094
  no_of_devels = Developer.count
888
1095
  no_of_projects = Project.count
@@ -920,7 +1127,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
920
1127
  # SQL Server doesn't have a separate column type just for dates,
921
1128
  # so the time is in the string and incorrectly formatted
922
1129
  if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
923
- kenReloaded.projects.each { |prj| assert_equal(sqlnow, prj.joined_on.to_s) }
1130
+ kenReloaded.projects.each { |prj| assert_equal(sqlnow, prj.joined_on.strftime("%Y/%m/%d 00:00:00")) }
924
1131
  else
925
1132
  kenReloaded.projects.each { |prj| assert_equal(now.to_s, prj.joined_on.to_s) }
926
1133
  end
@@ -1014,7 +1221,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
1014
1221
  # SQL Server doesn't have a separate column type just for dates,
1015
1222
  # so the time is in the string and incorrectly formatted
1016
1223
  if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
1017
- assert_equal Time.mktime(2004, 10, 10).strftime("%Y/%m/%d 00:00:00"), Developer.find(1).projects.first.joined_on.to_s
1224
+ assert_equal Time.mktime(2004, 10, 10).strftime("%Y/%m/%d 00:00:00"), Time.parse(Developer.find(1).projects.first.joined_on).strftime("%Y/%m/%d 00:00:00")
1018
1225
  else
1019
1226
  assert_equal Date.new(2004, 10, 10).to_s, Developer.find(1).projects.first.joined_on.to_s
1020
1227
  end
@@ -1035,8 +1242,8 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
1035
1242
  # SQL Server doesn't have a separate column type just for dates,
1036
1243
  # so the time is in the string and incorrectly formatted
1037
1244
  if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
1038
- assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
1039
- assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
1245
+ assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), Time.parse(jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on).strftime("%Y/%m/%d 00:00:00")
1246
+ assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), Time.parse(developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on).strftime("%Y/%m/%d 00:00:00")
1040
1247
  else
1041
1248
  assert_equal Date.today.to_s, jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
1042
1249
  assert_equal Date.today.to_s, developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
@@ -1064,6 +1271,19 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
1064
1271
  assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find"
1065
1272
  end
1066
1273
 
1274
+ def test_find_in_association_with_custom_finder_sql
1275
+ assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find"
1276
+
1277
+ active_record = projects(:active_record)
1278
+ active_record.developers_with_finder_sql.reload
1279
+ assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
1280
+ end
1281
+
1282
+ def test_find_in_association_with_custom_finder_sql_and_string_id
1283
+ assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
1284
+ end
1285
+
1286
+
1067
1287
  def test_new_with_values_in_collection
1068
1288
  jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis')
1069
1289
  david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
@@ -1,5 +1,3 @@
1
- require 'abstract_unit'
2
- require 'fixtures/topic'
3
1
  require 'fixtures/reply'
4
2
  require 'fixtures/company'
5
3
  require 'fixtures/developer'
@@ -7,6 +5,8 @@ require 'fixtures/project'
7
5
  require 'fixtures/default'
8
6
  require 'fixtures/auto_id'
9
7
  require 'fixtures/column_name'
8
+ require 'fixtures/subscriber'
9
+ require 'fixtures/keyboard'
10
10
 
11
11
  class Category < ActiveRecord::Base; end
12
12
  class Smarts < ActiveRecord::Base; end
@@ -50,8 +50,8 @@ class BasicsTest < Test::Unit::TestCase
50
50
  end
51
51
 
52
52
  def test_integers_as_nil
53
- Topic.update(1, "approved" => "")
54
- assert_nil Topic.find(1).approved
53
+ test = AutoId.create('value' => '')
54
+ assert_nil AutoId.find(test.id).value
55
55
  end
56
56
 
57
57
  def test_set_attributes_with_block
@@ -180,10 +180,33 @@ class BasicsTest < Test::Unit::TestCase
180
180
  assert_equal "Still another topic: part 2", topic.title
181
181
  end
182
182
 
183
+ def test_read_attribute
184
+ topic = Topic.new
185
+ topic.title = "Don't change the topic"
186
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
187
+ assert_equal "Don't change the topic", topic["title"]
188
+
189
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
190
+ assert_equal "Don't change the topic", topic[:title]
191
+ end
192
+
183
193
  def test_read_attribute_when_false
184
194
  topic = topics(:first)
185
195
  topic.approved = false
186
- assert_equal 0, topic.approved, "approved should be 0"
196
+ assert !topic.approved?, "approved should be false"
197
+ end
198
+
199
+ def test_reader_generation
200
+ Topic.find(:first).title
201
+ Firm.find(:first).name
202
+ Client.find(:first).name
203
+ if ActiveRecord::Base.generate_read_methods
204
+ assert_readers(Topic, %w(type replies_count))
205
+ assert_readers(Firm, %w(type))
206
+ assert_readers(Client, %w(type))
207
+ else
208
+ [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
209
+ end
187
210
  end
188
211
 
189
212
  def test_preserving_date_objects
@@ -223,6 +246,12 @@ class BasicsTest < Test::Unit::TestCase
223
246
  assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
224
247
  end
225
248
 
249
+ def test_destroy_returns_self
250
+ topic = Topic.new("title" => "Yet Another Title")
251
+ assert topic.save
252
+ assert_equal topic, topic.destroy, "destroy did not return destroyed object"
253
+ end
254
+
226
255
  def test_record_not_found_exception
227
256
  assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
228
257
  end
@@ -267,31 +296,42 @@ class BasicsTest < Test::Unit::TestCase
267
296
  assert_equal "master_credit_cards", MasterCreditCard.table_name
268
297
 
269
298
  ActiveRecord::Base.pluralize_table_names = false
299
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
270
300
  assert_equal "category", Category.table_name
271
301
  assert_equal "smarts", Smarts.table_name
272
302
  assert_equal "credit_card", CreditCard.table_name
273
303
  assert_equal "master_credit_card", MasterCreditCard.table_name
274
304
  ActiveRecord::Base.pluralize_table_names = true
305
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
275
306
 
276
307
  ActiveRecord::Base.table_name_prefix = "test_"
308
+ Category.reset_table_name
277
309
  assert_equal "test_categories", Category.table_name
278
310
  ActiveRecord::Base.table_name_suffix = "_test"
311
+ Category.reset_table_name
279
312
  assert_equal "test_categories_test", Category.table_name
280
313
  ActiveRecord::Base.table_name_prefix = ""
314
+ Category.reset_table_name
281
315
  assert_equal "categories_test", Category.table_name
282
316
  ActiveRecord::Base.table_name_suffix = ""
317
+ Category.reset_table_name
283
318
  assert_equal "categories", Category.table_name
284
319
 
285
320
  ActiveRecord::Base.pluralize_table_names = false
286
321
  ActiveRecord::Base.table_name_prefix = "test_"
322
+ Category.reset_table_name
287
323
  assert_equal "test_category", Category.table_name
288
324
  ActiveRecord::Base.table_name_suffix = "_test"
325
+ Category.reset_table_name
289
326
  assert_equal "test_category_test", Category.table_name
290
327
  ActiveRecord::Base.table_name_prefix = ""
328
+ Category.reset_table_name
291
329
  assert_equal "category_test", Category.table_name
292
330
  ActiveRecord::Base.table_name_suffix = ""
331
+ Category.reset_table_name
293
332
  assert_equal "category", Category.table_name
294
333
  ActiveRecord::Base.pluralize_table_names = true
334
+ [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
295
335
  end
296
336
 
297
337
  def test_destroy_all
@@ -347,7 +387,7 @@ class BasicsTest < Test::Unit::TestCase
347
387
  end
348
388
 
349
389
  def test_update_many
350
- topic_data = { "1" => { "content" => "1 updated" }, "2" => { "content" => "2 updated" } }
390
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
351
391
  updated = Topic.update(topic_data.keys, topic_data.values)
352
392
 
353
393
  assert_equal 2, updated.size
@@ -364,7 +404,7 @@ class BasicsTest < Test::Unit::TestCase
364
404
  end
365
405
 
366
406
  def test_update_by_condition
367
- Topic.update_all "content = 'bulk updated!'", "approved = 1"
407
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
368
408
  assert_equal "Have a nice day", Topic.find(1).content
369
409
  assert_equal "bulk updated!", Topic.find(2).content
370
410
  end
@@ -410,7 +450,7 @@ class BasicsTest < Test::Unit::TestCase
410
450
 
411
451
  def test_default_values
412
452
  topic = Topic.new
413
- assert_equal 1, topic.approved
453
+ assert topic.approved?
414
454
  assert_nil topic.written_on
415
455
  assert_nil topic.bonus_time
416
456
  assert_nil topic.last_read
@@ -418,22 +458,20 @@ class BasicsTest < Test::Unit::TestCase
418
458
  topic.save
419
459
 
420
460
  topic = Topic.find(topic.id)
421
- assert_equal 1, topic.approved
461
+ assert topic.approved?
422
462
  assert_nil topic.last_read
423
463
  end
424
464
 
425
- def test_utc_as_time_zone
426
- # Oracle does not have a TIME datatype.
427
- if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter
428
- return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter)
465
+ # Oracle and SQLServer do not have a TIME datatype.
466
+ unless 'OCI' == ActiveRecord::Base.connection.adapter_name or ActiveRecord::ConnectionAdapters.const_defined?(:SQLServerAdapter)
467
+ def test_utc_as_time_zone
468
+ Topic.default_timezone = :utc
469
+ attributes = { "bonus_time" => "5:42:00AM" }
470
+ topic = Topic.find(1)
471
+ topic.attributes = attributes
472
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
473
+ Topic.default_timezone = :local
429
474
  end
430
-
431
- Topic.default_timezone = :utc
432
- attributes = { "bonus_time" => "5:42:00AM" }
433
- topic = Topic.find(1)
434
- topic.attributes = attributes
435
- assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
436
- Topic.default_timezone = :local
437
475
  end
438
476
 
439
477
  def test_default_values_on_empty_strings
@@ -452,6 +490,10 @@ class BasicsTest < Test::Unit::TestCase
452
490
  assert_equal Topic.find(1), Topic.find(2).parent
453
491
  end
454
492
 
493
+ def test_equality_of_new_records
494
+ assert_not_equal Topic.new, Topic.new
495
+ end
496
+
455
497
  def test_hashing
456
498
  assert_equal [ Topic.find(1) ], [ Topic.find(2).parent ] & [ Topic.find(1) ]
457
499
  end
@@ -484,6 +526,22 @@ class BasicsTest < Test::Unit::TestCase
484
526
  firm.attributes = { "name" => "Next Angle", "rating" => 5 }
485
527
  assert_equal 1, firm.rating
486
528
  end
529
+
530
+ def test_customized_primary_key_remains_protected
531
+ subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
532
+ assert_nil subscriber.id
533
+
534
+ keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
535
+ assert_nil keyboard.id
536
+ end
537
+
538
+ def test_customized_primary_key_remains_protected_when_refered_to_as_id
539
+ subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
540
+ assert_nil subscriber.id
541
+
542
+ keyboard = Keyboard.new(:id => 9, :name => 'nice try')
543
+ assert_nil keyboard.id
544
+ end
487
545
 
488
546
  def test_mass_assignment_protection_on_defaults
489
547
  firm = Firm.new
@@ -493,15 +551,15 @@ class BasicsTest < Test::Unit::TestCase
493
551
  end
494
552
 
495
553
  def test_mass_assignment_accessible
496
- reply = Reply.new("title" => "hello", "content" => "world", "approved" => 0)
554
+ reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
497
555
  reply.save
556
+
557
+ assert reply.approved?
498
558
 
499
- assert_equal 1, reply.approved
500
-
501
- reply.approved = 0
559
+ reply.approved = false
502
560
  reply.save
503
561
 
504
- assert_equal 0, reply.approved
562
+ assert !reply.approved?
505
563
  end
506
564
 
507
565
  def test_mass_assignment_protection_inheritance
@@ -571,7 +629,7 @@ class BasicsTest < Test::Unit::TestCase
571
629
 
572
630
  def test_multiparameter_mass_assignment_protector
573
631
  task = Task.new
574
- time = Time.mktime(0)
632
+ time = Time.mktime(2000, 1, 1, 1)
575
633
  task.starting = time
576
634
  attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
577
635
  task.attributes = attributes
@@ -583,6 +641,10 @@ class BasicsTest < Test::Unit::TestCase
583
641
  if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter
584
642
  return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter)
585
643
  end
644
+ # Sqlserver doesn't either .
645
+ if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter
646
+ return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
647
+ end
586
648
 
587
649
  attributes = {
588
650
  "bonus_time" => "5:42:00AM"
@@ -662,14 +724,21 @@ class BasicsTest < Test::Unit::TestCase
662
724
  assert_equal 2147483647, company.rating
663
725
  end
664
726
 
665
- def test_default
666
- if Default.connection.class.name == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
727
+ # TODO: extend defaults tests to other databases!
728
+ if 'PostgreSQL' == ActiveRecord::Base.connection.adapter_name
729
+ def test_default
667
730
  default = Default.new
668
731
 
669
- # dates / timestamps
732
+ # CURRENT_TIMESTAMP and NOW() timestamps
670
733
  time_format = "%m/%d/%Y %H:%M"
671
- assert_equal Time.now.strftime(time_format), default.modified_time.strftime(time_format)
672
- assert_equal Date.today, default.modified_date
734
+ now = Time.now.strftime(time_format)
735
+ assert_equal now, default.modified_time.strftime(time_format)
736
+ assert_equal now, default.modified_time_function.strftime(time_format)
737
+
738
+ # CURRENT_DATE and NOW() dates
739
+ today = Date.today
740
+ assert_equal today, default.modified_date
741
+ assert_equal today, default.modified_date_function
673
742
 
674
743
  # fixed dates / times
675
744
  assert_equal Date.new(2004, 1, 1), default.fixed_date
@@ -680,6 +749,68 @@ class BasicsTest < Test::Unit::TestCase
680
749
  assert_equal 'a varchar field', default.char2
681
750
  assert_equal 'a text field', default.char3
682
751
  end
752
+
753
+ class Geometric < ActiveRecord::Base; end
754
+ def test_geometric_content
755
+
756
+ # accepted format notes:
757
+ # ()'s aren't required
758
+ # values can be a mix of float or integer
759
+
760
+ g = Geometric.new(
761
+ :a_point => '(5.0, 6.1)',
762
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
763
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
764
+ :a_box => '2.0, 3, 5.5, 7.0',
765
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
766
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
767
+ :a_circle => '<(5.3, 10.4), 2>'
768
+ )
769
+
770
+ assert g.save
771
+
772
+ # Reload and check that we have all the geometric attributes.
773
+ h = Geometric.find(g.id)
774
+
775
+ assert_equal '(5,6.1)', h.a_point
776
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
777
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
778
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
779
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
780
+ assert_equal '<(5.3,10.4),2>', h.a_circle
781
+
782
+ # use a geometric function to test for an open path
783
+ objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
784
+ assert_equal objs[0].isopen, 't'
785
+
786
+ # test alternate formats when defining the geometric types
787
+
788
+ g = Geometric.new(
789
+ :a_point => '5.0, 6.1',
790
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
791
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
792
+ :a_box => '(2.0, 3), (5.5, 7.0)',
793
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
794
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
795
+ :a_circle => '((5.3, 10.4), 2)'
796
+ )
797
+
798
+ assert g.save
799
+
800
+ # Reload and check that we have all the geometric attributes.
801
+ h = Geometric.find(g.id)
802
+
803
+ assert_equal '(5,6.1)', h.a_point
804
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
805
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
806
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
807
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
808
+ assert_equal '<(5.3,10.4),2>', h.a_circle
809
+
810
+ # use a geometric function to test for an closed path
811
+ objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
812
+ assert_equal objs[0].isclosed, 't'
813
+ end
683
814
  end
684
815
 
685
816
  def test_auto_id
@@ -734,9 +865,9 @@ class BasicsTest < Test::Unit::TestCase
734
865
  end
735
866
 
736
867
  def test_quote
737
- content = "\\ \001 ' \n \\n \""
738
- topic = Topic.create('content' => content)
739
- assert_equal content, Topic.find(topic.id).content
868
+ author_name = "\\ \001 ' \n \\n \""
869
+ topic = Topic.create('author_name' => author_name)
870
+ assert_equal author_name, Topic.find(topic.id).author_name
740
871
  end
741
872
 
742
873
  def test_class_level_destroy
@@ -787,6 +918,11 @@ class BasicsTest < Test::Unit::TestCase
787
918
  assert !topics(:first).approved?
788
919
  topics(:first).toggle!(:approved)
789
920
  assert topics(:first).approved?
921
+ topic = topics(:first)
922
+ topic.toggle(:approved)
923
+ assert !topic.approved?
924
+ topic.reload
925
+ assert topic.approved?
790
926
  end
791
927
 
792
928
  def test_reload
@@ -883,4 +1019,39 @@ class BasicsTest < Test::Unit::TestCase
883
1019
 
884
1020
  assert_equal firm.clients.collect{ |x| x.name }.sort, clients.collect{ |x| x.name }.sort
885
1021
  end
1022
+
1023
+ def test_interpolate_sql
1024
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
1025
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
1026
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
1027
+ end
1028
+
1029
+ # FIXME: this test ought to run, but it needs to run sandboxed so that it
1030
+ # doesn't b0rk the current test environment by undefing everything.
1031
+ #
1032
+ #def test_dev_mode_memory_leak
1033
+ # counts = []
1034
+ # 2.times do
1035
+ # require_dependency 'fixtures/company'
1036
+ # Firm.find(:first)
1037
+ # Dependencies.clear
1038
+ # ActiveRecord::Base.reset_subclasses
1039
+ # Dependencies.remove_subclasses_for(ActiveRecord::Base)
1040
+ #
1041
+ # GC.start
1042
+ #
1043
+ # count = 0
1044
+ # ObjectSpace.each_object(Proc) { count += 1 }
1045
+ # counts << count
1046
+ # end
1047
+ # assert counts.last <= counts.first,
1048
+ # "expected last count (#{counts.last}) to be <= first count (#{counts.first})"
1049
+ #end
1050
+
1051
+ private
1052
+
1053
+ def assert_readers(model, exceptions)
1054
+ expected_readers = model.column_names - (model.serialized_attributes.keys + exceptions + ['id'])
1055
+ assert_equal expected_readers.sort, model.read_methods.keys.sort
1056
+ end
886
1057
  end