activerecord 1.15.6 → 2.0.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -2,7 +2,19 @@ require 'abstract_unit'
2
2
  require 'fixtures/topic'
3
3
  require 'fixtures/task'
4
4
 
5
- class EmptyDateTimeTest < Test::Unit::TestCase
5
+ class DateTimeTest < Test::Unit::TestCase
6
+ def test_saves_both_date_and_time
7
+ time_values = [1807, 2, 10, 15, 30, 45]
8
+ now = DateTime.civil(*time_values)
9
+
10
+ task = Task.new
11
+ task.starting = now
12
+ task.save!
13
+
14
+ # check against Time.local_time, since some platforms will return a Time instead of a DateTime
15
+ assert_equal Time.local_time(*time_values), Task.find(task.id).starting
16
+ end
17
+
6
18
  def test_assign_empty_date_time
7
19
  task = Task.new
8
20
  task.starting = ''
@@ -31,7 +31,8 @@ class DefaultTest < Test::Unit::TestCase
31
31
 
32
32
  assert_equal 0, klass.columns_hash['zero'].default
33
33
  assert !klass.columns_hash['zero'].null
34
- assert_equal nil, klass.columns_hash['omit'].default
34
+ # 0 in MySQL 4, nil in 5.
35
+ assert [0, nil].include?(klass.columns_hash['omit'].default)
35
36
  assert !klass.columns_hash['omit'].null
36
37
 
37
38
  assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
@@ -57,4 +58,10 @@ class DefaultTest < Test::Unit::TestCase
57
58
  assert_equal BigDecimal.new("2.78"), default.decimal_number
58
59
  end
59
60
  end
61
+
62
+ if current_adapter?(:PostgreSQLAdapter)
63
+ def test_multiline_default_text
64
+ assert_equal "--- []\n\n", Default.columns_hash['multiline_default'].default
65
+ end
66
+ end
60
67
  end
@@ -1,90 +1,19 @@
1
1
  require 'abstract_unit'
2
- require 'fixtures/company'
3
- require 'fixtures/topic'
4
- require 'fixtures/reply'
5
2
  require 'fixtures/entrant'
6
- require 'fixtures/developer'
7
3
 
8
4
  class DeprecatedFinderTest < Test::Unit::TestCase
9
- fixtures :companies, :topics, :entrants, :developers
5
+ fixtures :entrants
10
6
 
11
- def test_find_all_with_limit
12
- entrants = assert_deprecated { Entrant.find_all nil, "id ASC", 2 }
13
- assert_equal 2, entrants.size
14
- assert_equal entrants(:first), entrants.first
7
+ def test_deprecated_find_all_was_removed
8
+ assert_raise(NoMethodError) { Entrant.find_all }
15
9
  end
16
10
 
17
- def test_find_all_with_prepared_limit_and_offset
18
- entrants = assert_deprecated { Entrant.find_all nil, "id ASC", [2, 1] }
19
- assert_equal 2, entrants.size
20
- assert_equal entrants(:second), entrants.first
11
+ def test_deprecated_find_first_was_removed
12
+ assert_raise(NoMethodError) { Entrant.find_first }
21
13
  end
22
14
 
23
- def test_find_first
24
- first = assert_deprecated { Topic.find_first "title = 'The First Topic'" }
25
- assert_equal topics(:first), first
26
- end
27
-
28
- def test_find_first_failing
29
- first = assert_deprecated { Topic.find_first "title = 'The First Topic!'" }
30
- assert_nil first
31
- end
32
-
33
- def test_deprecated_find_on_conditions
34
- assert_deprecated 'find_on_conditions' do
35
- assert Topic.find_on_conditions(1, ["approved = ?", false])
36
- assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, ["approved = ?", true]) }
37
- end
38
- end
39
-
40
- def test_condition_interpolation
41
- assert_deprecated do
42
- assert_kind_of Firm, Company.find_first(["name = '%s'", "37signals"])
43
- assert_nil Company.find_first(["name = '%s'", "37signals!"])
44
- assert_nil Company.find_first(["name = '%s'", "37signals!' OR 1=1"])
45
- assert_kind_of Time, Topic.find_first(["id = %d", 1]).written_on
46
- end
47
- end
48
-
49
- def test_bind_variables
50
- assert_deprecated do
51
- assert_kind_of Firm, Company.find_first(["name = ?", "37signals"])
52
- assert_nil Company.find_first(["name = ?", "37signals!"])
53
- assert_nil Company.find_first(["name = ?", "37signals!' OR 1=1"])
54
- assert_kind_of Time, Topic.find_first(["id = ?", 1]).written_on
55
- assert_raises(ActiveRecord::PreparedStatementInvalid) {
56
- Company.find_first(["id=? AND name = ?", 2])
57
- }
58
- assert_raises(ActiveRecord::PreparedStatementInvalid) {
59
- Company.find_first(["id=?", 2, 3, 4])
60
- }
61
- end
62
- end
63
-
64
- def test_bind_variables_with_quotes
65
- Company.create("name" => "37signals' go'es agains")
66
- assert_deprecated do
67
- assert_not_nil Company.find_first(["name = ?", "37signals' go'es agains"])
68
- end
69
- end
70
-
71
- def test_named_bind_variables_with_quotes
72
- Company.create("name" => "37signals' go'es agains")
73
- assert_deprecated do
74
- assert_not_nil Company.find_first(["name = :name", {:name => "37signals' go'es agains"}])
75
- end
76
- end
77
-
78
- def test_named_bind_variables
79
- assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
80
- assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
81
-
82
- assert_deprecated do
83
- assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
84
- assert_nil Company.find_first(["name = :name", { :name => "37signals!" }])
85
- assert_nil Company.find_first(["name = :name", { :name => "37signals!' OR 1=1" }])
86
- assert_kind_of Time, Topic.find_first(["id = :id", { :id => 1 }]).written_on
87
- end
15
+ def test_deprecated_find_on_conditions_was_removed
16
+ assert_raise(NoMethodError) { Entrant.find_on_conditions }
88
17
  end
89
18
 
90
19
  def test_count
@@ -98,54 +27,4 @@ class DeprecatedFinderTest < Test::Unit::TestCase
98
27
  assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
99
28
  assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
100
29
  end
101
-
102
- def test_find_all_with_limit
103
- assert_deprecated do
104
- first_five_developers = Developer.find_all nil, 'id ASC', 5
105
- assert_equal 5, first_five_developers.length
106
- assert_equal 'David', first_five_developers.first.name
107
- assert_equal 'fixture_5', first_five_developers.last.name
108
-
109
- no_developers = Developer.find_all nil, 'id ASC', 0
110
- assert_equal 0, no_developers.length
111
-
112
- assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5])
113
- assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0])
114
- end
115
- end
116
-
117
- def test_find_all_with_limit_and_offset
118
- assert_deprecated do
119
- first_three_developers = Developer.find_all nil, 'id ASC', [3, 0]
120
- second_three_developers = Developer.find_all nil, 'id ASC', [3, 3]
121
- last_two_developers = Developer.find_all nil, 'id ASC', [2, 8]
122
-
123
- assert_equal 3, first_three_developers.length
124
- assert_equal 3, second_three_developers.length
125
- assert_equal 2, last_two_developers.length
126
-
127
- assert_equal 'David', first_three_developers.first.name
128
- assert_equal 'fixture_4', second_three_developers.first.name
129
- assert_equal 'fixture_9', last_two_developers.first.name
130
- end
131
- end
132
-
133
- def test_find_all_by_one_attribute_with_options
134
- assert_not_deprecated do
135
- topics = Topic.find_all_by_content("Have a nice day", "id DESC")
136
- assert topics(:first), topics.last
137
-
138
- topics = Topic.find_all_by_content("Have a nice day", "id DESC")
139
- assert topics(:first), topics.first
140
- end
141
- end
142
-
143
- protected
144
- def bind(statement, *vars)
145
- if vars.first.is_a?(Hash)
146
- ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
147
- else
148
- ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
149
- end
150
- end
151
30
  end
@@ -1,4 +1,6 @@
1
1
  require 'abstract_unit'
2
+ require 'fixtures/author'
3
+ require 'fixtures/comment'
2
4
  require 'fixtures/company'
3
5
  require 'fixtures/topic'
4
6
  require 'fixtures/reply'
@@ -7,18 +9,18 @@ require 'fixtures/developer'
7
9
  require 'fixtures/post'
8
10
 
9
11
  class FinderTest < Test::Unit::TestCase
10
- fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :accounts
12
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors
11
13
 
12
14
  def test_find
13
15
  assert_equal(topics(:first).title, Topic.find(1).title)
14
16
  end
15
-
17
+
16
18
  # find should handle strings that come from URLs
17
19
  # (example: Category.find(params[:id]))
18
20
  def test_find_with_string
19
21
  assert_equal(Topic.find(1).title,Topic.find("1").title)
20
22
  end
21
-
23
+
22
24
  def test_exists
23
25
  assert Topic.exists?(1)
24
26
  assert Topic.exists?("1")
@@ -29,15 +31,27 @@ class FinderTest < Test::Unit::TestCase
29
31
  assert !Topic.exists?("foo")
30
32
  assert_raise(NoMethodError) { Topic.exists?([1,2]) }
31
33
  end
32
-
34
+
33
35
  def test_find_by_array_of_one_id
34
36
  assert_kind_of(Array, Topic.find([ 1 ]))
35
37
  assert_equal(1, Topic.find([ 1 ]).length)
36
38
  end
37
-
39
+
38
40
  def test_find_by_ids
39
- assert_equal(2, Topic.find(1, 2).length)
40
- assert_equal(topics(:second).title, Topic.find([ 2 ]).first.title)
41
+ assert_equal 2, Topic.find(1, 2).size
42
+ assert_equal topics(:second).title, Topic.find([2]).first.title
43
+ end
44
+
45
+ def test_find_by_ids_with_limit_and_offset
46
+ assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
47
+ assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
48
+
49
+ # Also test an edge case: If you have 11 results, and you set a
50
+ # limit of 3 and offset of 9, then you should find that there
51
+ # will be only 2 results, regardless of the limit.
52
+ devs = Developer.find :all
53
+ last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
54
+ assert_equal 2, last_devs.size
41
55
  end
42
56
 
43
57
  def test_find_an_empty_array
@@ -45,14 +59,12 @@ class FinderTest < Test::Unit::TestCase
45
59
  end
46
60
 
47
61
  def test_find_by_ids_missing_one
48
- assert_raises(ActiveRecord::RecordNotFound) {
49
- Topic.find(1, 2, 45)
50
- }
62
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
51
63
  end
52
-
64
+
53
65
  def test_find_all_with_limit
54
66
  entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
55
-
67
+
56
68
  assert_equal(2, entrants.size)
57
69
  assert_equal(entrants(:first).name, entrants.first.name)
58
70
  end
@@ -67,12 +79,12 @@ class FinderTest < Test::Unit::TestCase
67
79
  assert_equal(1, entrants.size)
68
80
  assert_equal(entrants(:third).name, entrants.first.name)
69
81
  end
70
-
82
+
71
83
  def test_find_all_with_limit_and_offset_and_multiple_orderings
72
84
  developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
73
85
  assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
74
86
  end
75
-
87
+
76
88
  def test_find_with_limit_and_condition
77
89
  developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
78
90
  assert_equal(1, developers.size)
@@ -81,28 +93,28 @@ class FinderTest < Test::Unit::TestCase
81
93
 
82
94
  def test_find_with_entire_select_statement
83
95
  topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
84
-
96
+
85
97
  assert_equal(1, topics.size)
86
98
  assert_equal(topics(:second).title, topics.first.title)
87
99
  end
88
-
100
+
89
101
  def test_find_with_prepared_select_statement
90
102
  topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
91
-
103
+
92
104
  assert_equal(1, topics.size)
93
105
  assert_equal(topics(:second).title, topics.first.title)
94
106
  end
95
-
107
+
96
108
  def test_find_by_sql_with_sti_on_joined_table
97
109
  accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id")
98
110
  assert_equal [Account], accounts.collect(&:class).uniq
99
111
  end
100
-
112
+
101
113
  def test_find_first
102
114
  first = Topic.find(:first, :conditions => "title = 'The First Topic'")
103
115
  assert_equal(topics(:first).title, first.title)
104
116
  end
105
-
117
+
106
118
  def test_find_first_failing
107
119
  first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
108
120
  assert_nil(first)
@@ -112,60 +124,89 @@ class FinderTest < Test::Unit::TestCase
112
124
  assert_raises(ActiveRecord::RecordNotFound) {
113
125
  Topic.find(1).parent
114
126
  }
115
-
127
+
116
128
  Topic.find(2).topic
117
129
  end
118
-
130
+
119
131
  def test_find_only_some_columns
120
132
  topic = Topic.find(1, :select => "author_name")
121
- assert_raises(NoMethodError) { topic.title }
133
+ assert_raises(ActiveRecord::MissingAttributeError) {topic.title}
122
134
  assert_equal "David", topic.author_name
123
135
  assert !topic.attribute_present?("title")
124
- assert !topic.respond_to?("title")
136
+ #assert !topic.respond_to?("title")
125
137
  assert topic.attribute_present?("author_name")
126
138
  assert topic.respond_to?("author_name")
127
139
  end
140
+
141
+ def test_find_on_blank_conditions
142
+ [nil, " ", [], {}].each do |blank|
143
+ assert_nothing_raised { Topic.find(:first, :conditions => blank) }
144
+ end
145
+ end
146
+
147
+ def test_find_on_blank_bind_conditions
148
+ [ [""], ["",{}] ].each do |blank|
149
+ assert_nothing_raised { Topic.find(:first, :conditions => blank) }
150
+ end
151
+ end
128
152
 
129
153
  def test_find_on_array_conditions
130
154
  assert Topic.find(1, :conditions => ["approved = ?", false])
131
155
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
132
156
  end
133
-
157
+
134
158
  def test_find_on_hash_conditions
135
159
  assert Topic.find(1, :conditions => { :approved => false })
136
160
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
137
161
  end
138
-
162
+
163
+ def test_find_on_hash_conditions_with_explicit_table_name
164
+ assert Topic.find(1, :conditions => { 'topics.approved' => false })
165
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
166
+ end
167
+
168
+ def test_find_on_association_proxy_conditions
169
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
170
+ end
171
+
139
172
  def test_find_on_hash_conditions_with_range
140
173
  assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
141
174
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
142
175
  end
143
-
176
+
144
177
  def test_find_on_hash_conditions_with_multiple_ranges
145
178
  assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
146
179
  assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
147
180
  end
148
-
181
+
149
182
  def test_find_on_multiple_hash_conditions
150
183
  assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
151
184
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
152
185
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
153
186
  assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
154
187
  end
155
-
188
+
189
+
190
+ def test_condition_interpolation
191
+ assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
192
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
193
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
194
+ assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
195
+ end
196
+
156
197
  def test_condition_array_interpolation
157
198
  assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
158
199
  assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
159
200
  assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
160
201
  assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
161
202
  end
162
-
203
+
163
204
  def test_condition_hash_interpolation
164
205
  assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
165
206
  assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
166
207
  assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
167
208
  end
168
-
209
+
169
210
  def test_hash_condition_find_malformed
170
211
  assert_raises(ActiveRecord::StatementInvalid) {
171
212
  Company.find(:first, :conditions => { :id => 2, :dhh => true })
@@ -201,7 +242,7 @@ class FinderTest < Test::Unit::TestCase
201
242
  Company.find(:first, :conditions => ["id=?", 2, 3, 4])
202
243
  }
203
244
  end
204
-
245
+
205
246
  def test_bind_variables_with_quotes
206
247
  Company.create("name" => "37signals' go'es agains")
207
248
  assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
@@ -215,16 +256,16 @@ class FinderTest < Test::Unit::TestCase
215
256
  def test_bind_arity
216
257
  assert_nothing_raised { bind '' }
217
258
  assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
218
-
259
+
219
260
  assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
220
261
  assert_nothing_raised { bind '?', 1 }
221
262
  assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
222
263
  end
223
-
264
+
224
265
  def test_named_bind_variables
225
266
  assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
226
267
  assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
227
-
268
+
228
269
  assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
229
270
  assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
230
271
  assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
@@ -232,18 +273,20 @@ class FinderTest < Test::Unit::TestCase
232
273
  end
233
274
 
234
275
  def test_bind_enumerable
276
+ quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
277
+
235
278
  assert_equal '1,2,3', bind('?', [1, 2, 3])
236
- assert_equal %('a','b','c'), bind('?', %w(a b c))
279
+ assert_equal quoted_abc, bind('?', %w(a b c))
237
280
 
238
281
  assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
239
- assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # '
282
+ assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
240
283
 
241
284
  require 'set'
242
285
  assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
243
- assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c)))
286
+ assert_equal quoted_abc, bind('?', Set.new(%w(a b c)))
244
287
 
245
288
  assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
246
- assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # '
289
+ assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # '
247
290
  end
248
291
 
249
292
  def test_bind_empty_enumerable
@@ -254,7 +297,7 @@ class FinderTest < Test::Unit::TestCase
254
297
  end
255
298
 
256
299
  def test_bind_string
257
- assert_equal "''", bind('?', '')
300
+ assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
258
301
  end
259
302
 
260
303
  def test_bind_record
@@ -266,8 +309,8 @@ class FinderTest < Test::Unit::TestCase
266
309
  end
267
310
 
268
311
  def test_string_sanitation
269
- assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
270
- assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
312
+ assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
313
+ assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
271
314
  end
272
315
 
273
316
  def test_count
@@ -286,6 +329,21 @@ class FinderTest < Test::Unit::TestCase
286
329
  assert_equal topics(:first), Topic.find_by_title("The First Topic")
287
330
  assert_nil Topic.find_by_title("The First Topic!")
288
331
  end
332
+
333
+ def test_find_by_one_attribute_caches_dynamic_finder
334
+ # ensure this test can run independently of order
335
+ class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
336
+ assert !Topic.respond_to?(:find_by_title)
337
+ t = Topic.find_by_title("The First Topic")
338
+ assert Topic.respond_to?(:find_by_title)
339
+ end
340
+
341
+ def test_dynamic_finder_returns_same_results_after_caching
342
+ # ensure this test can run independently of order
343
+ class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
344
+ t = Topic.find_by_title("The First Topic")
345
+ assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached
346
+ end
289
347
 
290
348
  def test_find_by_one_attribute_with_order_option
291
349
  assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
@@ -296,14 +354,29 @@ class FinderTest < Test::Unit::TestCase
296
354
  assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
297
355
  end
298
356
 
357
+ def test_dynamic_finder_on_one_attribute_with_conditions_caches_method
358
+ # ensure this test can run independently of order
359
+ class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
360
+ assert !Account.respond_to?(:find_by_credit_limit)
361
+ a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
362
+ assert Account.respond_to?(:find_by_credit_limit)
363
+ end
364
+
365
+ def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
366
+ # ensure this test can run independently of order
367
+ class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
368
+ a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
369
+ assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
370
+ end
371
+
299
372
  def test_find_by_one_attribute_with_several_options
300
373
  assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
301
374
  end
302
-
375
+
303
376
  def test_find_by_one_missing_attribute
304
377
  assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
305
378
  end
306
-
379
+
307
380
  def test_find_by_invalid_method_syntax
308
381
  assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
309
382
  assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") }
@@ -323,7 +396,7 @@ class FinderTest < Test::Unit::TestCase
323
396
 
324
397
  assert_equal [], Topic.find_all_by_title("The First Topic!!")
325
398
  end
326
-
399
+
327
400
  def test_find_all_by_one_attribute_with_options
328
401
  topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
329
402
  assert topics(:first), topics.last
@@ -345,19 +418,19 @@ class FinderTest < Test::Unit::TestCase
345
418
  assert_equal 1, topics.size
346
419
  assert topics.include?(topics(:second))
347
420
  end
348
-
421
+
349
422
  def test_find_by_nil_attribute
350
423
  topic = Topic.find_by_last_read nil
351
424
  assert_not_nil topic
352
425
  assert_nil topic.last_read
353
426
  end
354
-
427
+
355
428
  def test_find_all_by_nil_attribute
356
429
  topics = Topic.find_all_by_last_read nil
357
430
  assert_equal 1, topics.size
358
431
  assert_nil topics[0].last_read
359
432
  end
360
-
433
+
361
434
  def test_find_by_nil_and_not_nil_attributes
362
435
  topic = Topic.find_by_last_read_and_author_name nil, "Mary"
363
436
  assert_equal "Mary", topic.author_name
@@ -384,13 +457,47 @@ class FinderTest < Test::Unit::TestCase
384
457
  assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
385
458
  assert !another.new_record?
386
459
  end
387
-
460
+
461
+ def test_find_or_create_from_one_attribute_and_hash
462
+ number_of_companies = Company.count
463
+ sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
464
+ assert_equal number_of_companies + 1, Company.count
465
+ assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
466
+ assert !sig38.new_record?
467
+ assert_equal "38signals", sig38.name
468
+ assert_equal 17, sig38.firm_id
469
+ assert_equal 23, sig38.client_of
470
+ end
471
+
388
472
  def test_find_or_initialize_from_one_attribute
389
473
  sig38 = Company.find_or_initialize_by_name("38signals")
390
474
  assert_equal "38signals", sig38.name
391
475
  assert sig38.new_record?
392
476
  end
393
477
 
478
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
479
+ c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
480
+ assert_equal "Fortune 1000", c.name
481
+ assert_equal 1000, c.rating
482
+ assert c.valid?
483
+ assert c.new_record?
484
+ end
485
+
486
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
487
+ c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
488
+ assert_equal "Fortune 1000", c.name
489
+ assert_equal 1000, c.rating
490
+ assert c.valid?
491
+ assert !c.new_record?
492
+ end
493
+
494
+ def test_dynamic_find_or_initialize_from_one_attribute_caches_method
495
+ class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.respond_to?(:find_or_initialize_by_name)
496
+ assert !Company.respond_to?(:find_or_initialize_by_name)
497
+ sig38 = Company.find_or_initialize_by_name("38signals")
498
+ assert Company.respond_to?(:find_or_initialize_by_name)
499
+ end
500
+
394
501
  def test_find_or_initialize_from_two_attributes
395
502
  another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
396
503
  assert_equal "Another topic", another.title
@@ -398,6 +505,14 @@ class FinderTest < Test::Unit::TestCase
398
505
  assert another.new_record?
399
506
  end
400
507
 
508
+ def test_find_or_initialize_from_one_attribute_and_hash
509
+ sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
510
+ assert_equal "38signals", sig38.name
511
+ assert_equal 17, sig38.firm_id
512
+ assert_equal 23, sig38.client_of
513
+ assert sig38.new_record?
514
+ end
515
+
401
516
  def test_find_with_bad_sql
402
517
  assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
403
518
  end
@@ -407,12 +522,16 @@ class FinderTest < Test::Unit::TestCase
407
522
  assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
408
523
  end
409
524
 
525
+ def test_dynamic_finder_with_invalid_params
526
+ assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
527
+ end
528
+
410
529
  def test_find_all_with_limit
411
530
  first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5
412
531
  assert_equal 5, first_five_developers.length
413
532
  assert_equal 'David', first_five_developers.first.name
414
533
  assert_equal 'fixture_5', first_five_developers.last.name
415
-
534
+
416
535
  no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
417
536
  assert_equal 0, no_developers.length
418
537
  end
@@ -421,11 +540,11 @@ class FinderTest < Test::Unit::TestCase
421
540
  first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
422
541
  second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
423
542
  last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
424
-
543
+
425
544
  assert_equal 3, first_three_developers.length
426
545
  assert_equal 3, second_three_developers.length
427
546
  assert_equal 2, last_two_developers.length
428
-
547
+
429
548
  assert_equal 'David', first_three_developers.first.name
430
549
  assert_equal 'fixture_4', second_three_developers.first.name
431
550
  assert_equal 'fixture_9', last_two_developers.first.name
@@ -443,8 +562,8 @@ class FinderTest < Test::Unit::TestCase
443
562
 
444
563
  def test_find_all_with_join
445
564
  developers_on_project_one = Developer.find(
446
- :all,
447
- :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
565
+ :all,
566
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
448
567
  :conditions => 'project_id=1'
449
568
  )
450
569
  assert_equal 3, developers_on_project_one.length
@@ -453,6 +572,15 @@ class FinderTest < Test::Unit::TestCase
453
572
  assert developer_names.include?('Jamis')
454
573
  end
455
574
 
575
+ def test_joins_dont_clobber_id
576
+ first = Firm.find(
577
+ :first,
578
+ :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id',
579
+ :conditions => 'companies.id = 1'
580
+ )
581
+ assert_equal 1, first.id
582
+ end
583
+
456
584
  def test_find_by_id_with_conditions_with_or
457
585
  assert_nothing_raised do
458
586
  Post.find([1,2,3],
@@ -493,6 +621,16 @@ class FinderTest < Test::Unit::TestCase
493
621
  assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
494
622
  end
495
623
 
624
+ def test_select_rows
625
+ assert_equal(
626
+ [["1", nil, nil, "37signals"],
627
+ ["2", "1", "2", "Summit"],
628
+ ["3", "1", "1", "Microsoft"]],
629
+ Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}})
630
+ assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
631
+ Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
632
+ end
633
+
496
634
  protected
497
635
  def bind(statement, *vars)
498
636
  if vars.first.is_a?(Hash)