odbc-rails 1.3 → 1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/ChangeLog +18 -0
  2. data/NEWS +5 -0
  3. data/README +20 -7
  4. data/lib/active_record/connection_adapters/odbc_adapter.rb +254 -211
  5. data/lib/active_record/vendor/odbcext_informix.rb +17 -4
  6. data/lib/active_record/vendor/odbcext_ingres.rb +5 -5
  7. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +35 -4
  8. data/lib/active_record/vendor/odbcext_mysql.rb +36 -8
  9. data/lib/active_record/vendor/odbcext_oracle.rb +4 -4
  10. data/lib/active_record/vendor/odbcext_postgresql.rb +8 -12
  11. data/lib/active_record/vendor/odbcext_progress.rb +3 -3
  12. data/lib/active_record/vendor/odbcext_progress89.rb +5 -4
  13. data/lib/active_record/vendor/odbcext_sybase.rb +6 -5
  14. data/lib/active_record/vendor/odbcext_virtuoso.rb +16 -3
  15. data/support/odbc_rails.diff +335 -266
  16. data/support/rake_fixes/README +6 -0
  17. data/support/rake_fixes/databases.dif +13 -0
  18. data/support/test/base_test.rb +333 -75
  19. data/support/test/migration_test.rb +430 -149
  20. data/test/connections/native_odbc/connection.rb +63 -44
  21. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  22. data/test/fixtures/db_definitions/db2.sql +9 -0
  23. data/test/fixtures/db_definitions/informix.drop.sql +1 -0
  24. data/test/fixtures/db_definitions/informix.sql +10 -0
  25. data/test/fixtures/db_definitions/ingres.drop.sql +2 -0
  26. data/test/fixtures/db_definitions/ingres.sql +9 -0
  27. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  28. data/test/fixtures/db_definitions/mysql.sql +21 -12
  29. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +4 -0
  30. data/test/fixtures/db_definitions/oracle_odbc.sql +29 -1
  31. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  32. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  33. data/test/fixtures/db_definitions/progress.drop.sql +2 -0
  34. data/test/fixtures/db_definitions/progress.sql +11 -0
  35. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  36. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  37. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  38. data/test/fixtures/db_definitions/sybase.sql +16 -7
  39. data/test/fixtures/db_definitions/virtuoso.drop.sql +1 -0
  40. data/test/fixtures/db_definitions/virtuoso.sql +10 -0
  41. metadata +6 -3
@@ -0,0 +1,6 @@
1
+ databases.dif contains changes required to databases.rake in order for
2
+ the test:units and test:functional tasks to recognize the odbc-rails
3
+ adapter.
4
+
5
+ databases.rake can be found somewhere like:
6
+ ruby/lib/ruby/gems/1.8/gems/rails-1.2.1/lib/tasks
@@ -0,0 +1,13 @@
1
+ --- databases.rake.orig 2007-01-22 12:09:02.230758200 +0000
2
+ +++ databases.rake 2007-02-01 17:08:12.350537700 +0000
3
+ @@ -142,6 +142,10 @@
4
+ when "firebird"
5
+ ActiveRecord::Base.establish_connection(:test)
6
+ ActiveRecord::Base.connection.recreate_database!
7
+ + when "odbc"
8
+ + ActiveRecord::Base.establish_connection(:test)
9
+ + test_db = ActiveRecord::Base.connection.current_database
10
+ + ActiveRecord::Base.connection.recreate_database(test_db, false) unless test_db.empty?
11
+ else
12
+ raise "Task not supported by '#{abcs["test"]["adapter"]}'"
13
+ end
@@ -10,10 +10,18 @@ require 'fixtures/auto_id'
10
10
  require 'fixtures/column_name'
11
11
  require 'fixtures/subscriber'
12
12
  require 'fixtures/keyboard'
13
+ require 'fixtures/post'
13
14
 
14
15
  class Category < ActiveRecord::Base; end
15
16
  class Smarts < ActiveRecord::Base; end
16
- class CreditCard < ActiveRecord::Base; end
17
+ class CreditCard < ActiveRecord::Base
18
+ class PinNumber < ActiveRecord::Base
19
+ class CvvCode < ActiveRecord::Base; end
20
+ class SubCvvCode < CvvCode; end
21
+ end
22
+ class SubPinNumber < PinNumber; end
23
+ class Brand < Category; end
24
+ end
17
25
  class MasterCreditCard < ActiveRecord::Base; end
18
26
  class Post < ActiveRecord::Base; end
19
27
  class Computer < ActiveRecord::Base; end
@@ -21,8 +29,9 @@ class NonExistentTable < ActiveRecord::Base; end
21
29
  class TestOracleDefault < ActiveRecord::Base; end
22
30
 
23
31
  class LoosePerson < ActiveRecord::Base
24
- attr_protected :credit_rating, :administrator
32
+ self.table_name = 'people'
25
33
  self.abstract_class = true
34
+ attr_protected :credit_rating, :administrator
26
35
  end
27
36
 
28
37
  class LooseDescendant < LoosePerson
@@ -30,6 +39,7 @@ class LooseDescendant < LoosePerson
30
39
  end
31
40
 
32
41
  class TightPerson < ActiveRecord::Base
42
+ self.table_name = 'people'
33
43
  attr_accessible :name, :address
34
44
  end
35
45
 
@@ -59,7 +69,7 @@ class BasicsTest < Test::Unit::TestCase
59
69
  assert_equal("Jason", topic.author_name)
60
70
  assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
61
71
  end
62
-
72
+
63
73
  def test_integers_as_nil
64
74
  test = AutoId.create('value' => '')
65
75
  assert_nil AutoId.find(test.id).value
@@ -142,8 +152,20 @@ class BasicsTest < Test::Unit::TestCase
142
152
  def test_save!
143
153
  topic = Topic.new(:title => "New Topic")
144
154
  assert topic.save!
145
- end
146
155
 
156
+ reply = Reply.new
157
+ assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
158
+ end
159
+
160
+ def test_save_null_string_attributes
161
+ topic = Topic.find(1)
162
+ topic.attributes = { "title" => "null", "author_name" => "null" }
163
+ topic.save!
164
+ topic.reload
165
+ assert_equal("null", topic.title)
166
+ assert_equal("null", topic.author_name)
167
+ end
168
+
147
169
  def test_hashes_not_mangled
148
170
  new_topic = { :title => "New Topic" }
149
171
  new_topic_values = { :title => "AnotherTopic" }
@@ -304,8 +326,6 @@ class BasicsTest < Test::Unit::TestCase
304
326
  "The last_read attribute should be of the Time class"
305
327
  )
306
328
  else
307
- # ODBCAdapter fails against SQL Server because topics.last_read is
308
- # defined as a datetime column, which is returned as a Ruby Time object.
309
329
  assert_kind_of(
310
330
  Date, Topic.find(1).last_read,
311
331
  "The last_read attribute should be of the Date class"
@@ -323,23 +343,22 @@ class BasicsTest < Test::Unit::TestCase
323
343
  Time, Topic.find(1).written_on,
324
344
  "The written_on attribute should be of the Time class"
325
345
  )
346
+
347
+ # For adapters which support microsecond resolution.
348
+ if current_adapter?(:PostgreSQLAdapter)
349
+ assert_equal 11, Topic.find(1).written_on.sec
350
+ assert_equal 223300, Topic.find(1).written_on.usec
351
+ assert_equal 9900, Topic.find(2).written_on.usec
352
+ end
326
353
  end
327
-
354
+
328
355
  def test_destroy
329
- topic = Topic.new
330
- topic.title = "Yet Another New Topic"
331
- topic.written_on = "2003-12-12 23:23:00"
332
- topic.save
333
- topic.destroy
356
+ topic = Topic.find(1)
357
+ assert_equal topic, topic.destroy, 'topic.destroy did not return self'
358
+ assert topic.frozen?, 'topic not frozen after destroy'
334
359
  assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
335
360
  end
336
-
337
- def test_destroy_returns_self
338
- topic = Topic.new("title" => "Yet Another Title")
339
- assert topic.save
340
- assert_equal topic, topic.destroy, "destroy did not return destroyed object"
341
- end
342
-
361
+
343
362
  def test_record_not_found_exception
344
363
  assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
345
364
  end
@@ -376,21 +395,33 @@ class BasicsTest < Test::Unit::TestCase
376
395
  end
377
396
 
378
397
  def test_table_name_guesses
398
+ classes = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
399
+
379
400
  assert_equal "topics", Topic.table_name
380
-
401
+
381
402
  assert_equal "categories", Category.table_name
382
403
  assert_equal "smarts", Smarts.table_name
383
404
  assert_equal "credit_cards", CreditCard.table_name
405
+ assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
406
+ assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name
407
+ assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name
408
+ assert_equal "categories", CreditCard::Brand.table_name
384
409
  assert_equal "master_credit_cards", MasterCreditCard.table_name
385
410
 
386
411
  ActiveRecord::Base.pluralize_table_names = false
387
- [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
412
+ classes.each(&:reset_table_name)
413
+
388
414
  assert_equal "category", Category.table_name
389
415
  assert_equal "smarts", Smarts.table_name
390
416
  assert_equal "credit_card", CreditCard.table_name
417
+ assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
418
+ assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name
419
+ assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name
420
+ assert_equal "category", CreditCard::Brand.table_name
391
421
  assert_equal "master_credit_card", MasterCreditCard.table_name
422
+
392
423
  ActiveRecord::Base.pluralize_table_names = true
393
- [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
424
+ classes.each(&:reset_table_name)
394
425
 
395
426
  ActiveRecord::Base.table_name_prefix = "test_"
396
427
  Category.reset_table_name
@@ -418,8 +449,9 @@ class BasicsTest < Test::Unit::TestCase
418
449
  ActiveRecord::Base.table_name_suffix = ""
419
450
  Category.reset_table_name
420
451
  assert_equal "category", Category.table_name
452
+
421
453
  ActiveRecord::Base.pluralize_table_names = true
422
- [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
454
+ classes.each(&:reset_table_name)
423
455
  end
424
456
 
425
457
  def test_destroy_all
@@ -447,18 +479,18 @@ class BasicsTest < Test::Unit::TestCase
447
479
 
448
480
  def test_increment_counter
449
481
  Topic.increment_counter("replies_count", 1)
450
- assert_equal 1, Topic.find(1).replies_count
482
+ assert_equal 2, Topic.find(1).replies_count
451
483
 
452
484
  Topic.increment_counter("replies_count", 1)
453
- assert_equal 2, Topic.find(1).replies_count
485
+ assert_equal 3, Topic.find(1).replies_count
454
486
  end
455
487
 
456
488
  def test_decrement_counter
457
489
  Topic.decrement_counter("replies_count", 2)
458
- assert_equal 1, Topic.find(2).replies_count
490
+ assert_equal -1, Topic.find(2).replies_count
459
491
 
460
492
  Topic.decrement_counter("replies_count", 2)
461
- assert_equal 0, Topic.find(1).replies_count
493
+ assert_equal -2, Topic.find(2).replies_count
462
494
  end
463
495
 
464
496
  def test_update_all
@@ -557,16 +589,29 @@ class BasicsTest < Test::Unit::TestCase
557
589
  end
558
590
  end
559
591
 
560
- def test_utc_as_time_zone
561
- # Oracle and SQLServer do not have a TIME datatype.
562
- return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
592
+ # Oracle, SQLServer, and Sybase do not have a TIME datatype.
593
+ unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
594
+ def test_utc_as_time_zone
595
+ Topic.default_timezone = :utc
596
+ attributes = { "bonus_time" => "5:42:00AM" }
597
+ topic = Topic.find(1)
598
+ topic.attributes = attributes
599
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
600
+ Topic.default_timezone = :local
601
+ end
563
602
 
564
- Topic.default_timezone = :utc
565
- attributes = { "bonus_time" => "5:42:00AM" }
566
- topic = Topic.find(1)
567
- topic.attributes = attributes
568
- assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
569
- Topic.default_timezone = :local
603
+ def test_utc_as_time_zone_and_new
604
+ Topic.default_timezone = :utc
605
+ attributes = { "bonus_time(1i)"=>"2000",
606
+ "bonus_time(2i)"=>"1",
607
+ "bonus_time(3i)"=>"1",
608
+ "bonus_time(4i)"=>"10",
609
+ "bonus_time(5i)"=>"35",
610
+ "bonus_time(6i)"=>"50" }
611
+ topic = Topic.new(attributes)
612
+ assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
613
+ Topic.default_timezone = :local
614
+ end
570
615
  end
571
616
 
572
617
  def test_default_values_on_empty_strings
@@ -628,6 +673,40 @@ class BasicsTest < Test::Unit::TestCase
628
673
  assert !Topic.find(1).approved?
629
674
  end
630
675
 
676
+ def test_update_attributes
677
+ topic = Topic.find(1)
678
+ assert !topic.approved?
679
+ assert_equal "The First Topic", topic.title
680
+
681
+ topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
682
+ topic.reload
683
+ assert topic.approved?
684
+ assert_equal "The First Topic Updated", topic.title
685
+
686
+ topic.update_attributes(:approved => false, :title => "The First Topic")
687
+ topic.reload
688
+ assert !topic.approved?
689
+ assert_equal "The First Topic", topic.title
690
+ end
691
+
692
+ def test_update_attributes!
693
+ reply = Reply.find(2)
694
+ assert_equal "The Second Topic's of the day", reply.title
695
+ assert_equal "Have a nice day", reply.content
696
+
697
+ reply.update_attributes!("title" => "The Second Topic's of the day updated", "content" => "Have a nice evening")
698
+ reply.reload
699
+ assert_equal "The Second Topic's of the day updated", reply.title
700
+ assert_equal "Have a nice evening", reply.content
701
+
702
+ reply.update_attributes!(:title => "The Second Topic's of the day", :content => "Have a nice day")
703
+ reply.reload
704
+ assert_equal "The Second Topic's of the day", reply.title
705
+ assert_equal "Have a nice day", reply.content
706
+
707
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
708
+ end
709
+
631
710
  def test_mass_assignment_protection
632
711
  firm = Firm.new
633
712
  firm.attributes = { "name" => "Next Angle", "rating" => 5 }
@@ -746,12 +825,11 @@ class BasicsTest < Test::Unit::TestCase
746
825
  end
747
826
 
748
827
  def test_attributes_on_dummy_time
749
- # Oracle and SQL Server do not have a TIME datatype.
750
- return true if current_adapter?(:SQLServerAdapter) ||
751
- current_adapter?(:OracleAdapter)
828
+ # Oracle, SQL Server, and Sybase do not have a TIME datatype.
829
+ return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
752
830
  if current_adapter?(:ODBCAdapter)
753
- # Check for databases which don't have a true TIME datatype
754
- return true if [:ingres, :oracle].include?(ActiveRecord::Base.connection.dbmsName)
831
+ # Check for databases which don't have a true TIME datatype
832
+ return true if [:ingres, :oracle].include?(ActiveRecord::Base.connection.dbmsName)
755
833
  end
756
834
 
757
835
  attributes = {
@@ -922,12 +1000,45 @@ class BasicsTest < Test::Unit::TestCase
922
1000
  end
923
1001
  end
924
1002
 
1003
+ class NumericData < ActiveRecord::Base
1004
+ self.table_name = 'numeric_data'
1005
+ end
1006
+
1007
+ def test_numeric_fields
1008
+ m = NumericData.new(
1009
+ :bank_balance => 1586.43,
1010
+ :big_bank_balance => BigDecimal("1000234000567.95"),
1011
+ :world_population => 6000000000,
1012
+ :my_house_population => 3
1013
+ )
1014
+ assert m.save
1015
+
1016
+ m1 = NumericData.find(m.id)
1017
+ assert_not_nil m1
1018
+
1019
+ # As with migration_test.rb, we should make world_population >= 2**62
1020
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
1021
+ # is that it's an Integer.
1022
+ assert_kind_of Integer, m1.world_population
1023
+ assert_equal 6000000000, m1.world_population
1024
+
1025
+ assert_kind_of Fixnum, m1.my_house_population
1026
+ assert_equal 3, m1.my_house_population
1027
+
1028
+ assert_kind_of BigDecimal, m1.bank_balance
1029
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
1030
+
1031
+ assert_kind_of BigDecimal, m1.big_bank_balance
1032
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
1033
+
1034
+ end
1035
+
925
1036
  def test_auto_id
926
1037
  auto = AutoId.new
927
1038
  auto.save
928
1039
  assert (auto.id > 0)
929
1040
  end
930
-
1041
+
931
1042
  def quote_column_name(name)
932
1043
  "<#{name}>"
933
1044
  end
@@ -942,12 +1053,8 @@ class BasicsTest < Test::Unit::TestCase
942
1053
  end
943
1054
 
944
1055
  def test_sql_injection_via_find
945
- assert_raises(ActiveRecord::RecordNotFound) do
946
- Topic.find("123456 OR id > 0")
947
- end
948
-
949
- assert_raises(ActiveRecord::RecordNotFound) do
950
- Topic.find(";;; this should raise an RecordNotFound error")
1056
+ assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
1057
+ Topic.find("123456 OR id > 0")
951
1058
  end
952
1059
  end
953
1060
 
@@ -961,6 +1068,17 @@ class BasicsTest < Test::Unit::TestCase
961
1068
  assert_equal(41, c2.references)
962
1069
  end
963
1070
 
1071
+ def test_quoting_arrays
1072
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
1073
+ assert_equal topics(:first).replies.size, replies.size
1074
+
1075
+ # DB2 doesn't support "WHERE (id IN (NULL))" clause
1076
+ unless current_adapter?(:ODBCAdapter) && [:db2].include?(ActiveRecord::Base.connection.dbmsName)
1077
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
1078
+ assert_equal 0, replies.size
1079
+ end
1080
+ end
1081
+
964
1082
  MyObject = Struct.new :attribute1, :attribute2
965
1083
 
966
1084
  def test_serialized_attribute
@@ -994,6 +1112,18 @@ class BasicsTest < Test::Unit::TestCase
994
1112
  assert_equal author_name, Topic.find(topic.id).author_name
995
1113
  end
996
1114
 
1115
+ def test_quote_chars
1116
+ str = 'The Narrator'
1117
+ topic = Topic.create(:author_name => str)
1118
+ assert_equal str, topic.author_name
1119
+
1120
+ assert_kind_of ActiveSupport::Multibyte::Chars, str.chars
1121
+ topic = Topic.find_by_author_name(str.chars)
1122
+
1123
+ assert_kind_of Topic, topic
1124
+ assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
1125
+ end
1126
+
997
1127
  def test_class_level_destroy
998
1128
  should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
999
1129
  Topic.find(1).replies << should_be_destroyed_reply
@@ -1013,12 +1143,12 @@ class BasicsTest < Test::Unit::TestCase
1013
1143
  end
1014
1144
 
1015
1145
  def test_increment_attribute
1016
- assert_equal 0, topics(:first).replies_count
1146
+ assert_equal 1, topics(:first).replies_count
1017
1147
  topics(:first).increment! :replies_count
1018
- assert_equal 1, topics(:first, :reload).replies_count
1148
+ assert_equal 2, topics(:first, :reload).replies_count
1019
1149
 
1020
1150
  topics(:first).increment(:replies_count).increment!(:replies_count)
1021
- assert_equal 3, topics(:first, :reload).replies_count
1151
+ assert_equal 4, topics(:first, :reload).replies_count
1022
1152
  end
1023
1153
 
1024
1154
  def test_increment_nil_attribute
@@ -1029,13 +1159,13 @@ class BasicsTest < Test::Unit::TestCase
1029
1159
 
1030
1160
  def test_decrement_attribute
1031
1161
  topics(:first).increment(:replies_count).increment!(:replies_count)
1032
- assert_equal 2, topics(:first).replies_count
1162
+ assert_equal 3, topics(:first).replies_count
1033
1163
 
1034
1164
  topics(:first).decrement!(:replies_count)
1035
- assert_equal 1, topics(:first, :reload).replies_count
1165
+ assert_equal 2, topics(:first, :reload).replies_count
1036
1166
 
1037
1167
  topics(:first).decrement(:replies_count).decrement!(:replies_count)
1038
- assert_equal -1, topics(:first, :reload).replies_count
1168
+ assert_equal 0, topics(:first, :reload).replies_count
1039
1169
  end
1040
1170
 
1041
1171
  def test_toggle_attribute
@@ -1115,7 +1245,7 @@ class BasicsTest < Test::Unit::TestCase
1115
1245
  def test_count_with_join
1116
1246
  res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1117
1247
  res2 = nil
1118
- assert_nothing_raised do
1248
+ assert_deprecated 'count' do
1119
1249
  res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
1120
1250
  "LEFT JOIN comments ON posts.id=comments.post_id")
1121
1251
  end
@@ -1128,21 +1258,21 @@ class BasicsTest < Test::Unit::TestCase
1128
1258
  end
1129
1259
  assert_equal res, res3
1130
1260
 
1131
- 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"
1261
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1132
1262
  res5 = nil
1133
1263
  assert_nothing_raised do
1134
- res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1135
- :joins => "p, comments c",
1264
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1265
+ :joins => "p, comments co",
1136
1266
  :select => "p.id")
1137
1267
  end
1138
1268
 
1139
1269
  assert_equal res4, res5
1140
1270
 
1141
- 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"
1271
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1142
1272
  res7 = nil
1143
1273
  assert_nothing_raised do
1144
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
1145
- :joins => "p, comments c",
1274
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1275
+ :joins => "p, comments co",
1146
1276
  :select => "p.id",
1147
1277
  :distinct => true)
1148
1278
  end
@@ -1198,13 +1328,104 @@ class BasicsTest < Test::Unit::TestCase
1198
1328
  assert_equal Developer.count, developers.size
1199
1329
  end
1200
1330
 
1201
- def test_base_class
1331
+ def test_scoped_find_order
1332
+ # Test order in scope
1333
+ scoped_developers = Developer.with_scope(:find => { :limit => 1, :order => 'salary DESC' }) do
1334
+ Developer.find(:all)
1335
+ end
1336
+ assert_equal 'Jamis', scoped_developers.first.name
1337
+ assert scoped_developers.include?(developers(:jamis))
1338
+ # Test scope without order and order in find
1339
+ scoped_developers = Developer.with_scope(:find => { :limit => 1 }) do
1340
+ Developer.find(:all, :order => 'salary DESC')
1341
+ end
1342
+ # Test scope order + find order, find has priority
1343
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :order => 'id DESC' }) do
1344
+ Developer.find(:all, :order => 'salary ASC')
1345
+ end
1346
+ assert scoped_developers.include?(developers(:poor_jamis))
1347
+ assert scoped_developers.include?(developers(:david))
1348
+ assert scoped_developers.include?(developers(:dev_10))
1349
+ # Test without scoped find conditions to ensure we get the right thing
1350
+ developers = Developer.find(:all, :order => 'id', :limit => 1)
1351
+ assert scoped_developers.include?(developers(:david))
1352
+ end
1353
+
1354
+ def test_scoped_find_limit_offset_including_has_many_association
1355
+ topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do
1356
+ Topic.find(:all, :order => "topics.id")
1357
+ end
1358
+ assert_equal 1, topics.size
1359
+ assert_equal 2, topics.first.id
1360
+ end
1361
+
1362
+ def test_scoped_find_order_including_has_many_association
1363
+ developers = Developer.with_scope(:find => { :order => 'developers.salary DESC', :include => :projects }) do
1364
+ Developer.find(:all)
1365
+ end
1366
+ assert developers.size >= 2
1367
+ for i in 1...developers.size
1368
+ assert developers[i-1].salary >= developers[i].salary
1369
+ end
1370
+ end
1371
+
1372
+ def test_abstract_class
1373
+ assert !ActiveRecord::Base.abstract_class?
1202
1374
  assert LoosePerson.abstract_class?
1203
1375
  assert !LooseDescendant.abstract_class?
1376
+ end
1377
+
1378
+ def test_base_class
1204
1379
  assert_equal LoosePerson, LoosePerson.base_class
1205
1380
  assert_equal LooseDescendant, LooseDescendant.base_class
1206
1381
  assert_equal TightPerson, TightPerson.base_class
1207
1382
  assert_equal TightPerson, TightDescendant.base_class
1383
+
1384
+ assert_equal Post, Post.base_class
1385
+ assert_equal Post, SpecialPost.base_class
1386
+ assert_equal Post, StiPost.base_class
1387
+ assert_equal SubStiPost, SubStiPost.base_class
1388
+ end
1389
+
1390
+ def test_descends_from_active_record
1391
+ # Tries to call Object.abstract_class?
1392
+ assert_raise(NoMethodError) do
1393
+ ActiveRecord::Base.descends_from_active_record?
1394
+ end
1395
+
1396
+ # Abstract subclass of AR::Base.
1397
+ assert LoosePerson.descends_from_active_record?
1398
+
1399
+ # Concrete subclass of an abstract class.
1400
+ assert LooseDescendant.descends_from_active_record?
1401
+
1402
+ # Concrete subclass of AR::Base.
1403
+ assert TightPerson.descends_from_active_record?
1404
+
1405
+ # Concrete subclass of a concrete class but has no type column.
1406
+ assert TightDescendant.descends_from_active_record?
1407
+
1408
+ # Concrete subclass of AR::Base.
1409
+ assert Post.descends_from_active_record?
1410
+
1411
+ # Abstract subclass of a concrete class which has a type column.
1412
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
1413
+ assert !StiPost.descends_from_active_record?
1414
+
1415
+ # Concrete subclasses an abstract class which has a type column.
1416
+ assert !SubStiPost.descends_from_active_record?
1417
+ end
1418
+
1419
+ def test_find_on_abstract_base_class_doesnt_use_type_condition
1420
+ old_class = LooseDescendant
1421
+ Object.send :remove_const, :LooseDescendant
1422
+
1423
+ descendant = old_class.create!
1424
+ assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
1425
+ ensure
1426
+ unless Object.const_defined?(:LooseDescendant)
1427
+ Object.const_set :LooseDescendant, old_class
1428
+ end
1208
1429
  end
1209
1430
 
1210
1431
  def test_assert_queries
@@ -1218,26 +1439,29 @@ class BasicsTest < Test::Unit::TestCase
1218
1439
  xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
1219
1440
  bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
1220
1441
  written_on_in_current_timezone = topics(:first).written_on.xmlschema
1442
+ #cb+- Follwoing works
1443
+ #written_on = topics(:first).written_on
1221
1444
  last_read_in_current_timezone = topics(:first).last_read.xmlschema
1222
1445
  assert_equal "<topic>", xml.first(7)
1223
1446
  assert xml.include?(%(<title>The First Topic</title>))
1224
1447
  assert xml.include?(%(<author-name>David</author-name>))
1225
1448
  assert xml.include?(%(<id type="integer">1</id>))
1226
- assert xml.include?(%(<replies-count type="integer">0</replies-count>))
1449
+ assert xml.include?(%(<replies-count type="integer">1</replies-count>))
1227
1450
  assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
1451
+ #cb+- Following works
1452
+ #assert xml.include?(%(<written-on type="timestamp">#{written_on}</written-on>))
1228
1453
  assert xml.include?(%(<content>Have a nice day</content>))
1229
1454
  assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
1230
- assert xml.include?(%(<parent-id></parent-id>))
1455
+ assert xml.match(%(<parent-id type="integer"></parent-id>))
1231
1456
  # Following databases don't have a true date type, only a composite datetime type
1232
- if current_adapter?(:SybaseAdapter) or current_adapter?(:SQLServerAdapter) or
1233
- current_adapter?(:ODBCAdapter) &&
1234
- [:ingres,:oracle,:microsoftsqlserver].include?(ActiveRecord::Base.connection.dbmsName)
1457
+ if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter) or
1458
+ current_adapter?(:ODBCAdapter) && [:ingres,:oracle,:microsoftsqlserver].include?(ActiveRecord::Base.connection.dbmsName)
1235
1459
  assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
1236
1460
  else
1237
1461
  assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
1238
1462
  end
1239
1463
  # Following databases don't have a true boolean type
1240
- unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
1464
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
1241
1465
  if current_adapter?(:ODBCAdapter) &&
1242
1466
  [:ingres,:virtuoso,:oracle,:mysql,:db2,:progress].include?(ActiveRecord::Base.connection.dbmsName)
1243
1467
  assert xml.include?(%(<approved type="integer">0</approved>)), "Approved should be an integer"
@@ -1246,29 +1470,53 @@ class BasicsTest < Test::Unit::TestCase
1246
1470
  end
1247
1471
  end
1248
1472
  # Oracle and DB2 don't have a true time-only field
1249
- unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
1473
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
1250
1474
  assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
1251
1475
  end
1252
1476
  end
1253
-
1477
+
1254
1478
  def test_to_xml_skipping_attributes
1255
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title)
1479
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
1256
1480
  assert_equal "<topic>", xml.first(7)
1257
1481
  assert !xml.include?(%(<title>The First Topic</title>))
1258
1482
  assert xml.include?(%(<author-name>David</author-name>))
1259
1483
 
1260
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ])
1484
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
1261
1485
  assert !xml.include?(%(<title>The First Topic</title>))
1262
1486
  assert !xml.include?(%(<author-name>David</author-name>))
1263
1487
  end
1264
-
1488
+
1265
1489
  def test_to_xml_including_has_many_association
1266
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1490
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
1267
1491
  assert_equal "<topic>", xml.first(7)
1268
1492
  assert xml.include?(%(<replies><reply>))
1269
1493
  assert xml.include?(%(<title>The Second Topic's of the day</title>))
1270
1494
  end
1271
1495
 
1496
+ def test_array_to_xml_including_has_many_association
1497
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1498
+ assert xml.include?(%(<replies><reply>))
1499
+ end
1500
+
1501
+ def test_array_to_xml_including_methods
1502
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
1503
+ assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
1504
+ assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
1505
+ end
1506
+
1507
+ def test_array_to_xml_including_has_one_association
1508
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
1509
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
1510
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
1511
+ end
1512
+
1513
+ def test_array_to_xml_including_belongs_to_association
1514
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1515
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
1516
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
1517
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
1518
+ end
1519
+
1272
1520
  def test_to_xml_including_belongs_to_association
1273
1521
  xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1274
1522
  assert !xml.include?("<firm>")
@@ -1295,6 +1543,12 @@ class BasicsTest < Test::Unit::TestCase
1295
1543
  assert xml.include?(%(<clients><client>))
1296
1544
  end
1297
1545
 
1546
+ def test_to_xml_including_methods
1547
+ xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
1548
+ assert_equal "<company>", xml.first(9)
1549
+ assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
1550
+ end
1551
+
1298
1552
  def test_except_attributes
1299
1553
  assert_equal(
1300
1554
  %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
@@ -1316,6 +1570,10 @@ class BasicsTest < Test::Unit::TestCase
1316
1570
  assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
1317
1571
  assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
1318
1572
  end
1573
+
1574
+ def test_to_param_should_return_string
1575
+ assert_kind_of String, Client.find(:first).to_param
1576
+ end
1319
1577
 
1320
1578
  # FIXME: this test ought to run, but it needs to run sandboxed so that it
1321
1579
  # doesn't b0rk the current test environment by undefing everything.
@@ -1341,7 +1599,7 @@ class BasicsTest < Test::Unit::TestCase
1341
1599
 
1342
1600
  private
1343
1601
  def assert_readers(model, exceptions)
1344
- expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
1602
+ expected_readers = Set.new(model.column_names - ['id'])
1345
1603
  expected_readers += expected_readers.map { |col| "#{col}?" }
1346
1604
  expected_readers -= exceptions
1347
1605
  assert_equal expected_readers, model.read_methods