odbc-rails 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
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