ibm_db 0.9.5 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/CHANGES +5 -0
  2. data/README +63 -97
  3. data/ext/ibm_db.c +20 -4
  4. data/ext/ruby_ibm_db.h +10 -0
  5. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +2 -4
  6. data/test/{adapter_test.rb → cases/adapter_test.rb} +39 -14
  7. data/test/cases/associations/cascaded_eager_loading_test.rb +113 -0
  8. data/test/{associations → cases/associations}/eager_test.rb +231 -65
  9. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +686 -0
  10. data/test/cases/associations/join_model_test.rb +797 -0
  11. data/test/{base_test.rb → cases/base_test.rb} +605 -385
  12. data/test/cases/calculations_test.rb +275 -0
  13. data/test/cases/finder_test.rb +885 -0
  14. data/test/cases/fixtures_test.rb +630 -0
  15. data/test/{migration_test.rb → cases/migration_test.rb} +530 -146
  16. data/test/cases/query_cache_test.rb +127 -0
  17. data/test/cases/validations_test.rb +1509 -0
  18. data/test/connections/native_ibm_db/connection.rb +1 -1
  19. data/test/models/warehouse_thing.rb +5 -0
  20. data/test/schema/i5/ibm_db_specific_schema.rb +133 -0
  21. data/test/schema/ids/ibm_db_specific_schema.rb +136 -0
  22. data/test/schema/luw/ibm_db_specific_schema.rb +133 -0
  23. data/test/schema/schema.rb +432 -0
  24. data/test/schema/zOS/ibm_db_specific_schema.rb +204 -0
  25. metadata +29 -33
  26. data/test/associations_test.rb +0 -2151
  27. data/test/fixtures/db_definitions/i5/ibm_db.drop.sql +0 -33
  28. data/test/fixtures/db_definitions/i5/ibm_db.sql +0 -236
  29. data/test/fixtures/db_definitions/i5/ibm_db2.drop.sql +0 -2
  30. data/test/fixtures/db_definitions/i5/ibm_db2.sql +0 -5
  31. data/test/fixtures/db_definitions/ids/ibm_db.drop.sql +0 -33
  32. data/test/fixtures/db_definitions/ids/ibm_db.sql +0 -237
  33. data/test/fixtures/db_definitions/ids/ibm_db2.drop.sql +0 -2
  34. data/test/fixtures/db_definitions/ids/ibm_db2.sql +0 -5
  35. data/test/fixtures/db_definitions/luw/ibm_db.drop.sql +0 -33
  36. data/test/fixtures/db_definitions/luw/ibm_db.sql +0 -236
  37. data/test/fixtures/db_definitions/luw/ibm_db2.drop.sql +0 -2
  38. data/test/fixtures/db_definitions/luw/ibm_db2.sql +0 -5
  39. data/test/fixtures/db_definitions/schema.rb +0 -361
  40. data/test/fixtures/db_definitions/zOS/ibm_db.drop.sql +0 -33
  41. data/test/fixtures/db_definitions/zOS/ibm_db.sql +0 -288
  42. data/test/fixtures/db_definitions/zOS/ibm_db2.drop.sql +0 -2
  43. data/test/fixtures/db_definitions/zOS/ibm_db2.sql +0 -7
  44. data/test/locking_test.rb +0 -282
@@ -0,0 +1,127 @@
1
+ require "cases/helper"
2
+ require 'models/topic'
3
+ require 'models/reply'
4
+ require 'models/task'
5
+ require 'models/course'
6
+ require 'models/category'
7
+ require 'models/post'
8
+
9
+ unless current_adapter?(:IBM_DBAdapter)
10
+ class QueryCacheTest < ActiveRecord::TestCase
11
+ fixtures :tasks, :topics, :categories, :posts, :categories_posts
12
+
13
+ def test_find_queries
14
+ assert_queries(2) { Task.find(1); Task.find(1) }
15
+ end
16
+
17
+ def test_find_queries_with_cache
18
+ Task.cache do
19
+ assert_queries(1) { Task.find(1); Task.find(1) }
20
+ end
21
+ end
22
+
23
+ def test_count_queries_with_cache
24
+ Task.cache do
25
+ assert_queries(1) { Task.count; Task.count }
26
+ end
27
+ end
28
+
29
+ def test_query_cache_dups_results_correctly
30
+ Task.cache do
31
+ now = Time.now.utc
32
+ task = Task.find 1
33
+ assert_not_equal now, task.starting
34
+ task.starting = now
35
+ task.reload
36
+ assert_not_equal now, task.starting
37
+ end
38
+ end
39
+
40
+ def test_cache_is_flat
41
+ Task.cache do
42
+ Topic.columns # don't count this query
43
+ assert_queries(1) { Topic.find(1); Topic.find(1); }
44
+ end
45
+
46
+ ActiveRecord::Base.cache do
47
+ assert_queries(1) { Task.find(1); Task.find(1) }
48
+ end
49
+ end
50
+
51
+ def test_cache_does_not_wrap_string_results_in_arrays
52
+ Task.cache do
53
+ assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
54
+ end
55
+ end
56
+ end
57
+
58
+ uses_mocha 'QueryCacheExpiryTest' do
59
+
60
+ class QueryCacheExpiryTest < ActiveRecord::TestCase
61
+ fixtures :tasks
62
+
63
+ def test_find
64
+ Task.connection.expects(:clear_query_cache).times(1)
65
+
66
+ assert !Task.connection.query_cache_enabled
67
+ Task.cache do
68
+ assert Task.connection.query_cache_enabled
69
+ Task.find(1)
70
+
71
+ Task.uncached do
72
+ assert !Task.connection.query_cache_enabled
73
+ Task.find(1)
74
+ end
75
+
76
+ assert Task.connection.query_cache_enabled
77
+ end
78
+ assert !Task.connection.query_cache_enabled
79
+ end
80
+
81
+ def test_update
82
+ Task.connection.expects(:clear_query_cache).times(2)
83
+
84
+ Task.cache do
85
+ task = Task.find(1)
86
+ task.starting = Time.now.utc
87
+ task.save!
88
+ end
89
+ end
90
+
91
+ def test_destroy
92
+ Task.connection.expects(:clear_query_cache).times(2)
93
+
94
+ Task.cache do
95
+ Task.find(1).destroy
96
+ end
97
+ end
98
+
99
+ def test_insert
100
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
101
+
102
+ Task.cache do
103
+ Task.create!
104
+ end
105
+ end
106
+
107
+ def test_cache_is_expired_by_habtm_update
108
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
109
+ ActiveRecord::Base.cache do
110
+ c = Category.find(:first)
111
+ p = Post.find(:first)
112
+ p.categories << c
113
+ end
114
+ end
115
+
116
+ def test_cache_is_expired_by_habtm_delete
117
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
118
+ ActiveRecord::Base.cache do
119
+ c = Category.find(:first)
120
+ p = Post.find(:first)
121
+ p.categories.delete_all
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,1509 @@
1
+ # encoding: utf-8
2
+ require "cases/helper"
3
+ require 'models/topic'
4
+ require 'models/reply'
5
+ require 'models/person'
6
+ require 'models/developer'
7
+ require 'models/warehouse_thing'
8
+ require 'models/guid'
9
+
10
+ # The following methods in Topic are used in test_conditional_validation_*
11
+ class Topic
12
+ has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
13
+ has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
14
+
15
+ def condition_is_true
16
+ true
17
+ end
18
+
19
+ def condition_is_true_but_its_not
20
+ false
21
+ end
22
+ end
23
+
24
+ class ProtectedPerson < ActiveRecord::Base
25
+ set_table_name 'people'
26
+ attr_accessor :addon
27
+ attr_protected :first_name
28
+ end
29
+
30
+ class UniqueReply < Reply
31
+ validates_uniqueness_of :content, :scope => 'parent_id'
32
+ end
33
+
34
+ class PlagiarizedReply < Reply
35
+ validates_acceptance_of :author_name
36
+ end
37
+
38
+ class SillyUniqueReply < UniqueReply
39
+ end
40
+
41
+ class Wizard < ActiveRecord::Base
42
+ self.abstract_class = true
43
+
44
+ validates_uniqueness_of :name
45
+ end
46
+
47
+ class IneptWizard < Wizard
48
+ validates_uniqueness_of :city
49
+ end
50
+
51
+ class Conjurer < IneptWizard
52
+ end
53
+
54
+ class Thaumaturgist < IneptWizard
55
+ end
56
+
57
+
58
+ class ValidationsTest < ActiveRecord::TestCase
59
+ fixtures :topics, :developers, :warehouse_things
60
+
61
+ def setup
62
+ Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
63
+ Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
64
+ Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
65
+ end
66
+
67
+ def test_single_field_validation
68
+ r = Reply.new
69
+ r.title = "There's no content!"
70
+ assert !r.valid?, "A reply without content shouldn't be saveable"
71
+
72
+ r.content = "Messa content!"
73
+ assert r.valid?, "A reply with content should be saveable"
74
+ end
75
+
76
+ def test_single_attr_validation_and_error_msg
77
+ r = Reply.new
78
+ r.title = "There's no content!"
79
+ assert !r.valid?
80
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
81
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
82
+ assert_equal 1, r.errors.count
83
+ end
84
+
85
+ def test_double_attr_validation_and_error_msg
86
+ r = Reply.new
87
+ assert !r.valid?
88
+
89
+ assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
90
+ assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
91
+
92
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
93
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
94
+
95
+ assert_equal 2, r.errors.count
96
+ end
97
+
98
+ def test_error_on_create
99
+ r = Reply.new
100
+ r.title = "Wrong Create"
101
+ assert !r.valid?
102
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
103
+ assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
104
+ end
105
+
106
+ def test_error_on_update
107
+ r = Reply.new
108
+ r.title = "Bad"
109
+ r.content = "Good"
110
+ assert r.save, "First save should be successful"
111
+
112
+ r.title = "Wrong Update"
113
+ assert !r.save, "Second save should fail"
114
+
115
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
116
+ assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
117
+ end
118
+
119
+ def test_invalid_record_exception
120
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
121
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! }
122
+
123
+ begin
124
+ r = Reply.new
125
+ r.save!
126
+ flunk
127
+ rescue ActiveRecord::RecordInvalid => invalid
128
+ assert_equal r, invalid.record
129
+ end
130
+ end
131
+
132
+ def test_exception_on_create_bang_many
133
+ assert_raises(ActiveRecord::RecordInvalid) do
134
+ Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
135
+ end
136
+ end
137
+
138
+ def test_exception_on_create_bang_with_block
139
+ assert_raises(ActiveRecord::RecordInvalid) do
140
+ Reply.create!({ "title" => "OK" }) do |r|
141
+ r.content = nil
142
+ end
143
+ end
144
+ end
145
+
146
+ def test_exception_on_create_bang_many_with_block
147
+ assert_raises(ActiveRecord::RecordInvalid) do
148
+ Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
149
+ r.content = nil
150
+ end
151
+ end
152
+ end
153
+
154
+ def test_scoped_create_without_attributes
155
+ Reply.with_scope(:create => {}) do
156
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
157
+ end
158
+ end
159
+
160
+ def test_create_with_exceptions_using_scope_for_protected_attributes
161
+ assert_nothing_raised do
162
+ ProtectedPerson.with_scope( :create => { :first_name => "Mary" } ) do
163
+ person = ProtectedPerson.create! :addon => "Addon"
164
+ assert_equal person.first_name, "Mary", "scope should ignore attr_protected"
165
+ end
166
+ end
167
+ end
168
+
169
+ def test_create_with_exceptions_using_scope_and_empty_attributes
170
+ assert_nothing_raised do
171
+ ProtectedPerson.with_scope( :create => { :first_name => "Mary" } ) do
172
+ person = ProtectedPerson.create!
173
+ assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!"
174
+ end
175
+ end
176
+ end
177
+
178
+ def test_single_error_per_attr_iteration
179
+ r = Reply.new
180
+ r.save
181
+
182
+ errors = []
183
+ r.errors.each { |attr, msg| errors << [attr, msg] }
184
+
185
+ assert errors.include?(["title", "Empty"])
186
+ assert errors.include?(["content", "Empty"])
187
+ end
188
+
189
+ def test_multiple_errors_per_attr_iteration_with_full_error_composition
190
+ r = Reply.new
191
+ r.title = "Wrong Create"
192
+ r.content = "Mismatch"
193
+ r.save
194
+
195
+ errors = []
196
+ r.errors.each_full { |error| errors << error }
197
+
198
+ assert_equal "Title is Wrong Create", errors[0]
199
+ assert_equal "Title is Content Mismatch", errors[1]
200
+ assert_equal 2, r.errors.count
201
+ end
202
+
203
+ def test_errors_on_base
204
+ r = Reply.new
205
+ r.content = "Mismatch"
206
+ r.save
207
+ r.errors.add_to_base "Reply is not dignifying"
208
+
209
+ errors = []
210
+ r.errors.each_full { |error| errors << error }
211
+
212
+ assert_equal "Reply is not dignifying", r.errors.on_base
213
+
214
+ assert errors.include?("Title Empty")
215
+ assert errors.include?("Reply is not dignifying")
216
+ assert_equal 2, r.errors.count
217
+ end
218
+
219
+ def test_create_without_validation
220
+ reply = Reply.new
221
+ assert !reply.save
222
+ assert reply.save(false)
223
+ end
224
+
225
+ def test_create_without_validation_bang
226
+ count = Reply.count
227
+ assert_nothing_raised { Reply.new.save_without_validation! }
228
+ assert count+1, Reply.count
229
+ end
230
+
231
+ def test_validates_each
232
+ perform = true
233
+ hits = 0
234
+ Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
235
+ if perform
236
+ record.errors.add attr, 'gotcha'
237
+ hits += 1
238
+ end
239
+ end
240
+ t = Topic.new("title" => "valid", "content" => "whatever")
241
+ assert !t.save
242
+ assert_equal 4, hits
243
+ assert_equal %w(gotcha gotcha), t.errors.on(:title)
244
+ assert_equal %w(gotcha gotcha), t.errors.on(:content)
245
+ ensure
246
+ perform = false
247
+ end
248
+
249
+ def test_no_title_confirmation
250
+ Topic.validates_confirmation_of(:title)
251
+
252
+ t = Topic.new(:author_name => "Plutarch")
253
+ assert t.valid?
254
+
255
+ t.title_confirmation = "Parallel Lives"
256
+ assert !t.valid?
257
+
258
+ t.title_confirmation = nil
259
+ t.title = "Parallel Lives"
260
+ assert t.valid?
261
+
262
+ t.title_confirmation = "Parallel Lives"
263
+ assert t.valid?
264
+ end
265
+
266
+ def test_title_confirmation
267
+ Topic.validates_confirmation_of(:title)
268
+
269
+ t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
270
+ assert !t.save
271
+
272
+ t.title_confirmation = "We should be confirmed"
273
+ assert t.save
274
+ end
275
+
276
+ def test_terms_of_service_agreement_no_acceptance
277
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
278
+
279
+ t = Topic.create("title" => "We should not be confirmed")
280
+ assert t.save
281
+ end
282
+
283
+ def test_terms_of_service_agreement
284
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
285
+
286
+ t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
287
+ assert !t.save
288
+ assert_equal "must be accepted", t.errors.on(:terms_of_service)
289
+
290
+ t.terms_of_service = "1"
291
+ assert t.save
292
+ end
293
+
294
+
295
+ def test_eula
296
+ Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
297
+
298
+ t = Topic.create("title" => "We should be confirmed","eula" => "")
299
+ assert !t.save
300
+ assert_equal "must be abided", t.errors.on(:eula)
301
+
302
+ t.eula = "1"
303
+ assert t.save
304
+ end
305
+
306
+ def test_terms_of_service_agreement_with_accept_value
307
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
308
+
309
+ t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
310
+ assert !t.save
311
+ assert_equal "must be accepted", t.errors.on(:terms_of_service)
312
+
313
+ t.terms_of_service = "I agree."
314
+ assert t.save
315
+ end
316
+
317
+ def test_validates_acceptance_of_as_database_column
318
+ reply = PlagiarizedReply.create("author_name" => "Dan Brown")
319
+ assert_equal "Dan Brown", reply["author_name"]
320
+ end
321
+
322
+ def test_validates_acceptance_of_with_non_existant_table
323
+ Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
324
+
325
+ assert_nothing_raised ActiveRecord::StatementInvalid do
326
+ IncorporealModel.validates_acceptance_of(:incorporeal_column)
327
+ end
328
+ end
329
+
330
+ def test_validate_presences
331
+ Topic.validates_presence_of(:title, :content)
332
+
333
+ t = Topic.create
334
+ assert !t.save
335
+ assert_equal "can't be blank", t.errors.on(:title)
336
+ assert_equal "can't be blank", t.errors.on(:content)
337
+
338
+ t.title = "something"
339
+ t.content = " "
340
+
341
+ assert !t.save
342
+ assert_equal "can't be blank", t.errors.on(:content)
343
+
344
+ t.content = "like stuff"
345
+
346
+ assert t.save
347
+ end
348
+
349
+ def test_validate_uniqueness
350
+ Topic.validates_uniqueness_of(:title)
351
+
352
+ t = Topic.new("title" => "I'm unique!")
353
+ assert t.save, "Should save t as unique"
354
+
355
+ t.content = "Remaining unique"
356
+ assert t.save, "Should still save t as unique"
357
+
358
+ t2 = Topic.new("title" => "I'm unique!")
359
+ assert !t2.valid?, "Shouldn't be valid"
360
+ assert !t2.save, "Shouldn't save t2 as unique"
361
+ assert_equal "has already been taken", t2.errors.on(:title)
362
+
363
+ t2.title = "Now Im really also unique"
364
+ assert t2.save, "Should now save t2 as unique"
365
+ end
366
+
367
+ def test_validate_uniqueness_with_scope
368
+ Reply.validates_uniqueness_of(:content, :scope => "parent_id")
369
+
370
+ t = Topic.create("title" => "I'm unique!")
371
+
372
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
373
+ assert r1.valid?, "Saving r1"
374
+
375
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
376
+ assert !r2.valid?, "Saving r2 first time"
377
+
378
+ r2.content = "something else"
379
+ assert r2.save, "Saving r2 second time"
380
+
381
+ t2 = Topic.create("title" => "I'm unique too!")
382
+ r3 = t2.replies.create "title" => "r3", "content" => "hello world"
383
+ assert r3.valid?, "Saving r3"
384
+ end
385
+
386
+ def test_validate_uniqueness_scoped_to_defining_class
387
+ t = Topic.create("title" => "What, me worry?")
388
+
389
+ r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun"
390
+ assert r1.valid?, "Saving r1"
391
+
392
+ r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun"
393
+ assert !r2.valid?, "Saving r2"
394
+
395
+ # Should succeed as validates_uniqueness_of only applies to
396
+ # UniqueReply and its subclasses
397
+ r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun"
398
+ assert r3.valid?, "Saving r3"
399
+ end
400
+
401
+ def test_validate_uniqueness_with_scope_array
402
+ Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
403
+
404
+ t = Topic.create("title" => "The earth is actually flat!")
405
+
406
+ r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
407
+ assert r1.valid?, "Saving r1"
408
+
409
+ r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
410
+ assert !r2.valid?, "Saving r2. Double reply by same author."
411
+
412
+ r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
413
+ assert r2.save, "Saving r2 the second time."
414
+
415
+ r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
416
+ assert !r3.valid?, "Saving r3"
417
+
418
+ r3.author_name = "jj"
419
+ assert r3.save, "Saving r3 the second time."
420
+
421
+ r3.author_name = "jeremy"
422
+ assert !r3.save, "Saving r3 the third time."
423
+ end
424
+
425
+ def test_validate_case_insensitive_uniqueness
426
+ Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
427
+
428
+ t = Topic.new("title" => "I'm unique!", :parent_id => 2)
429
+ assert t.save, "Should save t as unique"
430
+
431
+ t.content = "Remaining unique"
432
+ assert t.save, "Should still save t as unique"
433
+
434
+ t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
435
+ assert !t2.valid?, "Shouldn't be valid"
436
+ assert !t2.save, "Shouldn't save t2 as unique"
437
+ assert t2.errors.on(:title)
438
+ assert t2.errors.on(:parent_id)
439
+ assert_equal "has already been taken", t2.errors.on(:title)
440
+
441
+ t2.title = "I'm truly UNIQUE!"
442
+ assert !t2.valid?, "Shouldn't be valid"
443
+ assert !t2.save, "Shouldn't save t2 as unique"
444
+ assert_nil t2.errors.on(:title)
445
+ assert t2.errors.on(:parent_id)
446
+
447
+ t2.parent_id = 4
448
+ assert t2.save, "Should now save t2 as unique"
449
+
450
+ t2.parent_id = nil
451
+ t2.title = nil
452
+ assert t2.valid?, "should validate with nil"
453
+ assert t2.save, "should save with nil"
454
+ end
455
+
456
+ def test_validate_case_sensitive_uniqueness
457
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true)
458
+
459
+ t = Topic.new("title" => "I'm unique!")
460
+ assert t.save, "Should save t as unique"
461
+
462
+ t.content = "Remaining unique"
463
+ assert t.save, "Should still save t as unique"
464
+
465
+ t2 = Topic.new("title" => "I'M UNIQUE!")
466
+ assert t2.valid?, "Should be valid"
467
+ assert t2.save, "Should save t2 as unique"
468
+ assert !t2.errors.on(:title)
469
+ assert !t2.errors.on(:parent_id)
470
+ assert_not_equal "has already been taken", t2.errors.on(:title)
471
+
472
+ t3 = Topic.new("title" => "I'M uNiQUe!")
473
+ assert t3.valid?, "Should be valid"
474
+ assert t3.save, "Should save t2 as unique"
475
+ assert !t3.errors.on(:title)
476
+ assert !t3.errors.on(:parent_id)
477
+ assert_not_equal "has already been taken", t3.errors.on(:title)
478
+ end
479
+
480
+ def test_validate_uniqueness_with_non_standard_table_names
481
+ i1 = WarehouseThing.create(:value => 1000)
482
+ assert !i1.valid?, "i1 should not be valid"
483
+ assert i1.errors.on(:value), "Should not be empty"
484
+ end
485
+
486
+ def test_validates_uniqueness_inside_with_scope
487
+ Topic.validates_uniqueness_of(:title)
488
+
489
+ Topic.with_scope(:find => { :conditions => { :author_name => "David" } }) do
490
+ t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
491
+ assert t1.save
492
+ t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
493
+ assert !t2.valid?
494
+ end
495
+ end
496
+
497
+ def test_validate_uniqueness_with_columns_which_are_sql_keywords
498
+ Guid.validates_uniqueness_of :key
499
+ g = Guid.new
500
+ g.key = "foo"
501
+ assert_nothing_raised { !g.valid? }
502
+ end
503
+
504
+ def test_validate_straight_inheritance_uniqueness
505
+ w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
506
+ assert w1.valid?, "Saving w1"
507
+
508
+ # Should use validation from base class (which is abstract)
509
+ w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm")
510
+ assert !w2.valid?, "w2 shouldn't be valid"
511
+ assert w2.errors.on(:name), "Should have errors for name"
512
+ assert_equal "has already been taken", w2.errors.on(:name), "Should have uniqueness message for name"
513
+
514
+ w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm")
515
+ assert !w3.valid?, "w3 shouldn't be valid"
516
+ assert w3.errors.on(:name), "Should have errors for name"
517
+ assert_equal "has already been taken", w3.errors.on(:name), "Should have uniqueness message for name"
518
+
519
+ w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm")
520
+ assert w4.valid?, "Saving w4"
521
+
522
+ w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre")
523
+ assert !w5.valid?, "w5 shouldn't be valid"
524
+ assert w5.errors.on(:name), "Should have errors for name"
525
+ assert_equal "has already been taken", w5.errors.on(:name), "Should have uniqueness message for name"
526
+
527
+ w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm")
528
+ assert !w6.valid?, "w6 shouldn't be valid"
529
+ assert w6.errors.on(:city), "Should have errors for city"
530
+ assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city"
531
+ end
532
+
533
+ def test_validate_format
534
+ Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
535
+
536
+ t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
537
+ assert !t.valid?, "Shouldn't be valid"
538
+ assert !t.save, "Shouldn't save because it's invalid"
539
+ assert_equal "is bad data", t.errors.on(:title)
540
+ assert_nil t.errors.on(:content)
541
+
542
+ t.title = "Validation macros rule!"
543
+
544
+ assert t.save
545
+ assert_nil t.errors.on(:title)
546
+
547
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
548
+ end
549
+
550
+ def test_validate_format_with_allow_blank
551
+ Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
552
+ assert !Topic.create("title" => "Shouldn't be valid").valid?
553
+ assert Topic.create("title" => "").valid?
554
+ assert Topic.create("title" => nil).valid?
555
+ assert Topic.create("title" => "Validation macros rule!").valid?
556
+ end
557
+
558
+ # testing ticket #3142
559
+ def test_validate_format_numeric
560
+ Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
561
+
562
+ t = Topic.create("title" => "72x", "content" => "6789")
563
+ assert !t.valid?, "Shouldn't be valid"
564
+ assert !t.save, "Shouldn't save because it's invalid"
565
+ assert_equal "is bad data", t.errors.on(:title)
566
+ assert_nil t.errors.on(:content)
567
+
568
+ t.title = "-11"
569
+ assert !t.valid?, "Shouldn't be valid"
570
+
571
+ t.title = "03"
572
+ assert !t.valid?, "Shouldn't be valid"
573
+
574
+ t.title = "z44"
575
+ assert !t.valid?, "Shouldn't be valid"
576
+
577
+ t.title = "5v7"
578
+ assert !t.valid?, "Shouldn't be valid"
579
+
580
+ t.title = "1"
581
+
582
+ assert t.save
583
+ assert_nil t.errors.on(:title)
584
+ end
585
+
586
+ def test_validate_format_with_formatted_message
587
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %s")
588
+ t = Topic.create(:title => 'Invalid title')
589
+ assert_equal "can't be Invalid title", t.errors.on(:title)
590
+ end
591
+
592
+ def test_validates_inclusion_of
593
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
594
+
595
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
596
+ assert !Topic.create("title" => "a b", "content" => "abc").valid?
597
+ assert !Topic.create("title" => nil, "content" => "def").valid?
598
+
599
+ t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
600
+ assert t.valid?
601
+ t.title = "uhoh"
602
+ assert !t.valid?
603
+ assert t.errors.on(:title)
604
+ assert_equal "is not included in the list", t.errors["title"]
605
+
606
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
607
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
608
+
609
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
610
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
611
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
612
+ end
613
+
614
+ def test_validates_inclusion_of_with_allow_nil
615
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
616
+
617
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
618
+ assert !Topic.create("title" => "", "content" => "abc").valid?
619
+ assert Topic.create("title" => nil, "content" => "abc").valid?
620
+ end
621
+
622
+ def test_numericality_with_getter_method
623
+ Developer.validates_numericality_of( :salary )
624
+ developer = Developer.new("name" => "michael", "salary" => nil)
625
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
626
+ assert developer.valid?
627
+ end
628
+
629
+ def test_validates_length_of_with_allow_nil
630
+ Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
631
+
632
+ assert !Topic.create("title" => "ab").valid?
633
+ assert !Topic.create("title" => "").valid?
634
+ assert Topic.create("title" => nil).valid?
635
+ assert Topic.create("title" => "abcde").valid?
636
+ end
637
+
638
+ def test_validates_length_of_with_allow_blank
639
+ Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
640
+
641
+ assert !Topic.create("title" => "ab").valid?
642
+ assert Topic.create("title" => "").valid?
643
+ assert Topic.create("title" => nil).valid?
644
+ assert Topic.create("title" => "abcde").valid?
645
+ end
646
+
647
+ def test_validates_inclusion_of_with_formatted_message
648
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %s is not in the list" )
649
+
650
+ assert Topic.create("title" => "a", "content" => "abc").valid?
651
+
652
+ t = Topic.create("title" => "uhoh", "content" => "abc")
653
+ assert !t.valid?
654
+ assert t.errors.on(:title)
655
+ assert_equal "option uhoh is not in the list", t.errors["title"]
656
+ end
657
+
658
+ def test_numericality_with_allow_nil_and_getter_method
659
+ Developer.validates_numericality_of( :salary, :allow_nil => true)
660
+ developer = Developer.new("name" => "michael", "salary" => nil)
661
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
662
+ assert developer.valid?
663
+ end
664
+
665
+ def test_validates_exclusion_of
666
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
667
+
668
+ assert Topic.create("title" => "something", "content" => "abc").valid?
669
+ assert !Topic.create("title" => "monkey", "content" => "abc").valid?
670
+ end
671
+
672
+ def test_validates_exclusion_of_with_formatted_message
673
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %s is restricted" )
674
+
675
+ assert Topic.create("title" => "something", "content" => "abc")
676
+
677
+ t = Topic.create("title" => "monkey")
678
+ assert !t.valid?
679
+ assert t.errors.on(:title)
680
+ assert_equal "option monkey is restricted", t.errors["title"]
681
+ end
682
+
683
+ def test_validates_length_of_using_minimum
684
+ Topic.validates_length_of :title, :minimum => 5
685
+
686
+ t = Topic.create("title" => "valid", "content" => "whatever")
687
+ assert t.valid?
688
+
689
+ t.title = "not"
690
+ assert !t.valid?
691
+ assert t.errors.on(:title)
692
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
693
+
694
+ t.title = ""
695
+ assert !t.valid?
696
+ assert t.errors.on(:title)
697
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
698
+
699
+ t.title = nil
700
+ assert !t.valid?
701
+ assert t.errors.on(:title)
702
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
703
+ end
704
+
705
+ def test_optionally_validates_length_of_using_minimum
706
+ Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
707
+
708
+ t = Topic.create("title" => "valid", "content" => "whatever")
709
+ assert t.valid?
710
+
711
+ t.title = nil
712
+ assert t.valid?
713
+ end
714
+
715
+ def test_validates_length_of_using_maximum
716
+ Topic.validates_length_of :title, :maximum => 5
717
+
718
+ t = Topic.create("title" => "valid", "content" => "whatever")
719
+ assert t.valid?
720
+
721
+ t.title = "notvalid"
722
+ assert !t.valid?
723
+ assert t.errors.on(:title)
724
+ assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
725
+
726
+ t.title = ""
727
+ assert t.valid?
728
+
729
+ t.title = nil
730
+ assert !t.valid?
731
+ end
732
+
733
+ def test_optionally_validates_length_of_using_maximum
734
+ Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
735
+
736
+ t = Topic.create("title" => "valid", "content" => "whatever")
737
+ assert t.valid?
738
+
739
+ t.title = nil
740
+ assert t.valid?
741
+ end
742
+
743
+ def test_validates_length_of_using_within
744
+ Topic.validates_length_of(:title, :content, :within => 3..5)
745
+
746
+ t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
747
+ assert !t.valid?
748
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
749
+ assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
750
+
751
+ t.title = nil
752
+ t.content = nil
753
+ assert !t.valid?
754
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
755
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content)
756
+
757
+ t.title = "abe"
758
+ t.content = "mad"
759
+ assert t.valid?
760
+ end
761
+
762
+ def test_optionally_validates_length_of_using_within
763
+ Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
764
+
765
+ t = Topic.create('title' => 'abc', 'content' => 'abcd')
766
+ assert t.valid?
767
+
768
+ t.title = nil
769
+ assert t.valid?
770
+ end
771
+
772
+ def test_optionally_validates_length_of_using_within_on_create
773
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d"
774
+
775
+ t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
776
+ assert !t.save
777
+ assert t.errors.on(:title)
778
+ assert_equal "my string is too long: 10", t.errors[:title]
779
+
780
+ t.title = "butthisis"
781
+ assert t.save
782
+
783
+ t.title = "few"
784
+ assert t.save
785
+
786
+ t.content = "andthisislong"
787
+ assert t.save
788
+
789
+ t.content = t.title = "iamfine"
790
+ assert t.save
791
+ end
792
+
793
+ def test_optionally_validates_length_of_using_within_on_update
794
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d"
795
+
796
+ t = Topic.create("title" => "vali", "content" => "whatever")
797
+ assert !t.save
798
+ assert t.errors.on(:title)
799
+
800
+ t.title = "not"
801
+ assert !t.save
802
+ assert t.errors.on(:title)
803
+ assert_equal "my string is too short: 5", t.errors[:title]
804
+
805
+ t.title = "valid"
806
+ t.content = "andthisistoolong"
807
+ assert !t.save
808
+ assert t.errors.on(:content)
809
+
810
+ t.content = "iamfine"
811
+ assert t.save
812
+ end
813
+
814
+ def test_validates_length_of_using_is
815
+ Topic.validates_length_of :title, :is => 5
816
+
817
+ t = Topic.create("title" => "valid", "content" => "whatever")
818
+ assert t.valid?
819
+
820
+ t.title = "notvalid"
821
+ assert !t.valid?
822
+ assert t.errors.on(:title)
823
+ assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
824
+
825
+ t.title = ""
826
+ assert !t.valid?
827
+
828
+ t.title = nil
829
+ assert !t.valid?
830
+ end
831
+
832
+ def test_optionally_validates_length_of_using_is
833
+ Topic.validates_length_of :title, :is => 5, :allow_nil => true
834
+
835
+ t = Topic.create("title" => "valid", "content" => "whatever")
836
+ assert t.valid?
837
+
838
+ t.title = nil
839
+ assert t.valid?
840
+ end
841
+
842
+ def test_validates_length_of_using_bignum
843
+ bigmin = 2 ** 30
844
+ bigmax = 2 ** 32
845
+ bigrange = bigmin...bigmax
846
+ assert_nothing_raised do
847
+ Topic.validates_length_of :title, :is => bigmin + 5
848
+ Topic.validates_length_of :title, :within => bigrange
849
+ Topic.validates_length_of :title, :in => bigrange
850
+ Topic.validates_length_of :title, :minimum => bigmin
851
+ Topic.validates_length_of :title, :maximum => bigmax
852
+ end
853
+ end
854
+
855
+ def test_validates_length_with_globally_modified_error_message
856
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
857
+ Topic.validates_length_of :title, :minimum => 10
858
+ t = Topic.create(:title => 'too short')
859
+ assert !t.valid?
860
+
861
+ assert_equal 'tu est trops petit hombre 10', t.errors['title']
862
+ end
863
+
864
+ def test_validates_size_of_association
865
+ assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
866
+ t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
867
+ assert !t.save
868
+ assert t.errors.on(:replies)
869
+ reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
870
+ assert t.valid?
871
+ end
872
+
873
+ def test_validates_size_of_association_using_within
874
+ assert_nothing_raised { Topic.validates_size_of :replies, :within => 1..2 }
875
+ t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
876
+ assert !t.save
877
+ assert t.errors.on(:replies)
878
+
879
+ reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
880
+ assert t.valid?
881
+
882
+ 2.times { t.replies.build('title' => 'areply', 'content' => 'whateveragain') }
883
+ assert !t.save
884
+ assert t.errors.on(:replies)
885
+ end
886
+
887
+ def test_validates_length_of_nasty_params
888
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
889
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
890
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
891
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
892
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
893
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
894
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
895
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
896
+ end
897
+
898
+ def test_validates_length_of_custom_errors_for_minimum_with_message
899
+ Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" )
900
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
901
+ assert !t.valid?
902
+ assert t.errors.on(:title)
903
+ assert_equal "boo 5", t.errors["title"]
904
+ end
905
+
906
+ def test_validates_length_of_custom_errors_for_minimum_with_too_short
907
+ Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" )
908
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
909
+ assert !t.valid?
910
+ assert t.errors.on(:title)
911
+ assert_equal "hoo 5", t.errors["title"]
912
+ end
913
+
914
+ def test_validates_length_of_custom_errors_for_maximum_with_message
915
+ Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" )
916
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
917
+ assert !t.valid?
918
+ assert t.errors.on(:title)
919
+ assert_equal "boo 5", t.errors["title"]
920
+ end
921
+
922
+ def test_validates_length_of_custom_errors_for_maximum_with_too_long
923
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" )
924
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
925
+ assert !t.valid?
926
+ assert t.errors.on(:title)
927
+ assert_equal "hoo 5", t.errors["title"]
928
+ end
929
+
930
+ def test_validates_length_of_custom_errors_for_is_with_message
931
+ Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" )
932
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
933
+ assert !t.valid?
934
+ assert t.errors.on(:title)
935
+ assert_equal "boo 5", t.errors["title"]
936
+ end
937
+
938
+ def test_validates_length_of_custom_errors_for_is_with_wrong_length
939
+ Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" )
940
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
941
+ assert !t.valid?
942
+ assert t.errors.on(:title)
943
+ assert_equal "hoo 5", t.errors["title"]
944
+ end
945
+
946
+ def test_validates_length_of_using_minimum_utf8
947
+ with_kcode('UTF8') do
948
+ Topic.validates_length_of :title, :minimum => 5
949
+
950
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
951
+ assert t.valid?
952
+
953
+ t.title = "一二三四"
954
+ assert !t.valid?
955
+ assert t.errors.on(:title)
956
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
957
+ end
958
+ end
959
+
960
+ def test_validates_length_of_using_maximum_utf8
961
+ with_kcode('UTF8') do
962
+ Topic.validates_length_of :title, :maximum => 5
963
+
964
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
965
+ assert t.valid?
966
+
967
+ t.title = "一二34五六"
968
+ assert !t.valid?
969
+ assert t.errors.on(:title)
970
+ assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
971
+ end
972
+ end
973
+
974
+ def test_validates_length_of_using_within_utf8
975
+ with_kcode('UTF8') do
976
+ Topic.validates_length_of(:title, :content, :within => 3..5)
977
+
978
+ t = Topic.new("title" => "一二", "content" => "12三四五六七")
979
+ assert !t.valid?
980
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
981
+ assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
982
+ t.title = "一二三"
983
+ t.content = "12三"
984
+ assert t.valid?
985
+ end
986
+ end
987
+
988
+ def test_optionally_validates_length_of_using_within_utf8
989
+ with_kcode('UTF8') do
990
+ Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
991
+
992
+ t = Topic.create(:title => "一二三四五")
993
+ assert t.valid?, t.errors.inspect
994
+
995
+ t = Topic.create(:title => "一二三")
996
+ assert t.valid?, t.errors.inspect
997
+
998
+ t.title = nil
999
+ assert t.valid?, t.errors.inspect
1000
+ end
1001
+ end
1002
+
1003
+ def test_optionally_validates_length_of_using_within_on_create_utf8
1004
+ with_kcode('UTF8') do
1005
+ Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: %d"
1006
+
1007
+ t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
1008
+ assert !t.save
1009
+ assert t.errors.on(:title)
1010
+ assert_equal "長すぎます: 10", t.errors[:title]
1011
+
1012
+ t.title = "一二三四五六七八九"
1013
+ assert t.save
1014
+
1015
+ t.title = "一二3"
1016
+ assert t.save
1017
+
1018
+ t.content = "一二三四五六七八九十"
1019
+ assert t.save
1020
+
1021
+ t.content = t.title = "一二三四五六"
1022
+ assert t.save
1023
+ end
1024
+ end
1025
+
1026
+ def test_optionally_validates_length_of_using_within_on_update_utf8
1027
+ with_kcode('UTF8') do
1028
+ Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: %d"
1029
+
1030
+ t = Topic.create("title" => "一二三4", "content" => "whatever")
1031
+ assert !t.save
1032
+ assert t.errors.on(:title)
1033
+
1034
+ t.title = "1二三4"
1035
+ assert !t.save
1036
+ assert t.errors.on(:title)
1037
+ assert_equal "短すぎます: 5", t.errors[:title]
1038
+
1039
+ t.title = "一二三四五六七八九十A"
1040
+ assert !t.save
1041
+ assert t.errors.on(:title)
1042
+
1043
+ t.title = "一二345"
1044
+ assert t.save
1045
+ end
1046
+ end
1047
+
1048
+ def test_validates_length_of_using_is_utf8
1049
+ with_kcode('UTF8') do
1050
+ Topic.validates_length_of :title, :is => 5
1051
+
1052
+ t = Topic.create("title" => "一二345", "content" => "whatever")
1053
+ assert t.valid?
1054
+
1055
+ t.title = "一二345六"
1056
+ assert !t.valid?
1057
+ assert t.errors.on(:title)
1058
+ assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
1059
+ end
1060
+ end
1061
+
1062
+ def test_validates_size_of_association_utf8
1063
+ with_kcode('UTF8') do
1064
+ assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
1065
+ t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
1066
+ assert !t.save
1067
+ assert t.errors.on(:replies)
1068
+ t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ')
1069
+ assert t.valid?
1070
+ end
1071
+ end
1072
+
1073
+ def test_validates_associated_many
1074
+ Topic.validates_associated( :replies )
1075
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1076
+ t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
1077
+ assert !t.valid?
1078
+ assert t.errors.on(:replies)
1079
+ assert_equal 1, r.errors.count # make sure all associated objects have been validated
1080
+ assert_equal 0, r2.errors.count
1081
+ assert_equal 1, r3.errors.count
1082
+ assert_equal 0, r4.errors.count
1083
+ r.content = r3.content = "non-empty"
1084
+ assert t.valid?
1085
+ end
1086
+
1087
+ def test_validates_associated_one
1088
+ Reply.validates_associated( :topic )
1089
+ Topic.validates_presence_of( :content )
1090
+ r = Reply.new("title" => "A reply", "content" => "with content!")
1091
+ r.topic = Topic.create("title" => "uhohuhoh")
1092
+ assert !r.valid?
1093
+ assert r.errors.on(:topic)
1094
+ r.topic.content = "non-empty"
1095
+ assert r.valid?
1096
+ end
1097
+
1098
+ def test_validate_block
1099
+ Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
1100
+ t = Topic.create("title" => "Title", "content" => "whatever")
1101
+ assert !t.valid?
1102
+ assert t.errors.on(:title)
1103
+ assert_equal "will never be valid", t.errors["title"]
1104
+ end
1105
+
1106
+ def test_invalid_validator
1107
+ Topic.validate 3
1108
+ assert_raise(ArgumentError) { t = Topic.create }
1109
+ end
1110
+
1111
+ def test_throw_away_typing
1112
+ d = Developer.new("name" => "David", "salary" => "100,000")
1113
+ assert !d.valid?
1114
+ assert_equal 100, d.salary
1115
+ assert_equal "100,000", d.salary_before_type_cast
1116
+ end
1117
+
1118
+ def test_validates_acceptance_of_with_custom_error_using_quotes
1119
+ Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
1120
+ d = Developer.new
1121
+ d.salary = "0"
1122
+ assert !d.valid?
1123
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
1124
+ end
1125
+
1126
+ def test_validates_confirmation_of_with_custom_error_using_quotes
1127
+ Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
1128
+ d = Developer.new
1129
+ d.name = "John"
1130
+ d.name_confirmation = "Johnny"
1131
+ assert !d.valid?
1132
+ assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
1133
+ end
1134
+
1135
+ def test_validates_format_of_with_custom_error_using_quotes
1136
+ Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
1137
+ d = Developer.new
1138
+ d.name = d.name_confirmation = "John 32"
1139
+ assert !d.valid?
1140
+ assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
1141
+ end
1142
+
1143
+ def test_validates_inclusion_of_with_custom_error_using_quotes
1144
+ Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
1145
+ d = Developer.new
1146
+ d.salary = "90,000"
1147
+ assert !d.valid?
1148
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
1149
+ end
1150
+
1151
+ def test_validates_length_of_with_custom_too_long_using_quotes
1152
+ Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
1153
+ d = Developer.new
1154
+ d.name = "Jeffrey"
1155
+ assert !d.valid?
1156
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
1157
+ end
1158
+
1159
+ def test_validates_length_of_with_custom_too_short_using_quotes
1160
+ Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
1161
+ d = Developer.new
1162
+ d.name = "Joe"
1163
+ assert !d.valid?
1164
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
1165
+ end
1166
+
1167
+ def test_validates_length_of_with_custom_message_using_quotes
1168
+ Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
1169
+ d = Developer.new
1170
+ d.name = "Joe"
1171
+ assert !d.valid?
1172
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
1173
+ end
1174
+
1175
+ def test_validates_presence_of_with_custom_message_using_quotes
1176
+ Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
1177
+ d = Developer.new
1178
+ d.name = "Joe"
1179
+ assert !d.valid?
1180
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
1181
+ end
1182
+
1183
+ def test_validates_uniqueness_of_with_custom_message_using_quotes
1184
+ Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
1185
+ d = Developer.new
1186
+ d.name = "David"
1187
+ assert !d.valid?
1188
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
1189
+ end
1190
+
1191
+ def test_validates_associated_with_custom_message_using_quotes
1192
+ Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
1193
+ Topic.validates_presence_of :content
1194
+ r = Reply.create("title" => "A reply", "content" => "with content!")
1195
+ r.topic = Topic.create("title" => "uhohuhoh")
1196
+ assert !r.valid?
1197
+ assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last
1198
+ end
1199
+
1200
+ def test_if_validation_using_method_true
1201
+ # When the method returns true
1202
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
1203
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1204
+ assert !t.valid?
1205
+ assert t.errors.on(:title)
1206
+ assert_equal "hoo 5", t.errors["title"]
1207
+ end
1208
+
1209
+ def test_unless_validation_using_method_true
1210
+ # When the method returns true
1211
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true )
1212
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1213
+ assert t.valid?
1214
+ assert !t.errors.on(:title)
1215
+ end
1216
+
1217
+ def test_if_validation_using_method_false
1218
+ # When the method returns false
1219
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
1220
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1221
+ assert t.valid?
1222
+ assert !t.errors.on(:title)
1223
+ end
1224
+
1225
+ def test_unless_validation_using_method_false
1226
+ # When the method returns false
1227
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true_but_its_not )
1228
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1229
+ assert !t.valid?
1230
+ assert t.errors.on(:title)
1231
+ assert_equal "hoo 5", t.errors["title"]
1232
+ end
1233
+
1234
+ def test_if_validation_using_string_true
1235
+ # When the evaluated string returns true
1236
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
1237
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1238
+ assert !t.valid?
1239
+ assert t.errors.on(:title)
1240
+ assert_equal "hoo 5", t.errors["title"]
1241
+ end
1242
+
1243
+ def test_unless_validation_using_string_true
1244
+ # When the evaluated string returns true
1245
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "a = 1; a == 1" )
1246
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1247
+ assert t.valid?
1248
+ assert !t.errors.on(:title)
1249
+ end
1250
+
1251
+ def test_if_validation_using_string_false
1252
+ # When the evaluated string returns false
1253
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
1254
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1255
+ assert t.valid?
1256
+ assert !t.errors.on(:title)
1257
+ end
1258
+
1259
+ def test_unless_validation_using_string_false
1260
+ # When the evaluated string returns false
1261
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "false")
1262
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1263
+ assert !t.valid?
1264
+ assert t.errors.on(:title)
1265
+ assert_equal "hoo 5", t.errors["title"]
1266
+ end
1267
+
1268
+ def test_if_validation_using_block_true
1269
+ # When the block returns true
1270
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
1271
+ :if => Proc.new { |r| r.content.size > 4 } )
1272
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1273
+ assert !t.valid?
1274
+ assert t.errors.on(:title)
1275
+ assert_equal "hoo 5", t.errors["title"]
1276
+ end
1277
+
1278
+ def test_unless_validation_using_block_true
1279
+ # When the block returns true
1280
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
1281
+ :unless => Proc.new { |r| r.content.size > 4 } )
1282
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1283
+ assert t.valid?
1284
+ assert !t.errors.on(:title)
1285
+ end
1286
+
1287
+ def test_if_validation_using_block_false
1288
+ # When the block returns false
1289
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
1290
+ :if => Proc.new { |r| r.title != "uhohuhoh"} )
1291
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1292
+ assert t.valid?
1293
+ assert !t.errors.on(:title)
1294
+ end
1295
+
1296
+ def test_unless_validation_using_block_false
1297
+ # When the block returns false
1298
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
1299
+ :unless => Proc.new { |r| r.title != "uhohuhoh"} )
1300
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
1301
+ assert !t.valid?
1302
+ assert t.errors.on(:title)
1303
+ assert_equal "hoo 5", t.errors["title"]
1304
+ end
1305
+
1306
+ def test_validates_associated_missing
1307
+ Reply.validates_presence_of(:topic)
1308
+ r = Reply.create("title" => "A reply", "content" => "with content!")
1309
+ assert !r.valid?
1310
+ assert r.errors.on(:topic)
1311
+
1312
+ r.topic = Topic.find :first
1313
+ assert r.valid?
1314
+ end
1315
+
1316
+ def test_errors_to_xml
1317
+ r = Reply.new :title => "Wrong Create"
1318
+ assert !r.valid?
1319
+ xml = r.errors.to_xml(:skip_instruct => true)
1320
+ assert_equal "<errors>", xml.first(8)
1321
+ assert xml.include?("<error>Title is Wrong Create</error>")
1322
+ assert xml.include?("<error>Content Empty</error>")
1323
+ end
1324
+
1325
+ def test_validation_order
1326
+ Topic.validates_presence_of :title
1327
+ Topic.validates_length_of :title, :minimum => 2
1328
+
1329
+ t = Topic.new("title" => "")
1330
+ assert !t.valid?
1331
+ assert_equal "can't be blank", t.errors.on("title").first
1332
+ end
1333
+
1334
+ # previous implementation of validates_presence_of eval'd the
1335
+ # string with the wrong binding, this regression test is to
1336
+ # ensure that it works correctly
1337
+ def test_validation_with_if_as_string
1338
+ Topic.validates_presence_of(:title)
1339
+ Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
1340
+
1341
+ t = Topic.new
1342
+ assert !t.valid?, "A topic without a title should not be valid"
1343
+ assert !t.errors.invalid?("author_name"), "A topic without an 'important' title should not require an author"
1344
+
1345
+ t.title = "Just a title"
1346
+ assert t.valid?, "A topic with a basic title should be valid"
1347
+
1348
+ t.title = "A very important title"
1349
+ assert !t.valid?, "A topic with an important title, but without an author, should not be valid"
1350
+ assert t.errors.invalid?("author_name"), "A topic with an 'important' title should require an author"
1351
+
1352
+ t.author_name = "Hubert J. Farnsworth"
1353
+ assert t.valid?, "A topic with an important title and author should be valid"
1354
+ end
1355
+
1356
+ private
1357
+ def with_kcode(kcode)
1358
+ if RUBY_VERSION < '1.9'
1359
+ orig_kcode, $KCODE = $KCODE, kcode
1360
+ begin
1361
+ yield
1362
+ ensure
1363
+ $KCODE = orig_kcode
1364
+ end
1365
+ else
1366
+ yield
1367
+ end
1368
+ end
1369
+ end
1370
+
1371
+
1372
+ class ValidatesNumericalityTest < ActiveRecord::TestCase
1373
+ NIL = [nil]
1374
+ BLANK = ["", " ", " \t \r \n"]
1375
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
1376
+ 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)
1377
+ INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
1378
+ FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
1379
+ INTEGERS = [0, 10, -10] + INTEGER_STRINGS
1380
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
1381
+ 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"]
1382
+
1383
+ def setup
1384
+ Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
1385
+ Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
1386
+ Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
1387
+ end
1388
+
1389
+ def test_default_validates_numericality_of
1390
+ Topic.validates_numericality_of :approved
1391
+
1392
+ invalid!(NIL + BLANK + JUNK)
1393
+ valid!(FLOATS + INTEGERS + BIGDECIMAL)
1394
+ end
1395
+
1396
+ def test_validates_numericality_of_with_nil_allowed
1397
+ Topic.validates_numericality_of :approved, :allow_nil => true
1398
+
1399
+ invalid!(BLANK + JUNK)
1400
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
1401
+ end
1402
+
1403
+ def test_validates_numericality_of_with_integer_only
1404
+ Topic.validates_numericality_of :approved, :only_integer => true
1405
+
1406
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
1407
+ valid!(INTEGERS)
1408
+ end
1409
+
1410
+ def test_validates_numericality_of_with_integer_only_and_nil_allowed
1411
+ Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
1412
+
1413
+ invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
1414
+ valid!(NIL + INTEGERS)
1415
+ end
1416
+
1417
+ def test_validates_numericality_with_greater_than
1418
+ Topic.validates_numericality_of :approved, :greater_than => 10
1419
+
1420
+ invalid!([-10, 10], 'must be greater than 10')
1421
+ valid!([11])
1422
+ end
1423
+
1424
+ def test_validates_numericality_with_greater_than_or_equal
1425
+ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
1426
+
1427
+ invalid!([-9, 9], 'must be greater than or equal to 10')
1428
+ valid!([10])
1429
+ end
1430
+
1431
+ def test_validates_numericality_with_equal_to
1432
+ Topic.validates_numericality_of :approved, :equal_to => 10
1433
+
1434
+ invalid!([-10, 11], 'must be equal to 10')
1435
+ valid!([10])
1436
+ end
1437
+
1438
+ def test_validates_numericality_with_less_than
1439
+ Topic.validates_numericality_of :approved, :less_than => 10
1440
+
1441
+ invalid!([10], 'must be less than 10')
1442
+ valid!([-9, 9])
1443
+ end
1444
+
1445
+ def test_validates_numericality_with_less_than_or_equal_to
1446
+ Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
1447
+
1448
+ invalid!([11], 'must be less than or equal to 10')
1449
+ valid!([-10, 10])
1450
+ end
1451
+
1452
+ def test_validates_numericality_with_odd
1453
+ Topic.validates_numericality_of :approved, :odd => true
1454
+
1455
+ invalid!([-2, 2], 'must be odd')
1456
+ valid!([-1, 1])
1457
+ end
1458
+
1459
+ def test_validates_numericality_with_even
1460
+ Topic.validates_numericality_of :approved, :even => true
1461
+
1462
+ invalid!([-1, 1], 'must be even')
1463
+ valid!([-2, 2])
1464
+ end
1465
+
1466
+ def test_validates_numericality_with_greater_than_less_than_and_even
1467
+ Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
1468
+
1469
+ invalid!([1, 3, 4])
1470
+ valid!([2])
1471
+ end
1472
+
1473
+ def test_validates_numericality_with_numeric_message
1474
+ Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %d"
1475
+ topic = Topic.new("title" => "numeric test", "approved" => 10)
1476
+
1477
+ assert !topic.valid?
1478
+ assert_equal "smaller than 4", topic.errors.on(:approved)
1479
+
1480
+ Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %d"
1481
+ topic = Topic.new("title" => "numeric test", "approved" => 1)
1482
+
1483
+ assert !topic.valid?
1484
+ assert_equal "greater than 4", topic.errors.on(:approved)
1485
+ end
1486
+
1487
+ private
1488
+ def invalid!(values, error=nil)
1489
+ with_each_topic_approved_value(values) do |topic, value|
1490
+ assert !topic.valid?, "#{value.inspect} not rejected as a number"
1491
+ assert topic.errors.on(:approved)
1492
+ assert_equal error, topic.errors.on(:approved) if error
1493
+ end
1494
+ end
1495
+
1496
+ def valid!(values)
1497
+ with_each_topic_approved_value(values) do |topic, value|
1498
+ assert topic.valid?, "#{value.inspect} not accepted as a number"
1499
+ end
1500
+ end
1501
+
1502
+ def with_each_topic_approved_value(values)
1503
+ topic = Topic.new("title" => "numeric test", "content" => "whatever")
1504
+ values.each do |value|
1505
+ topic.approved = value
1506
+ yield topic, value
1507
+ end
1508
+ end
1509
+ end