activerecord 1.13.2 → 1.14.0

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 (144) hide show
  1. data/CHANGELOG +452 -10
  2. data/RUNNING_UNIT_TESTS +1 -1
  3. data/lib/active_record.rb +5 -2
  4. data/lib/active_record/acts/list.rb +1 -1
  5. data/lib/active_record/acts/tree.rb +29 -25
  6. data/lib/active_record/aggregations.rb +3 -2
  7. data/lib/active_record/associations.rb +783 -337
  8. data/lib/active_record/associations/association_collection.rb +7 -12
  9. data/lib/active_record/associations/association_proxy.rb +62 -24
  10. data/lib/active_record/associations/belongs_to_association.rb +27 -46
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
  13. data/lib/active_record/associations/has_many_association.rb +61 -56
  14. data/lib/active_record/associations/has_many_through_association.rb +144 -0
  15. data/lib/active_record/associations/has_one_association.rb +22 -16
  16. data/lib/active_record/base.rb +482 -182
  17. data/lib/active_record/calculations.rb +225 -0
  18. data/lib/active_record/callbacks.rb +7 -7
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
  25. data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
  26. data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
  27. data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
  31. data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
  32. data/lib/active_record/fixtures.rb +42 -17
  33. data/lib/active_record/locking.rb +36 -15
  34. data/lib/active_record/migration.rb +111 -8
  35. data/lib/active_record/observer.rb +25 -1
  36. data/lib/active_record/reflection.rb +103 -41
  37. data/lib/active_record/schema.rb +2 -2
  38. data/lib/active_record/schema_dumper.rb +55 -18
  39. data/lib/active_record/timestamp.rb +6 -6
  40. data/lib/active_record/validations.rb +65 -40
  41. data/lib/active_record/vendor/db2.rb +10 -5
  42. data/lib/active_record/vendor/simple.rb +693 -702
  43. data/lib/active_record/version.rb +2 -2
  44. data/rakefile +4 -4
  45. data/test/aaa_create_tables_test.rb +25 -6
  46. data/test/abstract_unit.rb +39 -1
  47. data/test/adapter_test.rb +31 -4
  48. data/test/associations_cascaded_eager_loading_test.rb +106 -0
  49. data/test/associations_go_eager_test.rb +85 -16
  50. data/test/associations_join_model_test.rb +338 -0
  51. data/test/associations_test.rb +129 -50
  52. data/test/base_test.rb +204 -49
  53. data/test/binary_test.rb +1 -1
  54. data/test/calculations_test.rb +169 -0
  55. data/test/callbacks_test.rb +5 -23
  56. data/test/class_inheritable_attributes_test.rb +1 -1
  57. data/test/column_alias_test.rb +1 -1
  58. data/test/connections/native_mysql/connection.rb +1 -0
  59. data/test/connections/native_openbase/connection.rb +22 -0
  60. data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
  61. data/test/connections/native_sqlite/connection.rb +1 -1
  62. data/test/connections/native_sqlite3/connection.rb +1 -0
  63. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
  64. data/test/connections/native_sybase/connection.rb +24 -0
  65. data/test/defaults_test.rb +18 -0
  66. data/test/deprecated_associations_test.rb +2 -2
  67. data/test/deprecated_finder_test.rb +0 -6
  68. data/test/finder_test.rb +26 -23
  69. data/test/fixtures/accounts.yml +10 -0
  70. data/test/fixtures/author.rb +31 -6
  71. data/test/fixtures/author_favorites.yml +4 -0
  72. data/test/fixtures/categories/special_categories.yml +9 -0
  73. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  74. data/test/fixtures/categories_posts.yml +4 -0
  75. data/test/fixtures/categorization.rb +5 -0
  76. data/test/fixtures/categorizations.yml +11 -0
  77. data/test/fixtures/category.rb +6 -0
  78. data/test/fixtures/company.rb +17 -5
  79. data/test/fixtures/company_in_module.rb +19 -5
  80. data/test/fixtures/db_definitions/db2.drop.sql +3 -0
  81. data/test/fixtures/db_definitions/db2.sql +121 -100
  82. data/test/fixtures/db_definitions/db22.sql +2 -2
  83. data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
  84. data/test/fixtures/db_definitions/firebird.sql +26 -0
  85. data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
  86. data/test/fixtures/db_definitions/mysql.sql +21 -1
  87. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  88. data/test/fixtures/db_definitions/openbase.sql +282 -0
  89. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  90. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  91. data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
  92. data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
  93. data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
  94. data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
  95. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  96. data/test/fixtures/db_definitions/postgresql.sql +22 -1
  97. data/test/fixtures/db_definitions/schema.rb +32 -0
  98. data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
  99. data/test/fixtures/db_definitions/sqlite.sql +18 -0
  100. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  101. data/test/fixtures/db_definitions/sqlserver.sql +23 -3
  102. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  103. data/test/fixtures/db_definitions/sybase.sql +204 -0
  104. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  106. data/test/fixtures/developers.yml +6 -1
  107. data/test/fixtures/developers_projects.yml +4 -0
  108. data/test/fixtures/funny_jokes.yml +14 -0
  109. data/test/fixtures/joke.rb +6 -0
  110. data/test/fixtures/legacy_thing.rb +3 -0
  111. data/test/fixtures/legacy_things.yml +3 -0
  112. data/test/fixtures/mixin.rb +1 -1
  113. data/test/fixtures/person.rb +4 -1
  114. data/test/fixtures/post.rb +26 -1
  115. data/test/fixtures/project.rb +1 -0
  116. data/test/fixtures/reader.rb +4 -0
  117. data/test/fixtures/readers.yml +4 -0
  118. data/test/fixtures/reply.rb +2 -1
  119. data/test/fixtures/tag.rb +5 -0
  120. data/test/fixtures/tagging.rb +6 -0
  121. data/test/fixtures/taggings.yml +18 -0
  122. data/test/fixtures/tags.yml +7 -0
  123. data/test/fixtures/tasks.yml +2 -2
  124. data/test/fixtures/topic.rb +2 -2
  125. data/test/fixtures/topics.yml +1 -0
  126. data/test/fixtures_test.rb +47 -13
  127. data/test/inheritance_test.rb +2 -2
  128. data/test/locking_test.rb +15 -1
  129. data/test/method_scoping_test.rb +248 -13
  130. data/test/migration_test.rb +68 -11
  131. data/test/mixin_nested_set_test.rb +1 -1
  132. data/test/modules_test.rb +6 -1
  133. data/test/readonly_test.rb +1 -1
  134. data/test/reflection_test.rb +63 -9
  135. data/test/schema_dumper_test.rb +41 -0
  136. data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
  137. data/test/threaded_connections_test.rb +10 -0
  138. data/test/unconnected_test.rb +12 -5
  139. data/test/validations_test.rb +197 -10
  140. metadata +295 -260
  141. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
  142. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
  143. data/test/fixtures/fixture_database.sqlite +0 -0
  144. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -17,10 +17,11 @@ class MasterCreditCard < ActiveRecord::Base; end
17
17
  class Post < ActiveRecord::Base; end
18
18
  class Computer < ActiveRecord::Base; end
19
19
  class NonExistentTable < ActiveRecord::Base; end
20
- class TestOCIDefault < ActiveRecord::Base; end
20
+ class TestOracleDefault < ActiveRecord::Base; end
21
21
 
22
22
  class LoosePerson < ActiveRecord::Base
23
23
  attr_protected :credit_rating, :administrator
24
+ self.abstract_class = true
24
25
  end
25
26
 
26
27
  class LooseDescendant < LoosePerson
@@ -133,8 +134,24 @@ class BasicsTest < Test::Unit::TestCase
133
134
  topic = Topic.new
134
135
  topic.title = "New Topic"
135
136
  topic.save
136
- topicReloaded = Topic.find(topic.id)
137
- assert_equal("New Topic", topicReloaded.title)
137
+ topic_reloaded = Topic.find(topic.id)
138
+ assert_equal("New Topic", topic_reloaded.title)
139
+ end
140
+
141
+ def test_save!
142
+ topic = Topic.new(:title => "New Topic")
143
+ assert topic.save!
144
+ end
145
+
146
+ def test_hashes_not_mangled
147
+ new_topic = { :title => "New Topic" }
148
+ new_topic_values = { :title => "AnotherTopic" }
149
+
150
+ topic = Topic.new(new_topic)
151
+ assert_equal new_topic[:title], topic.title
152
+
153
+ topic.attributes= new_topic_values
154
+ assert_equal new_topic_values[:title], topic.title
138
155
  end
139
156
 
140
157
  def test_create_many
@@ -249,7 +266,7 @@ class BasicsTest < Test::Unit::TestCase
249
266
  if ActiveRecord::Base.generate_read_methods
250
267
  assert_readers(Topic, %w(type replies_count))
251
268
  assert_readers(Firm, %w(type))
252
- assert_readers(Client, %w(type))
269
+ assert_readers(Client, %w(type ruby_type rating?))
253
270
  else
254
271
  [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
255
272
  end
@@ -273,10 +290,18 @@ class BasicsTest < Test::Unit::TestCase
273
290
  # SQL Server doesn't have a separate column type just for dates, so all are returned as time
274
291
  return true if current_adapter?(:SQLServerAdapter)
275
292
 
276
- assert_kind_of(
277
- Date, Topic.find(1).last_read,
278
- "The last_read attribute should be of the Date class"
279
- )
293
+ if current_adapter?(:SybaseAdapter)
294
+ # Sybase ctlib does not (yet?) support the date type; use datetime instead.
295
+ assert_kind_of(
296
+ Time, Topic.find(1).last_read,
297
+ "The last_read attribute should be of the Time class"
298
+ )
299
+ else
300
+ assert_kind_of(
301
+ Date, Topic.find(1).last_read,
302
+ "The last_read attribute should be of the Date class"
303
+ )
304
+ end
280
305
  end
281
306
 
282
307
  def test_preserving_time_objects
@@ -434,7 +459,7 @@ class BasicsTest < Test::Unit::TestCase
434
459
  assert_equal 2, Topic.update_all("content = 'bulk updated!'")
435
460
  assert_equal "bulk updated!", Topic.find(1).content
436
461
  assert_equal "bulk updated!", Topic.find(2).content
437
- assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!']);
462
+ assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
438
463
  assert_equal "bulk updated again!", Topic.find(1).content
439
464
  assert_equal "bulk updated again!", Topic.find(2).content
440
465
  end
@@ -515,8 +540,8 @@ class BasicsTest < Test::Unit::TestCase
515
540
 
516
541
  # Oracle has some funky default handling, so it requires a bit of
517
542
  # extra testing. See ticket #2788.
518
- if current_adapter?(:OCIAdapter)
519
- test = TestOCIDefault.new
543
+ if current_adapter?(:OracleAdapter)
544
+ test = TestOracleDefault.new
520
545
  assert_equal "X", test.test_char
521
546
  assert_equal "hello", test.test_string
522
547
  assert_equal 3, test.test_int
@@ -525,7 +550,7 @@ class BasicsTest < Test::Unit::TestCase
525
550
 
526
551
  def test_utc_as_time_zone
527
552
  # Oracle and SQLServer do not have a TIME datatype.
528
- return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OCIAdapter)
553
+ return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
529
554
 
530
555
  Topic.default_timezone = :utc
531
556
  attributes = { "bonus_time" => "5:42:00AM" }
@@ -544,11 +569,17 @@ class BasicsTest < Test::Unit::TestCase
544
569
 
545
570
  topic = Topic.find(topic.id)
546
571
  assert_nil topic.last_read
547
- assert_nil topic.approved
572
+
573
+ # Sybase adapter does not allow nulls in boolean columns
574
+ if current_adapter?(:SybaseAdapter)
575
+ assert topic.approved == false
576
+ else
577
+ assert_nil topic.approved
578
+ end
548
579
  end
549
580
 
550
581
  def test_equality
551
- assert_equal Topic.find(1), Topic.find(2).parent
582
+ assert_equal Topic.find(1), Topic.find(2).topic
552
583
  end
553
584
 
554
585
  def test_equality_of_new_records
@@ -556,7 +587,7 @@ class BasicsTest < Test::Unit::TestCase
556
587
  end
557
588
 
558
589
  def test_hashing
559
- assert_equal [ Topic.find(1) ], [ Topic.find(2).parent ] & [ Topic.find(1) ]
590
+ assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
560
591
  end
561
592
 
562
593
  def test_destroy_new_record
@@ -638,27 +669,21 @@ class BasicsTest < Test::Unit::TestCase
638
669
  end
639
670
 
640
671
  def test_multiparameter_attributes_on_date
641
- # SQL Server doesn't have a separate column type just for dates, so all are returned as time
642
- return true if current_adapter?(:SQLServerAdapter)
643
-
644
672
  attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
645
673
  topic = Topic.find(1)
646
674
  topic.attributes = attributes
647
675
  # note that extra #to_date call allows test to pass for Oracle, which
648
676
  # treats dates/times the same
649
- assert_equal Date.new(2004, 6, 24).to_s, topic.last_read.to_date.to_s
677
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
650
678
  end
651
679
 
652
680
  def test_multiparameter_attributes_on_date_with_empty_date
653
- # SQL Server doesn't have a separate column type just for dates, so all are returned as time
654
- return true if current_adapter?(:SQLServerAdapter)
655
-
656
681
  attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
657
682
  topic = Topic.find(1)
658
683
  topic.attributes = attributes
659
684
  # note that extra #to_date call allows test to pass for Oracle, which
660
685
  # treats dates/times the same
661
- assert_equal Date.new(2004, 6, 1).to_s, topic.last_read.to_date.to_s
686
+ assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
662
687
  end
663
688
 
664
689
  def test_multiparameter_attributes_on_date_with_all_empty
@@ -699,7 +724,7 @@ class BasicsTest < Test::Unit::TestCase
699
724
 
700
725
  def test_attributes_on_dummy_time
701
726
  # Oracle and SQL Server do not have a TIME datatype.
702
- return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OCIAdapter)
727
+ return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
703
728
 
704
729
  attributes = {
705
730
  "bonus_time" => "5:42:00AM"
@@ -720,6 +745,18 @@ class BasicsTest < Test::Unit::TestCase
720
745
  b_true = Booleantest.find(true_id)
721
746
  assert b_true.value?
722
747
  end
748
+
749
+ def test_boolean_cast_from_string
750
+ b_false = Booleantest.create({ "value" => "0" })
751
+ false_id = b_false.id
752
+ b_true = Booleantest.create({ "value" => "1" })
753
+ true_id = b_true.id
754
+
755
+ b_false = Booleantest.find(false_id)
756
+ assert !b_false.value?
757
+ b_true = Booleantest.find(true_id)
758
+ assert b_true.value?
759
+ end
723
760
 
724
761
  def test_clone
725
762
  topic = Topic.find(1)
@@ -784,17 +821,6 @@ class BasicsTest < Test::Unit::TestCase
784
821
  def test_default
785
822
  default = Default.new
786
823
 
787
- # CURRENT_TIMESTAMP and NOW() timestamps
788
- time_format = "%m/%d/%Y %H:%M"
789
- now = Time.now.strftime(time_format)
790
- assert_equal now, default.modified_time.strftime(time_format)
791
- assert_equal now, default.modified_time_function.strftime(time_format)
792
-
793
- # CURRENT_DATE and NOW() dates
794
- today = Date.today
795
- assert_equal today, default.modified_date
796
- assert_equal today, default.modified_date_function
797
-
798
824
  # fixed dates / times
799
825
  assert_equal Date.new(2004, 1, 1), default.fixed_date
800
826
  assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
@@ -1045,12 +1071,39 @@ class BasicsTest < Test::Unit::TestCase
1045
1071
 
1046
1072
  def test_count_with_join
1047
1073
  res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1048
- res2 = res + 1
1074
+ res2 = nil
1049
1075
  assert_nothing_raised do
1050
1076
  res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
1051
1077
  "LEFT JOIN comments ON posts.id=comments.post_id")
1052
1078
  end
1053
1079
  assert_equal res, res2
1080
+
1081
+ res3 = nil
1082
+ assert_nothing_raised do
1083
+ res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
1084
+ :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1085
+ end
1086
+ assert_equal res, res3
1087
+
1088
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
1089
+ res5 = nil
1090
+ assert_nothing_raised do
1091
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1092
+ :joins => "p, comments c",
1093
+ :select => "p.id")
1094
+ end
1095
+
1096
+ assert_equal res4, res5
1097
+
1098
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
1099
+ res7 = nil
1100
+ assert_nothing_raised do
1101
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1102
+ :joins => "p, comments c",
1103
+ :select => "p.id",
1104
+ :distinct => true)
1105
+ end
1106
+ assert_equal res6, res7
1054
1107
  end
1055
1108
 
1056
1109
  def test_clear_association_cache_stored
@@ -1082,29 +1135,129 @@ class BasicsTest < Test::Unit::TestCase
1082
1135
  end
1083
1136
 
1084
1137
  def test_scoped_find_conditions
1085
- developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
1138
+ scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
1086
1139
  Developer.find(:all, :conditions => 'id < 5')
1087
1140
  end
1088
- david = Developer.find(1)
1089
- assert !developers.include?(david) # David's salary is less than 90,000
1090
- assert_equal 3, developers.size
1141
+ assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
1142
+ assert_equal 3, scoped_developers.size
1091
1143
  end
1092
1144
 
1093
1145
  def test_scoped_find_limit_offset
1094
- developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
1146
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
1095
1147
  Developer.find(:all, :order => 'id')
1096
1148
  end
1097
- david = Developer.find(1)
1098
- jamis = Developer.find(1)
1099
- assert !developers.include?(david) # David has id 1
1100
- assert !developers.include?(jamis) # Jamis has id 2
1101
- assert_equal 3, developers.size
1149
+ assert !scoped_developers.include?(developers(:david))
1150
+ assert !scoped_developers.include?(developers(:jamis))
1151
+ assert_equal 3, scoped_developers.size
1102
1152
 
1103
1153
  # Test without scoped find conditions to ensure we get the whole thing
1104
1154
  developers = Developer.find(:all, :order => 'id')
1105
- assert_equal 10, developers.size
1155
+ assert_equal Developer.count, developers.size
1156
+ end
1157
+
1158
+ def test_base_class
1159
+ assert LoosePerson.abstract_class?
1160
+ assert !LooseDescendant.abstract_class?
1161
+ assert_equal LoosePerson, LoosePerson.base_class
1162
+ assert_equal LooseDescendant, LooseDescendant.base_class
1163
+ assert_equal TightPerson, TightPerson.base_class
1164
+ assert_equal TightPerson, TightDescendant.base_class
1165
+ end
1166
+
1167
+ def test_assert_queries
1168
+ query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
1169
+ assert_queries(2) { 2.times { query.call } }
1170
+ assert_queries 1, &query
1171
+ assert_no_queries { assert true }
1172
+ end
1173
+
1174
+ def test_to_xml
1175
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
1176
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
1177
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
1178
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
1179
+ assert_equal "<topic>", xml.first(7)
1180
+ assert xml.include?(%(<title>The First Topic</title>))
1181
+ assert xml.include?(%(<author-name>David</author-name>))
1182
+ assert xml.include?(%(<id type="integer">1</id>))
1183
+ assert xml.include?(%(<replies-count type="integer">0</replies-count>))
1184
+ assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
1185
+ assert xml.include?(%(<content>Have a nice day</content>))
1186
+ assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
1187
+ assert xml.include?(%(<parent-id></parent-id>))
1188
+ if current_adapter?(:SybaseAdapter) or current_adapter?(:SQLServerAdapter)
1189
+ assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
1190
+ else
1191
+ assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
1192
+ end
1193
+ # Oracle and DB2 don't have true boolean or time-only fields
1194
+ unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
1195
+ assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean"
1196
+ assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
1197
+ end
1198
+ end
1199
+
1200
+ def test_to_xml_skipping_attributes
1201
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title)
1202
+ assert_equal "<topic>", xml.first(7)
1203
+ assert !xml.include?(%(<title>The First Topic</title>))
1204
+ assert xml.include?(%(<author-name>David</author-name>))
1205
+
1206
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ])
1207
+ assert !xml.include?(%(<title>The First Topic</title>))
1208
+ assert !xml.include?(%(<author-name>David</author-name>))
1209
+ end
1210
+
1211
+ def test_to_xml_including_has_many_association
1212
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1213
+ assert_equal "<topic>", xml.first(7)
1214
+ assert xml.include?(%(<replies><reply>))
1215
+ assert xml.include?(%(<title>The Second Topic's of the day</title>))
1216
+ end
1217
+
1218
+ def test_to_xml_including_belongs_to_association
1219
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1220
+ assert !xml.include?("<firm>")
1221
+
1222
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1223
+ assert xml.include?("<firm>")
1224
+ end
1225
+
1226
+ def test_to_xml_including_multiple_associations
1227
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
1228
+ assert_equal "<firm>", xml.first(6)
1229
+ assert xml.include?(%(<account>))
1230
+ assert xml.include?(%(<clients><client>))
1231
+ end
1232
+
1233
+ def test_to_xml_including_multiple_associations_with_options
1234
+ xml = companies(:first_firm).to_xml(
1235
+ :indent => 0, :skip_instruct => true,
1236
+ :include => { :clients => { :only => :name } }
1237
+ )
1238
+
1239
+ assert_equal "<firm>", xml.first(6)
1240
+ assert xml.include?(%(<client><name>Summit</name></client>))
1241
+ assert xml.include?(%(<clients><client>))
1242
+ end
1243
+
1244
+ def test_except_attributes
1245
+ assert_equal(
1246
+ %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
1247
+ topics(:first).attributes(:except => :title).keys
1248
+ )
1249
+
1250
+ assert_equal(
1251
+ %w( replies_count bonus_time written_on content author_email_address parent_id last_read),
1252
+ topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys
1253
+ )
1106
1254
  end
1107
1255
 
1256
+ def test_include_attributes
1257
+ assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys)
1258
+ assert_equal(%w( title author_name type id approved ), topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys)
1259
+ end
1260
+
1108
1261
  # FIXME: this test ought to run, but it needs to run sandboxed so that it
1109
1262
  # doesn't b0rk the current test environment by undefing everything.
1110
1263
  #
@@ -1129,7 +1282,9 @@ class BasicsTest < Test::Unit::TestCase
1129
1282
 
1130
1283
  private
1131
1284
  def assert_readers(model, exceptions)
1132
- expected_readers = model.column_names - (model.serialized_attributes.keys + exceptions + ['id'])
1133
- assert_equal expected_readers.sort, model.read_methods.to_a.sort
1285
+ expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
1286
+ expected_readers += expected_readers.map { |col| "#{col}?" }
1287
+ expected_readers -= exceptions
1288
+ assert_equal expected_readers, model.read_methods
1134
1289
  end
1135
1290
  end
@@ -20,7 +20,7 @@ class BinaryTest < Test::Unit::TestCase
20
20
  # Without using prepared statements, it makes no sense to test
21
21
  # BLOB data with DB2 or Firebird, because the length of a statement
22
22
  # is limited to 32KB.
23
- unless %w(SQLServer DB2 OCI Firebird).include? ActiveRecord::Base.connection.adapter_name
23
+ unless %w(SQLServer Sybase DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name
24
24
  def test_load_save
25
25
  bin = Binary.new
26
26
  bin.data = @data
@@ -0,0 +1,169 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/company'
3
+ require 'fixtures/topic'
4
+
5
+ Company.has_many :accounts
6
+
7
+ class CalculationsTest < Test::Unit::TestCase
8
+ fixtures :companies, :accounts, :topics
9
+
10
+ def test_should_sum_field
11
+ assert_equal 265, Account.sum(:credit_limit)
12
+ end
13
+
14
+ def test_should_average_field
15
+ value = Account.average(:credit_limit)
16
+ assert_equal 53, value
17
+ assert_kind_of Float, value
18
+ end
19
+
20
+ def test_should_get_maximum_of_field
21
+ assert_equal 60, Account.maximum(:credit_limit)
22
+ end
23
+
24
+ def test_should_get_minimum_of_field
25
+ assert_equal 50, Account.minimum(:credit_limit)
26
+ end
27
+
28
+ def test_should_group_by_field
29
+ c = Account.sum(:credit_limit, :group => :firm_id)
30
+ [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
31
+ end
32
+
33
+ def test_should_group_by_summed_field
34
+ c = Account.sum(:credit_limit, :group => :firm_id)
35
+ assert_equal 50, c[1]
36
+ assert_equal 105, c[6]
37
+ assert_equal 60, c[2]
38
+ end
39
+
40
+ def test_should_order_by_grouped_field
41
+ c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
42
+ assert_equal [1, 2, 6], c.keys.compact
43
+ end
44
+
45
+ def test_should_order_by_calculation
46
+ c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
47
+ assert_equal [105, 60, 50, 50], c.keys.collect { |k| c[k] }
48
+ assert_equal [6, 2, 1], c.keys.compact
49
+ end
50
+
51
+ def test_should_group_by_summed_field_having_condition
52
+ c = Account.sum(:credit_limit, :group => :firm_id,
53
+ :having => 'sum(credit_limit) > 50')
54
+ assert_nil c[1]
55
+ assert_equal 105, c[6]
56
+ assert_equal 60, c[2]
57
+ end
58
+
59
+ def test_should_group_by_summed_association
60
+ c = Account.sum(:credit_limit, :group => :firm)
61
+ assert_equal 50, c[companies(:first_firm)]
62
+ assert_equal 105, c[companies(:rails_core)]
63
+ assert_equal 60, c[companies(:first_client)]
64
+ end
65
+
66
+ def test_should_sum_field_with_conditions
67
+ assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
68
+ end
69
+
70
+ def test_should_group_by_summed_field_with_conditions
71
+ c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
72
+ :group => :firm_id)
73
+ assert_nil c[1]
74
+ assert_equal 105, c[6]
75
+ assert_equal 60, c[2]
76
+ end
77
+
78
+ def test_should_group_by_summed_field_with_conditions_and_having
79
+ c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
80
+ :group => :firm_id,
81
+ :having => 'sum(credit_limit) > 60')
82
+ assert_nil c[1]
83
+ assert_equal 105, c[6]
84
+ assert_nil c[2]
85
+ end
86
+
87
+ def test_should_group_by_fields_with_table_alias
88
+ c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
89
+ assert_equal 50, c[1]
90
+ assert_equal 105, c[6]
91
+ assert_equal 60, c[2]
92
+ end
93
+
94
+ def test_should_calculate_with_invalid_field
95
+ assert_equal 5, Account.calculate(:count, '*')
96
+ assert_equal 5, Account.calculate(:count, :all)
97
+ end
98
+
99
+ def test_should_calculate_grouped_with_invalid_field
100
+ c = Account.count(:all, :group => 'accounts.firm_id')
101
+ assert_equal 1, c[1]
102
+ assert_equal 2, c[6]
103
+ assert_equal 1, c[2]
104
+ end
105
+
106
+ def test_should_calculate_grouped_association_with_invalid_field
107
+ c = Account.count(:all, :group => :firm)
108
+ assert_equal 1, c[companies(:first_firm)]
109
+ assert_equal 2, c[companies(:rails_core)]
110
+ assert_equal 1, c[companies(:first_client)]
111
+ end
112
+
113
+ def test_should_calculate_grouped_by_function
114
+ c = Company.count(:all, :group => 'UPPER(type)')
115
+ assert_equal 2, c[nil]
116
+ assert_equal 1, c['DEPENDENTFIRM']
117
+ assert_equal 3, c['CLIENT']
118
+ assert_equal 2, c['FIRM']
119
+ end
120
+
121
+ def test_should_calculate_grouped_by_function_with_table_alias
122
+ c = Company.count(:all, :group => 'UPPER(companies.type)')
123
+ assert_equal 2, c[nil]
124
+ assert_equal 1, c['DEPENDENTFIRM']
125
+ assert_equal 3, c['CLIENT']
126
+ assert_equal 2, c['FIRM']
127
+ end
128
+
129
+ def test_should_sum_scoped_field
130
+ assert_equal 15, companies(:rails_core).companies.sum(:id)
131
+ end
132
+
133
+ def test_should_sum_scoped_field_with_conditions
134
+ assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
135
+ end
136
+
137
+ def test_should_group_by_scoped_field
138
+ c = companies(:rails_core).companies.sum(:id, :group => :name)
139
+ assert_equal 7, c['Leetsoft']
140
+ assert_equal 8, c['Jadedpixel']
141
+ end
142
+
143
+ def test_should_group_by_summed_field_with_conditions_and_having
144
+ c = companies(:rails_core).companies.sum(:id, :group => :name,
145
+ :having => 'sum(id) > 7')
146
+ assert_nil c['Leetsoft']
147
+ assert_equal 8, c['Jadedpixel']
148
+ end
149
+
150
+ def test_should_reject_invalid_options
151
+ assert_nothing_raised do
152
+ [:count, :sum].each do |func|
153
+ # empty options are valid
154
+ Company.send(:validate_calculation_options, func)
155
+ # these options are valid for all calculations
156
+ [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt|
157
+ Company.send(:validate_calculation_options, func, opt => true)
158
+ end
159
+ end
160
+
161
+ # :include is only valid on :count
162
+ Company.send(:validate_calculation_options, :count, :include => true)
163
+ end
164
+
165
+ assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :include => :posts) }
166
+ assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) }
167
+ assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }
168
+ end
169
+ end