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
@@ -0,0 +1,23 @@
1
+ require 'abstract_unit'
2
+ require "#{File.dirname(__FILE__)}/../lib/active_record/schema"
3
+
4
+ if ActiveRecord::Base.connection.supports_migrations?
5
+ class Order < ActiveRecord::Base
6
+ self.table_name = '[order]'
7
+ end
8
+
9
+ class TableNameTest < Test::Unit::TestCase
10
+ self.use_transactional_fixtures = false
11
+
12
+ # Ensures Model.columns works when using SQLServer escape characters.
13
+ # Enables legacy schemas using SQL reserved words as table names.
14
+ # Should work with table names with spaces as well ('table name').
15
+ def test_escaped_table_name
16
+ assert_nothing_raised do
17
+ ActiveRecord::Base.connection.select_all 'SELECT * FROM [order]'
18
+ end
19
+ assert_equal '[order]', Order.table_name
20
+ assert_equal 5, Order.columns.length
21
+ end
22
+ end
23
+ end
@@ -1,10 +1,12 @@
1
1
  require 'abstract_unit'
2
2
  require 'fixtures/topic'
3
+ require 'fixtures/reply'
3
4
 
4
- class ThreadedConnectionsTest < Test::Unit::TestCase
5
- self.use_transactional_fixtures = false
5
+ unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
6
+ class ThreadedConnectionsTest < Test::Unit::TestCase
7
+ self.use_transactional_fixtures = false
6
8
 
7
- fixtures :topics
9
+ fixtures :topics
8
10
 
9
11
  def setup
10
12
  @connection = ActiveRecord::Base.remove_connection
@@ -25,21 +27,22 @@ class ThreadedConnectionsTest < Test::Unit::TestCase
25
27
  ActiveRecord::Base.allow_concurrency = use_threaded_connections
26
28
  ActiveRecord::Base.establish_connection(@connection)
27
29
 
28
- 5.times do
29
- Thread.new do
30
- Topic.find :first
31
- @connections << ActiveRecord::Base.active_connections.values.first
32
- end.join
30
+ 5.times do
31
+ Thread.new do
32
+ Topic.find :first
33
+ @connections << ActiveRecord::Base.active_connections.values.first
34
+ end.join
35
+ end
33
36
  end
34
- end
35
37
 
36
- def test_threaded_connections
37
- gather_connections(true)
38
- assert_equal @connections.uniq.length, 5
39
- end
38
+ def test_threaded_connections
39
+ gather_connections(true)
40
+ assert_equal @connections.uniq.length, 5
41
+ end
40
42
 
41
- def test_unthreaded_connections
42
- gather_connections(false)
43
- assert_equal @connections.uniq.length, 1
43
+ def test_unthreaded_connections
44
+ gather_connections(false)
45
+ assert_equal @connections.uniq.length, 1
46
+ end
44
47
  end
45
48
  end
@@ -1,15 +1,13 @@
1
1
  require 'abstract_unit'
2
2
  require 'fixtures/topic'
3
+ require 'fixtures/reply'
3
4
  require 'fixtures/developer'
4
5
 
5
6
  class TransactionTest < Test::Unit::TestCase
6
7
  self.use_transactional_fixtures = false
7
-
8
8
  fixtures :topics, :developers
9
9
 
10
10
  def setup
11
- # sqlite does not seem to return these in the right order, so we sort them
12
- # explicitly for sqlite's sake. sqlite3 does fine.
13
11
  @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
14
12
  end
15
13
 
@@ -92,12 +90,14 @@ class TransactionTest < Test::Unit::TestCase
92
90
  assert !@first.approved?, "First should be unapproved initially"
93
91
 
94
92
  begin
95
- Topic.transaction(@first, @second) do
96
- @first.approved = true
97
- @second.approved = false
98
- @first.save
99
- @second.save
100
- raise "Bad things!"
93
+ assert_deprecated /Object transactions/ do
94
+ Topic.transaction(@first, @second) do
95
+ @first.approved = true
96
+ @second.approved = false
97
+ @first.save
98
+ @second.save
99
+ raise "Bad things!"
100
+ end
101
101
  end
102
102
  rescue
103
103
  # caught it
@@ -136,81 +136,95 @@ class TransactionTest < Test::Unit::TestCase
136
136
  assert !Topic.find(2).approved?, "Second should have been unapproved"
137
137
  end
138
138
 
139
- # This will cause transactions to overlap and fail unless they are
140
- # performed on separate database connections.
141
- def test_transaction_per_thread
142
- assert_nothing_raised do
143
- threads = (1..20).map do
144
- Thread.new do
145
- Topic.transaction do
146
- topic = Topic.find(:first)
147
- topic.approved = !topic.approved?
148
- topic.save!
149
- topic.approved = !topic.approved?
150
- topic.save!
139
+ private
140
+ def add_exception_raising_after_save_callback_to_topic
141
+ Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
142
+ end
143
+
144
+ def remove_exception_raising_after_save_callback_to_topic
145
+ Topic.class_eval { remove_method :after_save }
146
+ end
147
+ end
148
+
149
+ if current_adapter?(:PostgreSQLAdapter)
150
+ class ConcurrentTransactionTest < TransactionTest
151
+ def setup
152
+ @allow_concurrency = ActiveRecord::Base.allow_concurrency
153
+ ActiveRecord::Base.allow_concurrency = true
154
+ super
155
+ end
156
+
157
+ def teardown
158
+ super
159
+ ActiveRecord::Base.allow_concurrency = @allow_concurrency
160
+ end
161
+
162
+ # This will cause transactions to overlap and fail unless they are performed on
163
+ # separate database connections.
164
+ def test_transaction_per_thread
165
+ assert_nothing_raised do
166
+ threads = (1..3).map do
167
+ Thread.new do
168
+ Topic.transaction do
169
+ topic = Topic.find(1)
170
+ topic.approved = !topic.approved?
171
+ topic.save!
172
+ topic.approved = !topic.approved?
173
+ topic.save!
174
+ end
151
175
  end
152
176
  end
153
- end
154
177
 
155
- threads.each { |t| t.join }
178
+ threads.each { |t| t.join }
179
+ end
156
180
  end
157
- end
158
181
 
159
- # Test for dirty reads among simultaneous transactions.
160
- def test_transaction_isolation__read_committed
161
- # Should be invariant.
162
- original_salary = Developer.find(1).salary
163
- temporary_salary = 200000
164
-
165
- assert_nothing_raised do
166
- threads = (1..20).map do
167
- Thread.new do
168
- Developer.transaction do
169
- # Expect original salary.
170
- dev = Developer.find(1)
171
- assert_equal original_salary, dev.salary
172
-
173
- dev.salary = temporary_salary
174
- dev.save!
175
-
176
- # Expect temporary salary.
177
- dev = Developer.find(1)
178
- assert_equal temporary_salary, dev.salary
179
-
180
- dev.salary = original_salary
181
- dev.save!
182
-
183
- # Expect original salary.
184
- dev = Developer.find(1)
185
- assert_equal original_salary, dev.salary
182
+ # Test for dirty reads among simultaneous transactions.
183
+ def test_transaction_isolation__read_committed
184
+ # Should be invariant.
185
+ original_salary = Developer.find(1).salary
186
+ temporary_salary = 200000
187
+
188
+ assert_nothing_raised do
189
+ threads = (1..3).map do
190
+ Thread.new do
191
+ Developer.transaction do
192
+ # Expect original salary.
193
+ dev = Developer.find(1)
194
+ assert_equal original_salary, dev.salary
195
+
196
+ dev.salary = temporary_salary
197
+ dev.save!
198
+
199
+ # Expect temporary salary.
200
+ dev = Developer.find(1)
201
+ assert_equal temporary_salary, dev.salary
202
+
203
+ dev.salary = original_salary
204
+ dev.save!
205
+
206
+ # Expect original salary.
207
+ dev = Developer.find(1)
208
+ assert_equal original_salary, dev.salary
209
+ end
186
210
  end
187
211
  end
188
- end
189
212
 
190
- # Keep our eyes peeled.
191
- threads << Thread.new do
192
- 10.times do
193
- sleep 0.05
194
- Developer.transaction do
195
- # Always expect original salary.
196
- assert_equal original_salary, Developer.find(1).salary
213
+ # Keep our eyes peeled.
214
+ threads << Thread.new do
215
+ 10.times do
216
+ sleep 0.05
217
+ Developer.transaction do
218
+ # Always expect original salary.
219
+ assert_equal original_salary, Developer.find(1).salary
220
+ end
197
221
  end
198
222
  end
223
+
224
+ threads.each { |t| t.join }
199
225
  end
200
226
 
201
- threads.each { |t| t.join }
227
+ assert_equal original_salary, Developer.find(1).salary
202
228
  end
203
-
204
- assert_equal original_salary, Developer.find(1).salary
205
229
  end
206
-
207
-
208
- private
209
- def add_exception_raising_after_save_callback_to_topic
210
- Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
211
- end
212
-
213
- def remove_exception_raising_after_save_callback_to_topic
214
- Topic.class_eval { remove_method :after_save }
215
- end
216
230
  end
@@ -26,16 +26,16 @@ class ValidationsTest < Test::Unit::TestCase
26
26
  def test_single_field_validation
27
27
  r = Reply.new
28
28
  r.title = "There's no content!"
29
- assert !r.save, "A reply without content shouldn't be saveable"
29
+ assert !r.valid?, "A reply without content shouldn't be saveable"
30
30
 
31
31
  r.content = "Messa content!"
32
- assert r.save, "A reply with content should be saveable"
32
+ assert r.valid?, "A reply with content should be saveable"
33
33
  end
34
34
 
35
35
  def test_single_attr_validation_and_error_msg
36
36
  r = Reply.new
37
37
  r.title = "There's no content!"
38
- r.save
38
+ assert !r.valid?
39
39
  assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
40
40
  assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
41
41
  assert_equal 1, r.errors.count
@@ -43,7 +43,7 @@ class ValidationsTest < Test::Unit::TestCase
43
43
 
44
44
  def test_double_attr_validation_and_error_msg
45
45
  r = Reply.new
46
- assert !r.save
46
+ assert !r.valid?
47
47
 
48
48
  assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
49
49
  assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
@@ -57,7 +57,7 @@ class ValidationsTest < Test::Unit::TestCase
57
57
  def test_error_on_create
58
58
  r = Reply.new
59
59
  r.title = "Wrong Create"
60
- assert !r.save
60
+ assert !r.valid?
61
61
  assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
62
62
  assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
63
63
  end
@@ -88,6 +88,12 @@ class ValidationsTest < Test::Unit::TestCase
88
88
  end
89
89
  end
90
90
 
91
+ def test_scoped_create_without_attributes
92
+ Reply.with_scope(:create => {}) do
93
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
94
+ end
95
+ end
96
+
91
97
  def test_single_error_per_attr_iteration
92
98
  r = Reply.new
93
99
  r.save
@@ -135,6 +141,12 @@ class ValidationsTest < Test::Unit::TestCase
135
141
  assert reply.save(false)
136
142
  end
137
143
 
144
+ def test_create_without_validation_bang
145
+ count = Reply.count
146
+ assert_nothing_raised { Reply.new.save_without_validation! }
147
+ assert count+1, Reply.count
148
+ end
149
+
138
150
  def test_validates_each
139
151
  perform = true
140
152
  hits = 0
@@ -153,24 +165,21 @@ class ValidationsTest < Test::Unit::TestCase
153
165
  perform = false
154
166
  end
155
167
 
156
- def test_errors_on_boundary_breaking
157
- developer = Developer.new("name" => "xs")
158
- assert !developer.save
159
- assert_equal "is too short (minimum is 3 characters)", developer.errors.on("name")
168
+ def test_no_title_confirmation
169
+ Topic.validates_confirmation_of(:title)
160
170
 
161
- developer.name = "All too very long for this boundary, it really is"
162
- assert !developer.save
163
- assert_equal "is too long (maximum is 20 characters)", developer.errors.on("name")
171
+ t = Topic.new(:author_name => "Plutarch")
172
+ assert t.valid?
164
173
 
165
- developer.name = "Just right"
166
- assert developer.save
167
- end
174
+ t.title_confirmation = "Parallel Lives"
175
+ assert !t.valid?
168
176
 
169
- def test_title_confirmation_no_confirm
170
- Topic.validates_confirmation_of(:title)
177
+ t.title_confirmation = nil
178
+ t.title = "Parallel Lives"
179
+ assert t.valid?
171
180
 
172
- t = Topic.create("title" => "We should not be confirmed")
173
- assert t.save
181
+ t.title_confirmation = "Parallel Lives"
182
+ assert t.valid?
174
183
  end
175
184
 
176
185
  def test_title_confirmation
@@ -304,6 +313,37 @@ class ValidationsTest < Test::Unit::TestCase
304
313
  assert !r3.save, "Saving r3 the third time."
305
314
  end
306
315
 
316
+ def test_validate_case_insensitive_uniqueness
317
+ Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
318
+
319
+ t = Topic.new("title" => "I'm unique!", :parent_id => 2)
320
+ assert t.save, "Should save t as unique"
321
+
322
+ t.content = "Remaining unique"
323
+ assert t.save, "Should still save t as unique"
324
+
325
+ t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
326
+ assert !t2.valid?, "Shouldn't be valid"
327
+ assert !t2.save, "Shouldn't save t2 as unique"
328
+ assert t2.errors.on(:title)
329
+ assert t2.errors.on(:parent_id)
330
+ assert_equal "has already been taken", t2.errors.on(:title)
331
+
332
+ t2.title = "I'm truly UNIQUE!"
333
+ assert !t2.valid?, "Shouldn't be valid"
334
+ assert !t2.save, "Shouldn't save t2 as unique"
335
+ assert_nil t2.errors.on(:title)
336
+ assert t2.errors.on(:parent_id)
337
+
338
+ t2.parent_id = 3
339
+ assert t2.save, "Should now save t2 as unique"
340
+
341
+ t2.parent_id = nil
342
+ t2.title = nil
343
+ assert t2.valid?, "should validate with nil"
344
+ assert t2.save, "should save with nil"
345
+ end
346
+
307
347
  def test_validate_format
308
348
  Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
309
349
 
@@ -573,6 +613,18 @@ class ValidationsTest < Test::Unit::TestCase
573
613
 
574
614
  assert_equal 'tu est trops petit hombre 10', t.errors['title']
575
615
  end
616
+
617
+ def test_add_on_boundary_breaking_is_deprecated
618
+ t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
619
+ class << t
620
+ def validate
621
+ errors.add_on_boundary_breaking('title', 1..6)
622
+ end
623
+ end
624
+ assert_deprecated 'add_on_boundary_breaking' do
625
+ assert !t.valid?
626
+ end
627
+ end
576
628
 
577
629
  def test_validates_size_of_association
578
630
  assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
@@ -672,7 +724,7 @@ class ValidationsTest < Test::Unit::TestCase
672
724
 
673
725
  t = Topic.create("title" => "一二三四五", "content" => "whatever")
674
726
  assert t.valid?
675
-
727
+
676
728
  t.title = "一二34五六"
677
729
  assert !t.valid?
678
730
  assert t.errors.on(:title)
@@ -714,16 +766,16 @@ class ValidationsTest < Test::Unit::TestCase
714
766
  assert !t.save
715
767
  assert t.errors.on(:title)
716
768
  assert_equal "長すぎます: 10", t.errors[:title]
717
-
769
+
718
770
  t.title = "一二三四五六七八九"
719
771
  assert t.save
720
-
772
+
721
773
  t.title = "一二3"
722
774
  assert t.save
723
-
775
+
724
776
  t.content = "一二三四五六七八九十"
725
777
  assert t.save
726
-
778
+
727
779
  t.content = t.title = "一二三四五六"
728
780
  assert t.save
729
781
  end
@@ -736,17 +788,17 @@ class ValidationsTest < Test::Unit::TestCase
736
788
  t = Topic.create("title" => "一二三4", "content" => "whatever")
737
789
  assert !t.save
738
790
  assert t.errors.on(:title)
739
-
791
+
740
792
  t.title = "1二三4"
741
793
  assert !t.save
742
794
  assert t.errors.on(:title)
743
795
  assert_equal "短すぎます: 5", t.errors[:title]
744
-
796
+
745
797
  t.title = "valid"
746
798
  t.content = "一二三四五六七八九十A"
747
799
  assert !t.save
748
800
  assert t.errors.on(:content)
749
-
801
+
750
802
  t.content = "一二345"
751
803
  assert t.save
752
804
  end
@@ -814,7 +866,7 @@ class ValidationsTest < Test::Unit::TestCase
814
866
  end
815
867
 
816
868
  def test_throw_away_typing
817
- d = Developer.create "name" => "David", "salary" => "100,000"
869
+ d = Developer.new("name" => "David", "salary" => "100,000")
818
870
  assert !d.valid?
819
871
  assert_equal 100, d.salary
820
872
  assert_equal "100,000", d.salary_before_type_cast
@@ -829,20 +881,20 @@ class ValidationsTest < Test::Unit::TestCase
829
881
  end
830
882
 
831
883
  def test_validates_confirmation_of_with_custom_error_using_quotes
832
- Developer.validates_confirmation_of :name, :message=> "This string contains 'single' and \"double\" quotes"
884
+ Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
833
885
  d = Developer.new
834
886
  d.name = "John"
835
887
  d.name_confirmation = "Johnny"
836
888
  assert !d.valid?
837
- assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
889
+ assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
838
890
  end
839
891
 
840
892
  def test_validates_format_of_with_custom_error_using_quotes
841
- Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "This string contains 'single' and \"double\" quotes"
893
+ Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
842
894
  d = Developer.new
843
- d.name = "John 32"
895
+ d.name = d.name_confirmation = "John 32"
844
896
  assert !d.valid?
845
- assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
897
+ assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
846
898
  end
847
899
 
848
900
  def test_validates_inclusion_of_with_custom_error_using_quotes
@@ -850,7 +902,7 @@ class ValidationsTest < Test::Unit::TestCase
850
902
  d = Developer.new
851
903
  d.salary = "90,000"
852
904
  assert !d.valid?
853
- assert_equal d.errors.on(:salary).first, "This string contains 'single' and \"double\" quotes"
905
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).first
854
906
  end
855
907
 
856
908
  def test_validates_length_of_with_custom_too_long_using_quotes
@@ -858,7 +910,7 @@ class ValidationsTest < Test::Unit::TestCase
858
910
  d = Developer.new
859
911
  d.name = "Jeffrey"
860
912
  assert !d.valid?
861
- assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
913
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
862
914
  end
863
915
 
864
916
  def test_validates_length_of_with_custom_too_short_using_quotes
@@ -866,7 +918,7 @@ class ValidationsTest < Test::Unit::TestCase
866
918
  d = Developer.new
867
919
  d.name = "Joe"
868
920
  assert !d.valid?
869
- assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
921
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
870
922
  end
871
923
 
872
924
  def test_validates_length_of_with_custom_message_using_quotes
@@ -874,7 +926,7 @@ class ValidationsTest < Test::Unit::TestCase
874
926
  d = Developer.new
875
927
  d.name = "Joe"
876
928
  assert !d.valid?
877
- assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
929
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
878
930
  end
879
931
 
880
932
  def test_validates_presence_of_with_custom_message_using_quotes
@@ -882,7 +934,7 @@ class ValidationsTest < Test::Unit::TestCase
882
934
  d = Developer.new
883
935
  d.name = "Joe"
884
936
  assert !d.valid?
885
- assert_equal d.errors.on(:non_existent), "This string contains 'single' and \"double\" quotes"
937
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
886
938
  end
887
939
 
888
940
  def test_validates_uniqueness_of_with_custom_message_using_quotes
@@ -890,7 +942,7 @@ class ValidationsTest < Test::Unit::TestCase
890
942
  d = Developer.new
891
943
  d.name = "David"
892
944
  assert !d.valid?
893
- assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
945
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
894
946
  end
895
947
 
896
948
  def test_validates_associated_with_custom_message_using_quotes
@@ -899,7 +951,7 @@ class ValidationsTest < Test::Unit::TestCase
899
951
  r = Reply.create("title" => "A reply", "content" => "with content!")
900
952
  r.topic = Topic.create("title" => "uhohuhoh")
901
953
  assert !r.valid?
902
- assert_equal r.errors.on(:topic).first, "This string contains 'single' and \"double\" quotes"
954
+ assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).first
903
955
  end
904
956
 
905
957
  def test_conditional_validation_using_method_true
@@ -916,7 +968,7 @@ class ValidationsTest < Test::Unit::TestCase
916
968
  Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
917
969
  t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
918
970
  assert t.valid?
919
- assert !t.errors.on(:title)
971
+ assert !t.errors.on(:title)
920
972
  end
921
973
 
922
974
  def test_conditional_validation_using_string_true
@@ -964,16 +1016,28 @@ class ValidationsTest < Test::Unit::TestCase
964
1016
  r.topic = Topic.find :first
965
1017
  assert r.valid?
966
1018
  end
1019
+
1020
+ def test_errors_to_xml
1021
+ r = Reply.new :title => "Wrong Create"
1022
+ assert !r.valid?
1023
+ xml = r.errors.to_xml(:skip_instruct => true)
1024
+ assert_equal "<errors>", xml.first(8)
1025
+ assert xml.include?("<error>Title is Wrong Create</error>")
1026
+ assert xml.include?("<error>Content Empty</error>")
1027
+ end
967
1028
  end
968
1029
 
969
1030
 
970
- class ValidatesNumericalityTest
971
- NIL = [nil, "", " ", " \t \r \n"]
972
- FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1)
1031
+ class ValidatesNumericalityTest < Test::Unit::TestCase
1032
+ NIL = [nil]
1033
+ BLANK = ["", " ", " \t \r \n"]
1034
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
1035
+ FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
973
1036
  INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
974
1037
  FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
975
1038
  INTEGERS = [0, 10, -10] + INTEGER_STRINGS
976
- JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12"]
1039
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
1040
+ JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
977
1041
 
978
1042
  def setup
979
1043
  Topic.write_inheritable_attribute(:validate, nil)
@@ -984,44 +1048,50 @@ class ValidatesNumericalityTest
984
1048
  def test_default_validates_numericality_of
985
1049
  Topic.validates_numericality_of :approved
986
1050
 
987
- invalid!(NIL + JUNK)
988
- valid!(FLOATS + INTEGERS)
1051
+ invalid!(NIL + BLANK + JUNK)
1052
+ valid!(FLOATS + INTEGERS + BIGDECIMAL)
989
1053
  end
990
1054
 
991
1055
  def test_validates_numericality_of_with_nil_allowed
992
1056
  Topic.validates_numericality_of :approved, :allow_nil => true
993
1057
 
994
- invalid!(JUNK)
995
- valid!(NIL + FLOATS + INTEGERS)
1058
+ invalid!(BLANK + JUNK)
1059
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
996
1060
  end
997
1061
 
998
1062
  def test_validates_numericality_of_with_integer_only
999
1063
  Topic.validates_numericality_of :approved, :only_integer => true
1000
1064
 
1001
- invalid!(NIL + JUNK + FLOATS)
1065
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
1002
1066
  valid!(INTEGERS)
1003
1067
  end
1004
1068
 
1005
1069
  def test_validates_numericality_of_with_integer_only_and_nil_allowed
1006
1070
  Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
1007
1071
 
1008
- invalid!(JUNK + FLOATS)
1072
+ invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
1009
1073
  valid!(NIL + INTEGERS)
1010
1074
  end
1011
1075
 
1012
1076
  private
1013
1077
  def invalid!(values)
1014
- values.each do |value|
1015
- topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
1016
- assert !topic.valid?, "#{value} not rejected as a number"
1078
+ with_each_topic_approved_value(values) do |topic, value|
1079
+ assert !topic.valid?, "#{value.inspect} not rejected as a number"
1017
1080
  assert topic.errors.on(:approved)
1018
1081
  end
1019
1082
  end
1020
1083
 
1021
1084
  def valid!(values)
1085
+ with_each_topic_approved_value(values) do |topic, value|
1086
+ assert topic.valid?, "#{value.inspect} not accepted as a number"
1087
+ end
1088
+ end
1089
+
1090
+ def with_each_topic_approved_value(values)
1091
+ topic = Topic.new("title" => "numeric test", "content" => "whatever")
1022
1092
  values.each do |value|
1023
- topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
1024
- assert topic.valid?, "#{value} not accepted as a number"
1093
+ topic.approved = value
1094
+ yield topic, value
1025
1095
  end
1026
1096
  end
1027
1097
  end