activerecord 1.14.4 → 1.15.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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -14,7 +14,11 @@ class AAACreateTablesTest < Test::Unit::TestCase
14
14
  end
15
15
 
16
16
  def test_load_schema
17
- eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
17
+ if ActiveRecord::Base.connection.supports_migrations?
18
+ eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
19
+ else
20
+ recreate ActiveRecord::Base, '3'
21
+ end
18
22
  assert true
19
23
  end
20
24
 
@@ -8,6 +8,10 @@ require 'active_support/binding_of_caller'
8
8
  require 'active_support/breakpoint'
9
9
  require 'connection'
10
10
 
11
+ # Show backtraces for deprecated behavior for quicker cleanup.
12
+ ActiveSupport::Deprecation.debug = true
13
+
14
+
11
15
  QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
12
16
 
13
17
  class Test::Unit::TestCase #:nodoc:
@@ -18,9 +22,9 @@ class Test::Unit::TestCase #:nodoc:
18
22
  def create_fixtures(*table_names, &block)
19
23
  Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
20
24
  end
21
-
25
+
22
26
  def assert_date_from_db(expected, actual, message = nil)
23
- # SQL Server doesn't have a separate column type just for dates,
27
+ # SQL Server doesn't have a separate column type just for dates,
24
28
  # so the time is in the string and incorrectly formatted
25
29
  if current_adapter?(:SQLServerAdapter)
26
30
  assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
@@ -49,17 +53,23 @@ class Test::Unit::TestCase #:nodoc:
49
53
  end
50
54
  end
51
55
 
52
- def current_adapter?(type)
53
- ActiveRecord::ConnectionAdapters.const_defined?(type) &&
54
- ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
56
+ def current_adapter?(*types)
57
+ types.any? do |type|
58
+ ActiveRecord::ConnectionAdapters.const_defined?(type) &&
59
+ ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
60
+ end
55
61
  end
56
62
 
57
63
  ActiveRecord::Base.connection.class.class_eval do
58
64
  cattr_accessor :query_count
65
+
66
+ # Array of regexes of queries that are not counted against query_count
67
+ @@ignore_list = [/^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/]
68
+
59
69
  alias_method :execute_without_query_counting, :execute
60
- def execute_with_query_counting(sql, name = nil)
61
- self.query_count += 1
62
- execute_without_query_counting(sql, name)
70
+ def execute_with_query_counting(sql, name = nil, &block)
71
+ self.query_count += 1 unless @@ignore_list.any? { |r| sql =~ r }
72
+ execute_without_query_counting(sql, name, &block)
63
73
  end
64
74
  end
65
75
 
@@ -17,11 +17,11 @@ class ActiveSchemaTest < Test::Unit::TestCase
17
17
  end
18
18
 
19
19
  def test_add_column
20
- assert_equal "ALTER TABLE people ADD last_name varchar(255)", add_column(:people, :last_name, :string)
20
+ assert_equal "ALTER TABLE people ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
21
21
  end
22
22
 
23
23
  def test_add_column_with_limit
24
- assert_equal "ALTER TABLE people ADD key varchar(32)", add_column(:people, :key, :string, :limit => 32)
24
+ assert_equal "ALTER TABLE people ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
25
25
  end
26
26
 
27
27
  private
@@ -66,19 +66,21 @@ class AdapterTest < Test::Unit::TestCase
66
66
  if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
67
67
  require 'fixtures/movie'
68
68
  require 'fixtures/subscriber'
69
+
69
70
  def test_reset_empty_table_with_custom_pk
70
71
  Movie.delete_all
71
72
  Movie.connection.reset_pk_sequence! 'movies'
72
73
  assert_equal 1, Movie.create(:name => 'fight club').id
73
74
  end
74
75
 
75
- def test_reset_table_with_non_integer_pk
76
- Subscriber.delete_all
77
- Subscriber.connection.reset_pk_sequence! 'subscribers'
78
-
79
- sub = Subscriber.new(:name => 'robert drake')
80
- sub.id = 'bob drake'
81
- assert_nothing_raised { sub.save! }
76
+ if ActiveRecord::Base.connection.adapter_name != "FrontBase"
77
+ def test_reset_table_with_non_integer_pk
78
+ Subscriber.delete_all
79
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
80
+ sub = Subscriber.new(:name => 'robert drake')
81
+ sub.id = 'bob drake'
82
+ assert_nothing_raised { sub.save! }
83
+ end
82
84
  end
83
85
  end
84
86
 
@@ -0,0 +1,81 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/default'
3
+ require 'fixtures/post'
4
+ require 'fixtures/task'
5
+
6
+ class SqlServerAdapterTest < Test::Unit::TestCase
7
+ fixtures :posts, :tasks
8
+
9
+ def setup
10
+ @connection = ActiveRecord::Base.connection
11
+ end
12
+
13
+ def teardown
14
+ @connection.execute("SET LANGUAGE us_english")
15
+ end
16
+
17
+ # SQL Server 2000 has a bug where some unambiguous date formats are not
18
+ # correctly identified if the session language is set to german
19
+ def test_date_insertion_when_language_is_german
20
+ @connection.execute("SET LANGUAGE deutsch")
21
+
22
+ assert_nothing_raised do
23
+ Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
24
+ end
25
+ end
26
+
27
+ def test_execute_without_block_closes_statement
28
+ assert_all_statements_used_are_closed do
29
+ @connection.execute("SELECT 1")
30
+ end
31
+ end
32
+
33
+ def test_execute_with_block_closes_statement
34
+ assert_all_statements_used_are_closed do
35
+ @connection.execute("SELECT 1") do |sth|
36
+ assert !sth.finished?, "Statement should still be alive within block"
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_insert_with_identity_closes_statement
42
+ assert_all_statements_used_are_closed do
43
+ @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
44
+ end
45
+ end
46
+
47
+ def test_insert_without_identity_closes_statement
48
+ assert_all_statements_used_are_closed do
49
+ @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
50
+ end
51
+ end
52
+
53
+ def test_active_closes_statement
54
+ assert_all_statements_used_are_closed do
55
+ @connection.active?
56
+ end
57
+ end
58
+
59
+ def assert_all_statements_used_are_closed(&block)
60
+ existing_handles = []
61
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
62
+ GC.disable
63
+
64
+ yield
65
+
66
+ used_handles = []
67
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
68
+
69
+ assert_block "No statements were used within given block" do
70
+ used_handles.size > 0
71
+ end
72
+
73
+ ObjectSpace.each_object(DBI::StatementHandle) do |handle|
74
+ assert_block "Statement should have been closed within given block" do
75
+ handle.finished?
76
+ end
77
+ end
78
+ ensure
79
+ GC.enable
80
+ end
81
+ end
@@ -63,4 +63,33 @@ class AggregationsTest < Test::Unit::TestCase
63
63
  def test_gps_inequality
64
64
  assert GpsLocation.new('39x110') != GpsLocation.new('39x111')
65
65
  end
66
+
67
+ def test_allow_nil_gps_is_nil
68
+ assert_equal nil, customers(:zaphod).gps_location
69
+ end
70
+
71
+ def test_allow_nil_gps_set_to_nil
72
+ customers(:david).gps_location = nil
73
+ customers(:david).save
74
+ customers(:david).reload
75
+ assert_equal nil, customers(:david).gps_location
76
+ end
77
+
78
+ def test_allow_nil_set_address_attributes_to_nil
79
+ customers(:zaphod).address = nil
80
+ assert_equal nil, customers(:zaphod).attributes[:address_street]
81
+ assert_equal nil, customers(:zaphod).attributes[:address_city]
82
+ assert_equal nil, customers(:zaphod).attributes[:address_country]
83
+ end
84
+
85
+ def test_allow_nil_address_set_to_nil
86
+ customers(:zaphod).address = nil
87
+ customers(:zaphod).save
88
+ customers(:zaphod).reload
89
+ assert_equal nil, customers(:zaphod).address
90
+ end
91
+
92
+ def test_nil_raises_error_when_allow_nil_is_false
93
+ assert_raises(NoMethodError) { customers(:david).balance = nil }
94
+ end
66
95
  end
@@ -111,14 +111,16 @@ class AssociationCallbacksTest < Test::Unit::TestCase
111
111
  end
112
112
 
113
113
  def test_push_with_attributes
114
- david = developers(:david)
115
- activerecord = projects(:active_record)
116
- assert activerecord.developers_log.empty?
117
- activerecord.developers_with_callbacks.push_with_attributes(david, {})
118
- assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], activerecord.developers_log
119
- activerecord.developers_with_callbacks.push_with_attributes(david, {})
120
- assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
121
- "after_adding#{david.id}"], activerecord.developers_log
114
+ assert_deprecated 'push_with_attributes' do
115
+ david = developers(:david)
116
+ activerecord = projects(:active_record)
117
+ assert activerecord.developers_log.empty?
118
+ activerecord.developers_with_callbacks.push_with_attributes(david, {})
119
+ assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], activerecord.developers_log
120
+ activerecord.developers_with_callbacks.push_with_attributes(david, {})
121
+ assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
122
+ "after_adding#{david.id}"], activerecord.developers_log
123
+ end
122
124
  end
123
125
  end
124
126
 
@@ -11,7 +11,7 @@ require 'fixtures/topic'
11
11
  require 'fixtures/reply'
12
12
 
13
13
  class CascadedEagerLoadingTest < Test::Unit::TestCase
14
- fixtures :authors, :mixins, :companies, :posts, :categorizations, :topics
14
+ fixtures :authors, :mixins, :companies, :posts, :topics
15
15
 
16
16
  def test_eager_association_loading_with_cascaded_two_levels
17
17
  authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
@@ -28,7 +28,7 @@ class CascadedEagerLoadingTest < Test::Unit::TestCase
28
28
  assert_equal 1, authors[1].posts.size
29
29
  assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
30
30
  assert_equal 1, authors[0].categorizations.size
31
- assert_equal 1, authors[1].categorizations.size
31
+ assert_equal 2, authors[1].categorizations.size
32
32
  end
33
33
 
34
34
  def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
@@ -94,7 +94,7 @@ class CascadedEagerLoadingTest < Test::Unit::TestCase
94
94
  author.posts.first.very_special_comment
95
95
  end
96
96
  end
97
-
97
+
98
98
  def test_eager_association_loading_of_stis_with_multiple_references
99
99
  authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
100
100
  assert_equal [authors(:david)], authors
@@ -103,4 +103,36 @@ class CascadedEagerLoadingTest < Test::Unit::TestCase
103
103
  authors.first.posts.first.special_comments.first.post.very_special_comment
104
104
  end
105
105
  end
106
+
107
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
108
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:children=>{:children=>:children}}, :order => 'mixins.id')
109
+ assert_equal mixins(:recursively_cascaded_tree_4), assert_no_queries { root_node.children.first.children.first.children.first }
110
+ end
111
+
112
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
113
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:first_child=>{:first_child=>:first_child}}, :order => 'mixins.id')
114
+ assert_equal mixins(:recursively_cascaded_tree_4), assert_no_queries { root_node.first_child.first_child.first_child }
115
+ end
116
+
117
+ def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
118
+ leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:parent=>{:parent=>:parent}}, :order => 'mixins.id DESC')
119
+ assert_equal mixins(:recursively_cascaded_tree_1), assert_no_queries { leaf_node.parent.parent.parent }
120
+ end
121
+ end
122
+
123
+
124
+ require 'fixtures/vertex'
125
+ require 'fixtures/edge'
126
+ class CascadedEagerLoadingTest < Test::Unit::TestCase
127
+ fixtures :edges, :vertices
128
+
129
+ def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
130
+ source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id')
131
+ assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
132
+ end
133
+
134
+ def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
135
+ sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC')
136
+ assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
137
+ end
106
138
  end
@@ -104,7 +104,7 @@ class EagerAssociationTest < Test::Unit::TestCase
104
104
  end
105
105
 
106
106
  def test_eager_with_has_many_through
107
- posts_with_comments = people(:michael).posts.find(:all, :include => :comments )
107
+ posts_with_comments = people(:michael).posts.find(:all, :include => :comments)
108
108
  posts_with_author = people(:michael).posts.find(:all, :include => :author )
109
109
  posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ])
110
110
  assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
@@ -112,6 +112,22 @@ class EagerAssociationTest < Test::Unit::TestCase
112
112
  assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
113
113
  end
114
114
 
115
+ def test_eager_with_has_many_through_an_sti_join_model
116
+ author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
117
+ assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
118
+ end
119
+
120
+ def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
121
+ author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
122
+ assert_equal [], author.special_nonexistant_post_comments
123
+ end
124
+
125
+ def test_eager_with_has_many_through_join_model_with_conditions
126
+ assert_equal Author.find(:first, :include => :hello_post_comments,
127
+ :order => 'authors.id').hello_post_comments.sort_by(&:id),
128
+ Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
129
+ end
130
+
115
131
  def test_eager_with_has_many_and_limit
116
132
  posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
117
133
  assert_equal 2, posts.size
@@ -274,6 +290,16 @@ class EagerAssociationTest < Test::Unit::TestCase
274
290
  def find_all_ordered(className, include=nil)
275
291
  className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
276
292
  end
293
+
294
+ def test_limited_eager_with_order
295
+ assert_equal [posts(:thinking), posts(:sti_comments)], Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1)
296
+ assert_equal [posts(:sti_post_and_comments), posts(:sti_comments)], Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1)
297
+ end
298
+
299
+ def test_limited_eager_with_multiple_order_columns
300
+ assert_equal [posts(:thinking), posts(:sti_comments)], Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1)
301
+ assert_equal [posts(:sti_post_and_comments), posts(:sti_comments)], Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1)
302
+ end
277
303
 
278
304
  def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
279
305
  # Eager includes of has many and habtm associations aren't necessarily sorted in the same way
@@ -283,7 +309,7 @@ class EagerAssociationTest < Test::Unit::TestCase
283
309
  end
284
310
  # Test regular association, association with conditions, association with
285
311
  # STI, and association with conditions assured not to be true
286
- post_types = [:posts, :hello_posts, :special_posts, :nonexistent_posts]
312
+ post_types = [:posts, :other_posts, :special_posts]
287
313
  # test both has_many and has_and_belongs_to_many
288
314
  [Author, Category].each do |className|
289
315
  d1 = find_all_ordered(className)
@@ -356,4 +382,12 @@ class EagerAssociationTest < Test::Unit::TestCase
356
382
  assert_equal 2, one.comments.size
357
383
  assert_equal 2, one.categories.size
358
384
  end
385
+
386
+ def test_count_with_include
387
+ if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
388
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
389
+ else
390
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15")
391
+ end
392
+ end
359
393
  end
@@ -18,6 +18,11 @@ class AssociationsExtensionsTest < Test::Unit::TestCase
18
18
  def test_named_extension_on_habtm
19
19
  assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
20
20
  end
21
+
22
+ def test_named_two_extensions_on_habtm
23
+ assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent
24
+ assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent
25
+ end
21
26
 
22
27
  def test_marshalling_extensions
23
28
  david = developers(:david)
@@ -12,21 +12,26 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
12
12
  fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites
13
13
 
14
14
  def test_has_many
15
- assert_equal categories(:general), authors(:david).categories.first
15
+ assert authors(:david).categories.include?(categories(:general))
16
16
  end
17
-
17
+
18
18
  def test_has_many_inherited
19
- assert_equal categories(:sti_test), authors(:mary).categories.first
19
+ assert authors(:mary).categories.include?(categories(:sti_test))
20
20
  end
21
21
 
22
22
  def test_inherited_has_many
23
- assert_equal authors(:mary), categories(:sti_test).authors.first
23
+ assert categories(:sti_test).authors.include?(authors(:mary))
24
24
  end
25
-
25
+
26
+ def test_has_many_uniq_through_join_model
27
+ assert_equal 2, authors(:mary).categorized_posts.size
28
+ assert_equal 1, authors(:mary).unique_categorized_posts.size
29
+ end
30
+
26
31
  def test_polymorphic_has_many
27
- assert_equal taggings(:welcome_general), posts(:welcome).taggings.first
32
+ assert posts(:welcome).taggings.include?(taggings(:welcome_general))
28
33
  end
29
-
34
+
30
35
  def test_polymorphic_has_one
31
36
  assert_equal taggings(:welcome_general), posts(:welcome).tagging
32
37
  end
@@ -132,6 +137,13 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
132
137
  assert_equal "Post", tagging.taggable_type
133
138
  assert_equal old_count+1, posts(:welcome).taggings.count
134
139
  end
140
+
141
+ def test_create_bang_polymorphic_with_has_many_scope
142
+ old_count = posts(:welcome).taggings.count
143
+ tagging = posts(:welcome).taggings.create!(:tag => tags(:misc))
144
+ assert_equal "Post", tagging.taggable_type
145
+ assert_equal old_count+1, posts(:welcome).taggings.count
146
+ end
135
147
 
136
148
  def test_create_polymorphic_has_one_with_scope
137
149
  old_count = Tagging.count
@@ -241,6 +253,10 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
241
253
  def test_has_many_find_first
242
254
  assert_equal categories(:general), authors(:david).categories.find(:first)
243
255
  end
256
+
257
+ def test_has_many_with_hash_conditions
258
+ assert_equal categories(:general), authors(:david).categories_like_general.find(:first)
259
+ end
244
260
 
245
261
  def test_has_many_find_conditions
246
262
  assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
@@ -296,7 +312,8 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
296
312
  end
297
313
 
298
314
  def test_has_many_through_has_many_find_conditions
299
- assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, :conditions => "comments.type='SpecialComment'", :order => 'comments.id')
315
+ options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
316
+ assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options)
300
317
  end
301
318
 
302
319
  def test_has_many_through_has_many_find_by_id
@@ -334,6 +351,13 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
334
351
  assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
335
352
  end
336
353
  end
354
+
355
+ def test_eager_load_has_many_through_has_many_with_conditions
356
+ post = Post.find(:first, :include => :invalid_tags)
357
+ assert_no_queries do
358
+ post.invalid_tags
359
+ end
360
+ end
337
361
 
338
362
  def test_eager_belongs_to_and_has_one_not_singularized
339
363
  assert_nothing_raised do
@@ -357,6 +381,92 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
357
381
  assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
358
382
  end
359
383
 
384
+ def test_raise_error_when_adding_new_record_to_has_many_through
385
+ assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags << tags(:general).clone }
386
+ assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).clone.tags << tags(:general) }
387
+ assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.build }
388
+ end
389
+
390
+ def test_create_associate_when_adding_to_has_many_through
391
+ count = posts(:thinking).tags.count
392
+ push = Tag.create!(:name => 'pushme')
393
+ post_thinking = posts(:thinking)
394
+ assert_nothing_raised { post_thinking.tags << push }
395
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
396
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
397
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
398
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
399
+ assert_equal(count + 1, post_thinking.tags.size)
400
+ assert_equal(count + 1, post_thinking.tags(true).size)
401
+
402
+ assert_nothing_raised { post_thinking.tags.create!(:name => 'foo') }
403
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
404
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
405
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
406
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
407
+ assert_equal(count + 2, post_thinking.tags.size)
408
+ assert_equal(count + 2, post_thinking.tags(true).size)
409
+
410
+ assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) }
411
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
412
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
413
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
414
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
415
+ assert_equal(count + 4, post_thinking.tags.size)
416
+ assert_equal(count + 4, post_thinking.tags(true).size)
417
+ end
418
+
419
+ def test_adding_junk_to_has_many_through_should_raise_type_mismatch
420
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" }
421
+ end
422
+
423
+ def test_adding_to_has_many_through_should_return_self
424
+ tags = posts(:thinking).tags
425
+ assert_equal tags, posts(:thinking).tags.push(tags(:general))
426
+ end
427
+
428
+ def test_delete_associate_when_deleting_from_has_many_through
429
+ count = posts(:thinking).tags.count
430
+ tags_before = posts(:thinking).tags
431
+ tag = Tag.create!(:name => 'doomed')
432
+ post_thinking = posts(:thinking)
433
+ post_thinking.tags << tag
434
+ assert_equal(count + 1, post_thinking.tags(true).size)
435
+
436
+ assert_nothing_raised { post_thinking.tags.delete(tag) }
437
+ assert_equal(count, post_thinking.tags.size)
438
+ assert_equal(count, post_thinking.tags(true).size)
439
+ assert_equal(tags_before.sort, post_thinking.tags.sort)
440
+ end
441
+
442
+ def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags
443
+ count = posts(:thinking).tags.count
444
+ tags_before = posts(:thinking).tags
445
+ doomed = Tag.create!(:name => 'doomed')
446
+ doomed2 = Tag.create!(:name => 'doomed2')
447
+ quaked = Tag.create!(:name => 'quaked')
448
+ post_thinking = posts(:thinking)
449
+ post_thinking.tags << doomed << doomed2
450
+ assert_equal(count + 2, post_thinking.tags(true).size)
451
+
452
+ assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }
453
+ assert_equal(count, post_thinking.tags.size)
454
+ assert_equal(count, post_thinking.tags(true).size)
455
+ assert_equal(tags_before.sort, post_thinking.tags.sort)
456
+ end
457
+
458
+ def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
459
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
460
+ end
461
+
462
+ def test_has_many_through_sum_uses_calculations
463
+ assert_nothing_raised { authors(:david).comments.sum(:post_id) }
464
+ end
465
+
466
+ def test_has_many_through_has_many_with_sti
467
+ assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments
468
+ end
469
+
360
470
  private
361
471
  # create dynamic Post models to allow different dependency options
362
472
  def find_post_with_dependency(post_id, association, association_name, dependency)