ibm_db 2.5.17-universal-darwin-13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +221 -0
  3. data/LICENSE +18 -0
  4. data/MANIFEST +14 -0
  5. data/ParameterizedQueries README +39 -0
  6. data/README +225 -0
  7. data/ext/Makefile.nt32 +181 -0
  8. data/ext/Makefile.nt32.191 +212 -0
  9. data/ext/extconf.rb +127 -0
  10. data/ext/ibm_db.c +11719 -0
  11. data/ext/ruby_ibm_db.h +240 -0
  12. data/ext/ruby_ibm_db_cli.c +845 -0
  13. data/ext/ruby_ibm_db_cli.h +489 -0
  14. data/init.rb +42 -0
  15. data/lib/IBM_DB.rb +16 -0
  16. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3031 -0
  17. data/lib/active_record/connection_adapters/ibm_db_pstmt.rb +1965 -0
  18. data/lib/active_record/connection_adapters/ibmdb_adapter.rb +2 -0
  19. data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -0
  20. data/lib/linux/rb18x/ibm_db.bundle +0 -0
  21. data/lib/linux/rb19x/ibm_db.bundle +0 -0
  22. data/lib/linux/rb20x/ibm_db.bundle +0 -0
  23. data/lib/linux/rb21x/ibm_db.bundle +0 -0
  24. data/test/cases/adapter_test.rb +207 -0
  25. data/test/cases/associations/belongs_to_associations_test.rb +711 -0
  26. data/test/cases/associations/cascaded_eager_loading_test.rb +181 -0
  27. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +851 -0
  28. data/test/cases/associations/join_model_test.rb +743 -0
  29. data/test/cases/attribute_methods_test.rb +822 -0
  30. data/test/cases/base_test.rb +2133 -0
  31. data/test/cases/calculations_test.rb +482 -0
  32. data/test/cases/migration_test.rb +2408 -0
  33. data/test/cases/persistence_test.rb +642 -0
  34. data/test/cases/query_cache_test.rb +257 -0
  35. data/test/cases/relations_test.rb +1182 -0
  36. data/test/cases/schema_dumper_test.rb +256 -0
  37. data/test/cases/transaction_callbacks_test.rb +300 -0
  38. data/test/cases/validations/uniqueness_validation_test.rb +299 -0
  39. data/test/cases/xml_serialization_test.rb +408 -0
  40. data/test/config.yml +154 -0
  41. data/test/connections/native_ibm_db/connection.rb +44 -0
  42. data/test/ibm_db_test.rb +25 -0
  43. data/test/models/warehouse_thing.rb +5 -0
  44. data/test/schema/i5/ibm_db_specific_schema.rb +137 -0
  45. data/test/schema/ids/ibm_db_specific_schema.rb +140 -0
  46. data/test/schema/luw/ibm_db_specific_schema.rb +137 -0
  47. data/test/schema/schema.rb +751 -0
  48. data/test/schema/zOS/ibm_db_specific_schema.rb +208 -0
  49. metadata +109 -0
@@ -0,0 +1,2133 @@
1
+ require "cases/helper"
2
+ require 'models/post'
3
+ require 'models/author'
4
+ require 'models/topic'
5
+ require 'models/reply'
6
+ require 'models/category'
7
+ require 'models/company'
8
+ require 'models/customer'
9
+ require 'models/developer'
10
+ require 'models/project'
11
+ require 'models/default'
12
+ require 'models/auto_id'
13
+ require 'models/boolean'
14
+ require 'models/column_name'
15
+ require 'models/subscriber'
16
+ require 'models/keyboard'
17
+ require 'models/comment'
18
+ require 'models/minimalistic'
19
+ require 'models/warehouse_thing'
20
+ require 'models/parrot'
21
+ require 'models/person'
22
+ require 'models/edge'
23
+ require 'models/joke'
24
+ require 'models/bulb'
25
+ require 'models/bird'
26
+ require 'rexml/document'
27
+ require 'active_support/core_ext/exception'
28
+ require 'bcrypt'
29
+
30
+ class FirstAbstractClass < ActiveRecord::Base
31
+ self.abstract_class = true
32
+ end
33
+ class SecondAbstractClass < FirstAbstractClass
34
+ self.abstract_class = true
35
+ end
36
+ class Photo < SecondAbstractClass; end
37
+ class Category < ActiveRecord::Base; end
38
+ class Categorization < ActiveRecord::Base; end
39
+ class Smarts < ActiveRecord::Base; end
40
+ class CreditCard < ActiveRecord::Base
41
+ class PinNumber < ActiveRecord::Base
42
+ class CvvCode < ActiveRecord::Base; end
43
+ class SubCvvCode < CvvCode; end
44
+ end
45
+ class SubPinNumber < PinNumber; end
46
+ class Brand < Category; end
47
+ end
48
+ class MasterCreditCard < ActiveRecord::Base; end
49
+ class Post < ActiveRecord::Base; end
50
+ class Computer < ActiveRecord::Base; end
51
+ class NonExistentTable < ActiveRecord::Base; end
52
+ class TestOracleDefault < ActiveRecord::Base; end
53
+
54
+ class ReadonlyTitlePost < Post
55
+ attr_readonly :title
56
+ end
57
+
58
+ class ProtectedTitlePost < Post
59
+ attr_protected :title
60
+ end
61
+
62
+ class Weird < ActiveRecord::Base; end
63
+
64
+ class Boolean < ActiveRecord::Base; end
65
+
66
+ class LintTest < ActiveRecord::TestCase
67
+ include ActiveModel::Lint::Tests
68
+
69
+ class LintModel < ActiveRecord::Base; end
70
+
71
+ def setup
72
+ @model = LintModel.new
73
+ end
74
+ end
75
+
76
+ class BasicsTest < ActiveRecord::TestCase
77
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse_things', :authors, :categorizations, :categories, :posts
78
+
79
+ def test_generated_methods_modules
80
+ modules = Computer.ancestors
81
+ assert modules.include?(Computer::GeneratedFeatureMethods)
82
+ assert_equal(Computer::GeneratedFeatureMethods, Computer.generated_feature_methods)
83
+ assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods),
84
+ "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods")
85
+ assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods
86
+ end
87
+
88
+ def test_column_names_are_escaped
89
+ conn = ActiveRecord::Base.connection
90
+ classname = conn.class.name[/[^:]*$/]
91
+ badchar = {
92
+ 'SQLite3Adapter' => '"',
93
+ 'MysqlAdapter' => '`',
94
+ 'Mysql2Adapter' => '`',
95
+ 'PostgreSQLAdapter' => '"',
96
+ 'OracleAdapter' => '"',
97
+ 'IBM_DBAdapter' => '"'
98
+ }.fetch(classname) {
99
+ raise "need a bad char for #{classname}"
100
+ }
101
+
102
+ quoted = conn.quote_column_name "foo#{badchar}bar"
103
+ if current_adapter?(:OracleAdapter)
104
+ # Oracle does not allow double quotes in table and column names at all
105
+ # therefore quoting removes them
106
+ assert_equal("#{badchar}foobar#{badchar}", quoted)
107
+ elsif current_adapter?(:IBM_DBAdapter)
108
+ assert_equal("foo#{badchar}bar", quoted)
109
+ else
110
+ assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
111
+ end
112
+ end
113
+
114
+ def test_columns_should_obey_set_primary_key
115
+ pk = Subscriber.columns.find { |x| x.name == 'nick' }
116
+ assert pk.primary, 'nick should be primary key'
117
+ end
118
+
119
+ def test_primary_key_with_no_id
120
+ assert_nil Edge.primary_key
121
+ end
122
+
123
+ unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter, :IBM_DBAdapter)
124
+ def test_limit_with_comma
125
+ assert_nothing_raised do
126
+ Topic.limit("1,2").all
127
+ end
128
+ end
129
+ end
130
+
131
+ def test_limit_without_comma
132
+ assert_nothing_raised do
133
+ assert_equal 1, Topic.limit("1").all.length
134
+ end
135
+
136
+ assert_nothing_raised do
137
+ assert_equal 1, Topic.limit(1).all.length
138
+ end
139
+ end
140
+
141
+ def test_invalid_limit
142
+ assert_raises(ArgumentError) do
143
+ Topic.limit("asdfadf").all
144
+ end
145
+ end
146
+
147
+ def test_limit_should_sanitize_sql_injection_for_limit_without_comas
148
+ assert_raises(ArgumentError) do
149
+ Topic.limit("1 select * from schema").all
150
+ end
151
+ end
152
+
153
+ def test_limit_should_sanitize_sql_injection_for_limit_with_comas
154
+ assert_raises(ArgumentError) do
155
+ Topic.limit("1, 7 procedure help()").all
156
+ end
157
+ end
158
+
159
+ unless current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:IBM_DBAdapter)
160
+ def test_limit_should_allow_sql_literal
161
+ assert_equal 1, Topic.limit(Arel.sql('2-1')).all.length
162
+ end
163
+ end
164
+
165
+ def test_select_symbol
166
+ topic_ids = Topic.select(:id).map(&:id).sort
167
+ assert_equal Topic.all.map(&:id).sort, topic_ids
168
+ end
169
+
170
+ def test_table_exists
171
+ assert !NonExistentTable.table_exists?
172
+ assert Topic.table_exists?
173
+ end
174
+
175
+ def test_preserving_date_objects
176
+ if current_adapter?(:SybaseAdapter)
177
+ # Sybase ctlib does not (yet?) support the date type; use datetime instead.
178
+ assert_kind_of(
179
+ Time, Topic.find(1).last_read,
180
+ "The last_read attribute should be of the Time class"
181
+ )
182
+ else
183
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
184
+ assert_kind_of(
185
+ Date, Topic.find(1).last_read,
186
+ "The last_read attribute should be of the Date class"
187
+ )
188
+ end
189
+ end
190
+
191
+ def test_preserving_time_objects
192
+ assert_kind_of(
193
+ Time, Topic.find(1).bonus_time,
194
+ "The bonus_time attribute should be of the Time class"
195
+ )
196
+
197
+ assert_kind_of(
198
+ Time, Topic.find(1).written_on,
199
+ "The written_on attribute should be of the Time class"
200
+ )
201
+
202
+ # For adapters which support microsecond resolution.
203
+ if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter)
204
+ assert_equal 11, Topic.find(1).written_on.sec
205
+ assert_equal 223300, Topic.find(1).written_on.usec
206
+ assert_equal 9900, Topic.find(2).written_on.usec
207
+ end
208
+ end
209
+
210
+ def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
211
+ with_env_tz 'America/New_York' do
212
+ with_active_record_default_timezone :utc do
213
+ time = Time.local(2000)
214
+ topic = Topic.create('written_on' => time)
215
+ saved_time = Topic.find(topic.id).reload.written_on
216
+ assert_equal time, saved_time
217
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
218
+ assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
219
+ end
220
+ end
221
+ end
222
+
223
+ def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
224
+ with_env_tz 'America/New_York' do
225
+ with_active_record_default_timezone :utc do
226
+ Time.use_zone 'Central Time (US & Canada)' do
227
+ time = Time.zone.local(2000)
228
+ topic = Topic.create('written_on' => time)
229
+ saved_time = Topic.find(topic.id).reload.written_on
230
+ assert_equal time, saved_time
231
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
232
+ assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
239
+ with_env_tz 'America/New_York' do
240
+ time = Time.utc(2000)
241
+ topic = Topic.create('written_on' => time)
242
+ saved_time = Topic.find(topic.id).reload.written_on
243
+ assert_equal time, saved_time
244
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
245
+ assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
246
+ end
247
+ end
248
+
249
+ def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
250
+ with_env_tz 'America/New_York' do
251
+ with_active_record_default_timezone :local do
252
+ Time.use_zone 'Central Time (US & Canada)' do
253
+ time = Time.zone.local(2000)
254
+ topic = Topic.create('written_on' => time)
255
+ saved_time = Topic.find(topic.id).reload.written_on
256
+ assert_equal time, saved_time
257
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
258
+ assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ def test_custom_mutator
265
+ topic = Topic.find(1)
266
+ # This mutator is protected in the class definition
267
+ topic.send(:approved=, true)
268
+ assert topic.instance_variable_get("@custom_approved")
269
+ end
270
+
271
+ def test_initialize_with_attributes
272
+ topic = Topic.new({
273
+ "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
274
+ })
275
+
276
+ assert_equal("initialized from attributes", topic.title)
277
+ end
278
+
279
+ def test_initialize_with_invalid_attribute
280
+ begin
281
+ Topic.new({ "title" => "test",
282
+ "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
283
+ rescue ActiveRecord::MultiparameterAssignmentErrors => ex
284
+ assert_equal(1, ex.errors.size)
285
+ assert_equal("last_read", ex.errors[0].attribute)
286
+ end
287
+ end
288
+
289
+ def test_create_after_initialize_without_block
290
+ cb = CustomBulb.create(:name => 'Dude')
291
+ assert_equal('Dude', cb.name)
292
+ assert_equal(true, cb.frickinawesome)
293
+ end
294
+
295
+ def test_create_after_initialize_with_block
296
+ cb = CustomBulb.create {|c| c.name = 'Dude' }
297
+ assert_equal('Dude', cb.name)
298
+ assert_equal(true, cb.frickinawesome)
299
+ end
300
+
301
+ def test_first_or_create
302
+ parrot = Bird.first_or_create(:color => 'green', :name => 'parrot')
303
+ assert parrot.persisted?
304
+ the_same_parrot = Bird.first_or_create(:color => 'yellow', :name => 'macaw')
305
+ assert_equal parrot, the_same_parrot
306
+ end
307
+
308
+ def test_first_or_create_bang
309
+ assert_raises(ActiveRecord::RecordInvalid) { Bird.first_or_create! }
310
+ parrot = Bird.first_or_create!(:color => 'green', :name => 'parrot')
311
+ assert parrot.persisted?
312
+ the_same_parrot = Bird.first_or_create!(:color => 'yellow', :name => 'macaw')
313
+ assert_equal parrot, the_same_parrot
314
+ end
315
+
316
+ def test_first_or_initialize
317
+ parrot = Bird.first_or_initialize(:color => 'green', :name => 'parrot')
318
+ assert_kind_of Bird, parrot
319
+ assert !parrot.persisted?
320
+ assert parrot.new_record?
321
+ assert parrot.valid?
322
+ end
323
+
324
+ def test_load
325
+ topics = Topic.find(:all, :order => 'id')
326
+ assert_equal(4, topics.size)
327
+ assert_equal(topics(:first).title, topics.first.title)
328
+ end
329
+
330
+ def test_load_with_condition
331
+ topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
332
+
333
+ assert_equal(1, topics.size)
334
+ assert_equal(topics(:second).title, topics.first.title)
335
+ end
336
+
337
+ GUESSED_CLASSES = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
338
+
339
+ def test_table_name_guesses
340
+ assert_equal "topics", Topic.table_name
341
+
342
+ assert_equal "categories", Category.table_name
343
+ assert_equal "smarts", Smarts.table_name
344
+ assert_equal "credit_cards", CreditCard.table_name
345
+ assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
346
+ assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name
347
+ assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name
348
+ assert_equal "categories", CreditCard::Brand.table_name
349
+ assert_equal "master_credit_cards", MasterCreditCard.table_name
350
+ ensure
351
+ GUESSED_CLASSES.each(&:reset_table_name)
352
+ end
353
+
354
+ def test_singular_table_name_guesses
355
+ ActiveRecord::Base.pluralize_table_names = false
356
+ GUESSED_CLASSES.each(&:reset_table_name)
357
+
358
+ assert_equal "category", Category.table_name
359
+ assert_equal "smarts", Smarts.table_name
360
+ assert_equal "credit_card", CreditCard.table_name
361
+ assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
362
+ assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name
363
+ assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name
364
+ assert_equal "category", CreditCard::Brand.table_name
365
+ assert_equal "master_credit_card", MasterCreditCard.table_name
366
+ ensure
367
+ ActiveRecord::Base.pluralize_table_names = true
368
+ GUESSED_CLASSES.each(&:reset_table_name)
369
+ end
370
+
371
+ def test_table_name_guesses_with_prefixes_and_suffixes
372
+ ActiveRecord::Base.table_name_prefix = "test_"
373
+ Category.reset_table_name
374
+ assert_equal "test_categories", Category.table_name
375
+ ActiveRecord::Base.table_name_suffix = "_test"
376
+ Category.reset_table_name
377
+ assert_equal "test_categories_test", Category.table_name
378
+ ActiveRecord::Base.table_name_prefix = ""
379
+ Category.reset_table_name
380
+ assert_equal "categories_test", Category.table_name
381
+ ActiveRecord::Base.table_name_suffix = ""
382
+ Category.reset_table_name
383
+ assert_equal "categories", Category.table_name
384
+ ensure
385
+ ActiveRecord::Base.table_name_prefix = ""
386
+ ActiveRecord::Base.table_name_suffix = ""
387
+ GUESSED_CLASSES.each(&:reset_table_name)
388
+ end
389
+
390
+ def test_singular_table_name_guesses_with_prefixes_and_suffixes
391
+ ActiveRecord::Base.pluralize_table_names = false
392
+
393
+ ActiveRecord::Base.table_name_prefix = "test_"
394
+ Category.reset_table_name
395
+ assert_equal "test_category", Category.table_name
396
+ ActiveRecord::Base.table_name_suffix = "_test"
397
+ Category.reset_table_name
398
+ assert_equal "test_category_test", Category.table_name
399
+ ActiveRecord::Base.table_name_prefix = ""
400
+ Category.reset_table_name
401
+ assert_equal "category_test", Category.table_name
402
+ ActiveRecord::Base.table_name_suffix = ""
403
+ Category.reset_table_name
404
+ assert_equal "category", Category.table_name
405
+ ensure
406
+ ActiveRecord::Base.pluralize_table_names = true
407
+ ActiveRecord::Base.table_name_prefix = ""
408
+ ActiveRecord::Base.table_name_suffix = ""
409
+ GUESSED_CLASSES.each(&:reset_table_name)
410
+ end
411
+
412
+ def test_table_name_guesses_with_inherited_prefixes_and_suffixes
413
+ GUESSED_CLASSES.each(&:reset_table_name)
414
+
415
+ CreditCard.table_name_prefix = "test_"
416
+ CreditCard.reset_table_name
417
+ Category.reset_table_name
418
+ assert_equal "test_credit_cards", CreditCard.table_name
419
+ assert_equal "categories", Category.table_name
420
+ CreditCard.table_name_suffix = "_test"
421
+ CreditCard.reset_table_name
422
+ Category.reset_table_name
423
+ assert_equal "test_credit_cards_test", CreditCard.table_name
424
+ assert_equal "categories", Category.table_name
425
+ CreditCard.table_name_prefix = ""
426
+ CreditCard.reset_table_name
427
+ Category.reset_table_name
428
+ assert_equal "credit_cards_test", CreditCard.table_name
429
+ assert_equal "categories", Category.table_name
430
+ CreditCard.table_name_suffix = ""
431
+ CreditCard.reset_table_name
432
+ Category.reset_table_name
433
+ assert_equal "credit_cards", CreditCard.table_name
434
+ assert_equal "categories", Category.table_name
435
+ ensure
436
+ CreditCard.table_name_prefix = ""
437
+ CreditCard.table_name_suffix = ""
438
+ GUESSED_CLASSES.each(&:reset_table_name)
439
+ end
440
+
441
+ def test_singular_table_name_guesses_for_individual_table
442
+ CreditCard.pluralize_table_names = false
443
+ CreditCard.reset_table_name
444
+ assert_equal "credit_card", CreditCard.table_name
445
+ assert_equal "categories", Category.table_name
446
+ ensure
447
+ CreditCard.pluralize_table_names = true
448
+ CreditCard.reset_table_name
449
+ end
450
+
451
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
452
+ def test_update_all_with_order_and_limit
453
+ assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
454
+ end
455
+ end
456
+
457
+ def test_null_fields
458
+ assert_nil Topic.find(1).parent_id
459
+ assert_nil Topic.create("title" => "Hey you").parent_id
460
+ end
461
+
462
+ def test_default_values
463
+ topic = Topic.new
464
+ assert topic.approved?
465
+ assert_nil topic.written_on
466
+ assert_nil topic.bonus_time
467
+ assert_nil topic.last_read
468
+
469
+ topic.save
470
+
471
+ topic = Topic.find(topic.id)
472
+ assert topic.approved?
473
+ assert_nil topic.last_read
474
+
475
+ # Oracle has some funky default handling, so it requires a bit of
476
+ # extra testing. See ticket #2788.
477
+ if current_adapter?(:OracleAdapter)
478
+ test = TestOracleDefault.new
479
+ assert_equal "X", test.test_char
480
+ assert_equal "hello", test.test_string
481
+ assert_equal 3, test.test_int
482
+ end
483
+ end
484
+
485
+ # Oracle, and Sybase do not have a TIME datatype.
486
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter)
487
+ def test_utc_as_time_zone
488
+ Topic.default_timezone = :utc
489
+ attributes = { "bonus_time" => "5:42:00AM" }
490
+ topic = Topic.find(1)
491
+ topic.attributes = attributes
492
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
493
+ Topic.default_timezone = :local
494
+ end
495
+
496
+ def test_utc_as_time_zone_and_new
497
+ Topic.default_timezone = :utc
498
+ attributes = { "bonus_time(1i)"=>"2000",
499
+ "bonus_time(2i)"=>"1",
500
+ "bonus_time(3i)"=>"1",
501
+ "bonus_time(4i)"=>"10",
502
+ "bonus_time(5i)"=>"35",
503
+ "bonus_time(6i)"=>"50" }
504
+ topic = Topic.new(attributes)
505
+ assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
506
+ Topic.default_timezone = :local
507
+ end
508
+ end
509
+
510
+ def test_default_values_on_empty_strings
511
+ topic = Topic.new
512
+ topic.approved = nil
513
+ topic.last_read = nil
514
+
515
+ topic.save
516
+
517
+ topic = Topic.find(topic.id)
518
+ assert_nil topic.last_read
519
+
520
+ # Sybase adapter does not allow nulls in boolean columns
521
+ if current_adapter?(:SybaseAdapter)
522
+ assert topic.approved == false
523
+ else
524
+ assert_nil topic.approved
525
+ end
526
+ end
527
+
528
+ def test_equality
529
+ assert_equal Topic.find(1), Topic.find(2).topic
530
+ end
531
+
532
+ def test_find_by_slug
533
+ assert_equal Topic.find('1-meowmeow'), Topic.find(1)
534
+ end
535
+
536
+ def test_equality_of_new_records
537
+ assert_not_equal Topic.new, Topic.new
538
+ end
539
+
540
+ def test_equality_of_destroyed_records
541
+ topic_1 = Topic.new(:title => 'test_1')
542
+ topic_1.save
543
+ topic_2 = Topic.find(topic_1.id)
544
+ topic_1.destroy
545
+ assert_equal topic_1, topic_2
546
+ assert_equal topic_2, topic_1
547
+ end
548
+
549
+ def test_hashing
550
+ assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
551
+ end
552
+
553
+ def test_comparison
554
+ topic_1 = Topic.create!
555
+ topic_2 = Topic.create!
556
+
557
+ assert_equal [topic_2, topic_1].sort, [topic_1, topic_2]
558
+ end
559
+
560
+ def test_comparison_with_different_objects
561
+ topic = Topic.create
562
+ category = Category.create(:name => "comparison")
563
+ assert_nil topic <=> category
564
+ end
565
+
566
+ def test_readonly_attributes
567
+ assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
568
+
569
+ post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
570
+ post.reload
571
+ assert_equal "cannot change this", post.title
572
+
573
+ post.update_attributes(:title => "try to change", :body => "changed")
574
+ post.reload
575
+ assert_equal "cannot change this", post.title
576
+ assert_equal "changed", post.body
577
+ end
578
+
579
+ def test_non_valid_identifier_column_name
580
+ weird = Weird.create('a$b' => 'value')
581
+ weird.reload
582
+ assert_equal 'value', weird.send('a$b')
583
+ assert_equal 'value', weird.read_attribute('a$b')
584
+
585
+ weird.update_column('a$b', 'value2')
586
+ weird.reload
587
+ assert_equal 'value2', weird.send('a$b')
588
+ assert_equal 'value2', weird.read_attribute('a$b')
589
+ end
590
+
591
+ def test_multiparameter_attributes_on_date
592
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
593
+ topic = Topic.find(1)
594
+ topic.attributes = attributes
595
+ # note that extra #to_date call allows test to pass for Oracle, which
596
+ # treats dates/times the same
597
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
598
+ end
599
+
600
+ def test_multiparameter_attributes_on_date_with_empty_year
601
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
602
+ topic = Topic.find(1)
603
+ topic.attributes = attributes
604
+ # note that extra #to_date call allows test to pass for Oracle, which
605
+ # treats dates/times the same
606
+ assert_nil topic.last_read
607
+ end
608
+
609
+ def test_multiparameter_attributes_on_date_with_empty_month
610
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
611
+ topic = Topic.find(1)
612
+ topic.attributes = attributes
613
+ # note that extra #to_date call allows test to pass for Oracle, which
614
+ # treats dates/times the same
615
+ assert_nil topic.last_read
616
+ end
617
+
618
+ def test_multiparameter_attributes_on_date_with_empty_day
619
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
620
+ topic = Topic.find(1)
621
+ topic.attributes = attributes
622
+ # note that extra #to_date call allows test to pass for Oracle, which
623
+ # treats dates/times the same
624
+ assert_nil topic.last_read
625
+ end
626
+
627
+ def test_multiparameter_attributes_on_date_with_empty_day_and_year
628
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
629
+ topic = Topic.find(1)
630
+ topic.attributes = attributes
631
+ # note that extra #to_date call allows test to pass for Oracle, which
632
+ # treats dates/times the same
633
+ assert_nil topic.last_read
634
+ end
635
+
636
+ def test_multiparameter_attributes_on_date_with_empty_day_and_month
637
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
638
+ topic = Topic.find(1)
639
+ topic.attributes = attributes
640
+ # note that extra #to_date call allows test to pass for Oracle, which
641
+ # treats dates/times the same
642
+ assert_nil topic.last_read
643
+ end
644
+
645
+ def test_multiparameter_attributes_on_date_with_empty_year_and_month
646
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
647
+ topic = Topic.find(1)
648
+ topic.attributes = attributes
649
+ # note that extra #to_date call allows test to pass for Oracle, which
650
+ # treats dates/times the same
651
+ assert_nil topic.last_read
652
+ end
653
+
654
+ def test_multiparameter_attributes_on_date_with_all_empty
655
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
656
+ topic = Topic.find(1)
657
+ topic.attributes = attributes
658
+ assert_nil topic.last_read
659
+ end
660
+
661
+ def test_multiparameter_attributes_on_time
662
+ attributes = {
663
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
664
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
665
+ }
666
+ topic = Topic.find(1)
667
+ topic.attributes = attributes
668
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
669
+ end
670
+
671
+ def test_multiparameter_attributes_on_time_with_no_date
672
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
673
+ attributes = {
674
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
675
+ }
676
+ topic = Topic.find(1)
677
+ topic.attributes = attributes
678
+ end
679
+ assert_equal("written_on", ex.errors[0].attribute)
680
+ end
681
+
682
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
683
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
684
+ attributes = {
685
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
686
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
687
+ }
688
+ topic = Topic.find(1)
689
+ topic.attributes = attributes
690
+ end
691
+ assert_equal("written_on", ex.errors[0].attribute)
692
+ end
693
+
694
+ def test_multiparameter_attributes_on_time_with_old_date
695
+ attributes = {
696
+ "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
697
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
698
+ }
699
+ topic = Topic.find(1)
700
+ topic.attributes = attributes
701
+ # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform
702
+ assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
703
+ end
704
+
705
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
706
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
707
+ attributes = {
708
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
709
+ }
710
+ topic = Topic.find(1)
711
+ topic.attributes = attributes
712
+ end
713
+ assert_equal("written_on", ex.errors[0].attribute)
714
+ end
715
+
716
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
717
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
718
+ attributes = {
719
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
720
+ }
721
+ topic = Topic.find(1)
722
+ topic.attributes = attributes
723
+ end
724
+ assert_equal("written_on", ex.errors[0].attribute)
725
+ end
726
+
727
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
728
+ attributes = {
729
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
730
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
731
+ }
732
+ topic = Topic.find(1)
733
+ topic.attributes = attributes
734
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
735
+ end
736
+
737
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
738
+ attributes = {
739
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
740
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
741
+ }
742
+ topic = Topic.find(1)
743
+ topic.attributes = attributes
744
+ assert_nil topic.written_on
745
+ end
746
+
747
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
748
+ attributes = {
749
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
750
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
751
+ }
752
+ topic = Topic.find(1)
753
+ topic.attributes = attributes
754
+ assert_nil topic.written_on
755
+ end
756
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
757
+ attributes = {
758
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
759
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
760
+ }
761
+ topic = Topic.find(1)
762
+ topic.attributes = attributes
763
+ assert_nil topic.written_on
764
+ end
765
+
766
+ def test_multiparameter_attributes_on_time_with_utc
767
+ ActiveRecord::Base.default_timezone = :utc
768
+ attributes = {
769
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
770
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
771
+ }
772
+ topic = Topic.find(1)
773
+ topic.attributes = attributes
774
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
775
+ ensure
776
+ ActiveRecord::Base.default_timezone = :local
777
+ end
778
+
779
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
780
+ ActiveRecord::Base.time_zone_aware_attributes = true
781
+ ActiveRecord::Base.default_timezone = :utc
782
+ Time.zone = ActiveSupport::TimeZone[-28800]
783
+ attributes = {
784
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
785
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
786
+ }
787
+ topic = Topic.find(1)
788
+ topic.attributes = attributes
789
+ assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
790
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
791
+ assert_equal Time.zone, topic.written_on.time_zone
792
+ ensure
793
+ ActiveRecord::Base.time_zone_aware_attributes = false
794
+ ActiveRecord::Base.default_timezone = :local
795
+ Time.zone = nil
796
+ end
797
+
798
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
799
+ ActiveRecord::Base.time_zone_aware_attributes = false
800
+ Time.zone = ActiveSupport::TimeZone[-28800]
801
+ attributes = {
802
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
803
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
804
+ }
805
+ topic = Topic.find(1)
806
+ topic.attributes = attributes
807
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
808
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
809
+ ensure
810
+ Time.zone = nil
811
+ end
812
+
813
+ def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
814
+ ActiveRecord::Base.time_zone_aware_attributes = true
815
+ ActiveRecord::Base.default_timezone = :utc
816
+ Time.zone = ActiveSupport::TimeZone[-28800]
817
+ Topic.skip_time_zone_conversion_for_attributes = [:written_on]
818
+ attributes = {
819
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
820
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
821
+ }
822
+ topic = Topic.find(1)
823
+ topic.attributes = attributes
824
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
825
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
826
+ ensure
827
+ ActiveRecord::Base.time_zone_aware_attributes = false
828
+ ActiveRecord::Base.default_timezone = :local
829
+ Time.zone = nil
830
+ Topic.skip_time_zone_conversion_for_attributes = []
831
+ end
832
+
833
+ # Oracle, and Sybase do not have a TIME datatype.
834
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter)
835
+ def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
836
+ ActiveRecord::Base.time_zone_aware_attributes = true
837
+ ActiveRecord::Base.default_timezone = :utc
838
+ Time.zone = ActiveSupport::TimeZone[-28800]
839
+ attributes = {
840
+ "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
841
+ "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
842
+ }
843
+ topic = Topic.find(1)
844
+ topic.attributes = attributes
845
+ assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
846
+ assert topic.bonus_time.utc?
847
+ ensure
848
+ ActiveRecord::Base.time_zone_aware_attributes = false
849
+ ActiveRecord::Base.default_timezone = :local
850
+ Time.zone = nil
851
+ end
852
+ end
853
+
854
+ def test_multiparameter_attributes_on_time_with_empty_seconds
855
+ attributes = {
856
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
857
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
858
+ }
859
+ topic = Topic.find(1)
860
+ topic.attributes = attributes
861
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
862
+ end
863
+
864
+ def test_multiparameter_assignment_of_aggregation
865
+ customer = Customer.new
866
+ address = Address.new("The Street", "The City", "The Country")
867
+ attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
868
+ customer.attributes = attributes
869
+ assert_equal address, customer.address
870
+ end
871
+
872
+ def test_multiparameter_assignment_of_aggregation_out_of_order
873
+ customer = Customer.new
874
+ address = Address.new("The Street", "The City", "The Country")
875
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
876
+ customer.attributes = attributes
877
+ assert_equal address, customer.address
878
+ end
879
+
880
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
881
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
882
+ customer = Customer.new
883
+ address = Address.new("The Street", "The City", "The Country")
884
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
885
+ customer.attributes = attributes
886
+ end
887
+ assert_equal("address", ex.errors[0].attribute)
888
+ end
889
+
890
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
891
+ customer = Customer.new
892
+ address = Address.new("The Street", "The City", "The Country")
893
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
894
+ customer.attributes = attributes
895
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
896
+ end
897
+
898
+ def test_multiparameter_assignment_of_aggregation_with_large_index
899
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
900
+ customer = Customer.new
901
+ address = Address.new("The Street", "The City", "The Country")
902
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
903
+ customer.attributes = attributes
904
+ end
905
+ assert_equal("address", ex.errors[0].attribute)
906
+ end
907
+
908
+ def test_attributes_on_dummy_time
909
+ # Oracle, and Sybase do not have a TIME datatype.
910
+ return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
911
+
912
+ attributes = {
913
+ "bonus_time" => "5:42:00AM"
914
+ }
915
+ topic = Topic.find(1)
916
+ topic.attributes = attributes
917
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
918
+ end
919
+
920
+ def test_boolean
921
+ b_nil = Boolean.create({ "value" => nil })
922
+ nil_id = b_nil.id
923
+ b_false = Boolean.create({ "value" => false })
924
+ false_id = b_false.id
925
+ b_true = Boolean.create({ "value" => true })
926
+ true_id = b_true.id
927
+
928
+ b_nil = Boolean.find(nil_id)
929
+ assert_nil b_nil.value
930
+ b_false = Boolean.find(false_id)
931
+ assert !b_false.value?
932
+ b_true = Boolean.find(true_id)
933
+ assert b_true.value?
934
+ end
935
+
936
+ def test_boolean_cast_from_string
937
+ b_blank = Boolean.create({ "value" => "" })
938
+ blank_id = b_blank.id
939
+ b_false = Boolean.create({ "value" => "0" })
940
+ false_id = b_false.id
941
+ b_true = Boolean.create({ "value" => "1" })
942
+ true_id = b_true.id
943
+
944
+ b_blank = Boolean.find(blank_id)
945
+ assert_nil b_blank.value
946
+ b_false = Boolean.find(false_id)
947
+ assert !b_false.value?
948
+ b_true = Boolean.find(true_id)
949
+ assert b_true.value?
950
+ end
951
+
952
+ def test_new_record_returns_boolean
953
+ assert_equal false, Topic.new.persisted?
954
+ assert_equal true, Topic.find(1).persisted?
955
+ end
956
+
957
+ def test_dup
958
+ topic = Topic.find(1)
959
+ duped_topic = nil
960
+ assert_nothing_raised { duped_topic = topic.dup }
961
+ assert_equal topic.title, duped_topic.title
962
+ assert !duped_topic.persisted?
963
+
964
+ # test if the attributes have been duped
965
+ topic.title = "a"
966
+ duped_topic.title = "b"
967
+ assert_equal "a", topic.title
968
+ assert_equal "b", duped_topic.title
969
+
970
+ # test if the attribute values have been duped
971
+ topic.title = {"a" => "b"}
972
+ duped_topic = topic.dup
973
+ duped_topic.title["a"] = "c"
974
+ assert_equal "b", topic.title["a"]
975
+
976
+ # test if attributes set as part of after_initialize are duped correctly
977
+ assert_equal topic.author_email_address, duped_topic.author_email_address
978
+
979
+ # test if saved clone object differs from original
980
+ duped_topic.save
981
+ assert duped_topic.persisted?
982
+ assert_not_equal duped_topic.id, topic.id
983
+
984
+ duped_topic.reload
985
+ # FIXME: I think this is poor behavior, and will fix it with #5686
986
+ assert_equal({'a' => 'c'}.to_yaml, duped_topic.title)
987
+ end
988
+
989
+ def test_dup_with_aggregate_of_same_name_as_attribute
990
+ dev = DeveloperWithAggregate.find(1)
991
+ assert_kind_of DeveloperSalary, dev.salary
992
+
993
+ dup = nil
994
+ assert_nothing_raised { dup = dev.dup }
995
+ assert_kind_of DeveloperSalary, dup.salary
996
+ assert_equal dev.salary.amount, dup.salary.amount
997
+ assert !dup.persisted?
998
+
999
+ # test if the attributes have been dupd
1000
+ original_amount = dup.salary.amount
1001
+ dev.salary.amount = 1
1002
+ assert_equal original_amount, dup.salary.amount
1003
+
1004
+ assert dup.save
1005
+ assert dup.persisted?
1006
+ assert_not_equal dup.id, dev.id
1007
+ end
1008
+
1009
+ def test_dup_does_not_copy_associations
1010
+ author = authors(:david)
1011
+ assert_not_equal [], author.posts
1012
+ author.send(:clear_association_cache)
1013
+
1014
+ author_dup = author.dup
1015
+ assert_equal [], author_dup.posts
1016
+ end
1017
+
1018
+ def test_clone_preserves_subtype
1019
+ clone = nil
1020
+ assert_nothing_raised { clone = Company.find(3).clone }
1021
+ assert_kind_of Client, clone
1022
+ end
1023
+
1024
+ def test_clone_of_new_object_with_defaults
1025
+ developer = Developer.new
1026
+ assert !developer.name_changed?
1027
+ assert !developer.salary_changed?
1028
+
1029
+ cloned_developer = developer.clone
1030
+ assert !cloned_developer.name_changed?
1031
+ assert !cloned_developer.salary_changed?
1032
+ end
1033
+
1034
+ def test_clone_of_new_object_marks_attributes_as_dirty
1035
+ developer = Developer.new :name => 'Bjorn', :salary => 100000
1036
+ assert developer.name_changed?
1037
+ assert developer.salary_changed?
1038
+
1039
+ cloned_developer = developer.clone
1040
+ assert cloned_developer.name_changed?
1041
+ assert cloned_developer.salary_changed?
1042
+ end
1043
+
1044
+ def test_clone_of_new_object_marks_as_dirty_only_changed_attributes
1045
+ developer = Developer.new :name => 'Bjorn'
1046
+ assert developer.name_changed? # obviously
1047
+ assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed
1048
+
1049
+ cloned_developer = developer.clone
1050
+ assert cloned_developer.name_changed?
1051
+ assert !cloned_developer.salary_changed? # ... and cloned instance should behave same
1052
+ end
1053
+
1054
+ def test_dup_of_saved_object_marks_attributes_as_dirty
1055
+ developer = Developer.create! :name => 'Bjorn', :salary => 100000
1056
+ assert !developer.name_changed?
1057
+ assert !developer.salary_changed?
1058
+
1059
+ cloned_developer = developer.dup
1060
+ assert cloned_developer.name_changed? # both attributes differ from defaults
1061
+ assert cloned_developer.salary_changed?
1062
+ end
1063
+
1064
+ def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes
1065
+ developer = Developer.create! :name => 'Bjorn'
1066
+ assert !developer.name_changed? # both attributes of saved object should be treated as not changed
1067
+ assert !developer.salary_changed?
1068
+
1069
+ cloned_developer = developer.dup
1070
+ assert cloned_developer.name_changed? # ... but on cloned object should be
1071
+ assert !cloned_developer.salary_changed? # ... BUT salary has non-nil default which should be treated as not changed on cloned instance
1072
+ end
1073
+
1074
+ def test_bignum
1075
+ company = Company.find(1)
1076
+ company.rating = 2147483647
1077
+ company.save
1078
+ company = Company.find(1)
1079
+ assert_equal 2147483647, company.rating
1080
+ end
1081
+
1082
+ # TODO: extend defaults tests to other databases!
1083
+ if current_adapter?(:PostgreSQLAdapter)
1084
+ def test_default
1085
+ default = Default.new
1086
+
1087
+ # fixed dates / times
1088
+ assert_equal Date.new(2004, 1, 1), default.fixed_date
1089
+ assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
1090
+
1091
+ # char types
1092
+ assert_equal 'Y', default.char1
1093
+ assert_equal 'a varchar field', default.char2
1094
+ assert_equal 'a text field', default.char3
1095
+ end
1096
+
1097
+ class Geometric < ActiveRecord::Base; end
1098
+ def test_geometric_content
1099
+
1100
+ # accepted format notes:
1101
+ # ()'s aren't required
1102
+ # values can be a mix of float or integer
1103
+
1104
+ g = Geometric.new(
1105
+ :a_point => '(5.0, 6.1)',
1106
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
1107
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
1108
+ :a_box => '2.0, 3, 5.5, 7.0',
1109
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
1110
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
1111
+ :a_circle => '<(5.3, 10.4), 2>'
1112
+ )
1113
+
1114
+ assert g.save
1115
+
1116
+ # Reload and check that we have all the geometric attributes.
1117
+ h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
1118
+
1119
+ assert_equal '(5,6.1)', h.a_point
1120
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
1121
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
1122
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
1123
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
1124
+ assert_equal '<(5.3,10.4),2>', h.a_circle
1125
+
1126
+ # use a geometric function to test for an open path
1127
+ objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
1128
+ assert_equal objs[0].isopen, 't'
1129
+
1130
+ # test alternate formats when defining the geometric types
1131
+
1132
+ g = Geometric.new(
1133
+ :a_point => '5.0, 6.1',
1134
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
1135
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
1136
+ :a_box => '(2.0, 3), (5.5, 7.0)',
1137
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
1138
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
1139
+ :a_circle => '((5.3, 10.4), 2)'
1140
+ )
1141
+
1142
+ assert g.save
1143
+
1144
+ # Reload and check that we have all the geometric attributes.
1145
+ h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
1146
+
1147
+ assert_equal '(5,6.1)', h.a_point
1148
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
1149
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
1150
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
1151
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
1152
+ assert_equal '<(5.3,10.4),2>', h.a_circle
1153
+
1154
+ # use a geometric function to test for an closed path
1155
+ objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
1156
+ assert_equal objs[0].isclosed, 't'
1157
+ end
1158
+ end
1159
+
1160
+ class NumericData < ActiveRecord::Base
1161
+ self.table_name = 'numeric_data'
1162
+ end
1163
+
1164
+ def test_big_decimal_conditions
1165
+ m = NumericData.new(
1166
+ :bank_balance => 1586.43,
1167
+ :big_bank_balance => BigDecimal("1000234000567.95"),
1168
+ :world_population => 6000000000,
1169
+ :my_house_population => 3
1170
+ )
1171
+ assert m.save
1172
+ assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
1173
+ end
1174
+
1175
+ def test_numeric_fields
1176
+ m = NumericData.new(
1177
+ :bank_balance => 1586.43,
1178
+ :big_bank_balance => BigDecimal("1000234000567.95"),
1179
+ :world_population => 6000000000,
1180
+ :my_house_population => 3
1181
+ )
1182
+ assert m.save
1183
+
1184
+ m1 = NumericData.find(m.id)
1185
+ assert_not_nil m1
1186
+
1187
+ # As with migration_test.rb, we should make world_population >= 2**62
1188
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
1189
+ # is that it's an Integer.
1190
+ unless current_adapter?(:IBM_DBAdapter)
1191
+ assert_kind_of Integer, m1.world_population
1192
+ else
1193
+ assert_kind_of BigDecimal, m1.world_population
1194
+ end
1195
+ assert_equal 6000000000, m1.world_population
1196
+
1197
+ unless current_adapter?(:IBM_DBAdapter)
1198
+ assert_kind_of Fixnum, m1.my_house_population
1199
+ else
1200
+ assert_kind_of BigDecimal, m1.my_house_population
1201
+ end
1202
+ assert_equal 3, m1.my_house_population
1203
+
1204
+ assert_kind_of BigDecimal, m1.bank_balance
1205
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
1206
+
1207
+ assert_kind_of BigDecimal, m1.big_bank_balance
1208
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
1209
+ end
1210
+
1211
+ def test_auto_id
1212
+ auto = AutoId.new
1213
+ auto.save
1214
+ assert(auto.id > 0)
1215
+ end
1216
+
1217
+ def test_sql_injection_via_find
1218
+ assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
1219
+ Topic.find("123456 OR id > 0")
1220
+ end
1221
+ end
1222
+
1223
+ def test_column_name_properly_quoted
1224
+ col_record = ColumnName.new
1225
+ col_record.references = 40
1226
+ assert col_record.save
1227
+ col_record.references = 41
1228
+ assert col_record.save
1229
+ assert_not_nil c2 = ColumnName.find(col_record.id)
1230
+ assert_equal(41, c2.references)
1231
+ end
1232
+
1233
+ def test_quoting_arrays
1234
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
1235
+ assert_equal topics(:first).replies.size, replies.size
1236
+
1237
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
1238
+ assert_equal 0, replies.size
1239
+ end
1240
+
1241
+ MyObject = Struct.new :attribute1, :attribute2
1242
+
1243
+ def test_serialized_attribute
1244
+ Topic.serialize("content", MyObject)
1245
+
1246
+ myobj = MyObject.new('value1', 'value2')
1247
+ topic = Topic.create("content" => myobj)
1248
+ assert_equal(myobj, topic.content)
1249
+
1250
+ topic.reload
1251
+ assert_equal(myobj, topic.content)
1252
+ end
1253
+
1254
+ def test_serialized_attribute_in_base_class
1255
+ Topic.serialize("content", Hash)
1256
+
1257
+ hash = { 'content1' => 'value1', 'content2' => 'value2' }
1258
+ important_topic = ImportantTopic.create("content" => hash)
1259
+ assert_equal(hash, important_topic.content)
1260
+
1261
+ important_topic.reload
1262
+ assert_equal(hash, important_topic.content)
1263
+ end
1264
+
1265
+ # This test was added to fix GH #4004. Obviously the value returned
1266
+ # is not really the value 'before type cast' so we should maybe think
1267
+ # about changing that in the future.
1268
+ def test_serialized_attribute_before_type_cast_returns_unserialized_value
1269
+ klass = Class.new(ActiveRecord::Base)
1270
+ klass.table_name = "topics"
1271
+ klass.serialize :content, Hash
1272
+
1273
+ t = klass.new(:content => { :foo => :bar })
1274
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
1275
+ t.save!
1276
+ t.reload
1277
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
1278
+ end
1279
+
1280
+ def test_serialized_attribute_declared_in_subclass
1281
+ hash = { 'important1' => 'value1', 'important2' => 'value2' }
1282
+ important_topic = ImportantTopic.create("important" => hash)
1283
+ assert_equal(hash, important_topic.important)
1284
+
1285
+ important_topic.reload
1286
+ assert_equal(hash, important_topic.important)
1287
+ assert_equal(hash, important_topic.read_attribute(:important))
1288
+ end
1289
+
1290
+ def test_serialized_time_attribute
1291
+ myobj = Time.local(2008,1,1,1,0)
1292
+ topic = Topic.create("content" => myobj).reload
1293
+ assert_equal(myobj, topic.content)
1294
+ end
1295
+
1296
+ def test_serialized_string_attribute
1297
+ myobj = "Yes"
1298
+ topic = Topic.create("content" => myobj).reload
1299
+ assert_equal(myobj, topic.content)
1300
+ end
1301
+
1302
+ def test_nil_serialized_attribute_with_class_constraint
1303
+ topic = Topic.new
1304
+ assert_nil topic.content
1305
+ end
1306
+
1307
+ def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
1308
+ myobj = MyObject.new('value1', 'value2')
1309
+ topic = Topic.new(:content => myobj)
1310
+ assert topic.save
1311
+ Topic.serialize(:content, Hash)
1312
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).reload.content }
1313
+ ensure
1314
+ Topic.serialize(:content)
1315
+ end
1316
+
1317
+ def test_serialized_attribute_with_class_constraint
1318
+ settings = { "color" => "blue" }
1319
+ Topic.serialize(:content, Hash)
1320
+ topic = Topic.new(:content => settings)
1321
+ assert topic.save
1322
+ assert_equal(settings, Topic.find(topic.id).content)
1323
+ ensure
1324
+ Topic.serialize(:content)
1325
+ end
1326
+
1327
+ def test_serialized_default_class
1328
+ Topic.serialize(:content, Hash)
1329
+ topic = Topic.new
1330
+ assert_equal Hash, topic.content.class
1331
+ assert_equal Hash, topic.read_attribute(:content).class
1332
+ topic.content["beer"] = "MadridRb"
1333
+ assert topic.save
1334
+ topic.reload
1335
+ assert_equal Hash, topic.content.class
1336
+ assert_equal "MadridRb", topic.content["beer"]
1337
+ ensure
1338
+ Topic.serialize(:content)
1339
+ end
1340
+
1341
+ def test_serialized_no_default_class_for_object
1342
+ topic = Topic.new
1343
+ assert_nil topic.content
1344
+ end
1345
+
1346
+ def test_serialized_boolean_value_true
1347
+ Topic.serialize(:content)
1348
+ topic = Topic.new(:content => true)
1349
+ assert topic.save
1350
+ topic = topic.reload
1351
+ assert_equal topic.content, true
1352
+ end
1353
+
1354
+ def test_serialized_boolean_value_false
1355
+ Topic.serialize(:content)
1356
+ topic = Topic.new(:content => false)
1357
+ assert topic.save
1358
+ topic = topic.reload
1359
+ assert_equal topic.content, false
1360
+ end
1361
+
1362
+ def test_serialize_with_coder
1363
+ coder = Class.new {
1364
+ # Identity
1365
+ def load(thing)
1366
+ thing
1367
+ end
1368
+
1369
+ # base 64
1370
+ def dump(thing)
1371
+ [thing].pack('m')
1372
+ end
1373
+ }.new
1374
+
1375
+ Topic.serialize(:content, coder)
1376
+ s = 'hello world'
1377
+ topic = Topic.new(:content => s)
1378
+ assert topic.save
1379
+ topic = topic.reload
1380
+ assert_equal [s].pack('m'), topic.content
1381
+ ensure
1382
+ Topic.serialize(:content)
1383
+ end
1384
+
1385
+ def test_serialize_with_bcrypt_coder
1386
+ crypt_coder = Class.new {
1387
+ def load(thing)
1388
+ return unless thing
1389
+ BCrypt::Password.new thing
1390
+ end
1391
+
1392
+ def dump(thing)
1393
+ BCrypt::Password.create(thing).to_s
1394
+ end
1395
+ }.new
1396
+
1397
+ Topic.serialize(:content, crypt_coder)
1398
+ password = 'password'
1399
+ topic = Topic.new(:content => password)
1400
+ assert topic.save
1401
+ topic = topic.reload
1402
+ assert_kind_of BCrypt::Password, topic.content
1403
+ assert_equal(true, topic.content == password, 'password should equal')
1404
+ ensure
1405
+ Topic.serialize(:content)
1406
+ end
1407
+
1408
+ def test_quote
1409
+ author_name = "\\ \001 ' \n \\n \""
1410
+ topic = Topic.create('author_name' => author_name)
1411
+ assert_equal author_name, Topic.find(topic.id).author_name
1412
+ end
1413
+
1414
+ if RUBY_VERSION < '1.9'
1415
+ def test_quote_chars
1416
+ with_kcode('UTF8') do
1417
+ str = 'The Narrator'
1418
+ topic = Topic.create(:author_name => str)
1419
+ assert_equal str, topic.author_name
1420
+
1421
+ assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars
1422
+ topic = Topic.find_by_author_name(str.mb_chars)
1423
+
1424
+ assert_kind_of Topic, topic
1425
+ assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
1426
+ end
1427
+ end
1428
+ end
1429
+
1430
+ def test_toggle_attribute
1431
+ assert !topics(:first).approved?
1432
+ topics(:first).toggle!(:approved)
1433
+ assert topics(:first).approved?
1434
+ topic = topics(:first)
1435
+ topic.toggle(:approved)
1436
+ assert !topic.approved?
1437
+ topic.reload
1438
+ assert topic.approved?
1439
+ end
1440
+
1441
+ def test_reload
1442
+ t1 = Topic.find(1)
1443
+ t2 = Topic.find(1)
1444
+ t1.title = "something else"
1445
+ t1.save
1446
+ t2.reload
1447
+ assert_equal t1.title, t2.title
1448
+ end
1449
+
1450
+ def test_reload_with_exclusive_scope
1451
+ dev = DeveloperCalledDavid.first
1452
+ dev.update_attributes!( :name => "NotDavid" )
1453
+ assert_equal dev, dev.reload
1454
+ end
1455
+
1456
+ def test_set_table_name_with_value
1457
+ k = Class.new( ActiveRecord::Base )
1458
+ k.table_name = "foo"
1459
+ assert_equal "foo", k.table_name
1460
+
1461
+ assert_deprecated do
1462
+ k.set_table_name "bar"
1463
+ end
1464
+ assert_equal "bar", k.table_name
1465
+ end
1466
+
1467
+ def test_switching_between_table_name
1468
+ assert_difference("GoodJoke.count") do
1469
+ Joke.table_name = "cold_jokes"
1470
+ Joke.create
1471
+
1472
+ Joke.table_name = "funny_jokes"
1473
+ Joke.create
1474
+ end
1475
+ end
1476
+
1477
+ def test_set_table_name_symbol_converted_to_string
1478
+ Joke.table_name = :cold_jokes
1479
+ assert_equal 'cold_jokes', Joke.table_name
1480
+ end
1481
+
1482
+ def test_quoted_table_name_after_set_table_name
1483
+ klass = Class.new(ActiveRecord::Base)
1484
+
1485
+ klass.table_name = "foo"
1486
+ assert_equal "foo", klass.table_name
1487
+ assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name
1488
+
1489
+ klass.table_name = "bar"
1490
+ assert_equal "bar", klass.table_name
1491
+ assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name
1492
+ end
1493
+
1494
+ def test_set_table_name_with_block
1495
+ k = Class.new( ActiveRecord::Base )
1496
+ assert_deprecated do
1497
+ k.set_table_name "foo"
1498
+ k.set_table_name do
1499
+ ActiveSupport::Deprecation.silence { original_table_name } + "ks"
1500
+ end
1501
+ end
1502
+ assert_equal "fooks", k.table_name
1503
+ end
1504
+
1505
+ def test_set_table_name_with_inheritance
1506
+ k = Class.new( ActiveRecord::Base )
1507
+ def k.name; "Foo"; end
1508
+ def k.table_name; super + "ks"; end
1509
+ assert_equal "foosks", k.table_name
1510
+ end
1511
+
1512
+ def test_original_table_name
1513
+ k = Class.new(ActiveRecord::Base)
1514
+ def k.name; "Foo"; end
1515
+ k.table_name = "bar"
1516
+
1517
+ assert_deprecated do
1518
+ assert_equal "foos", k.original_table_name
1519
+ end
1520
+
1521
+ k = Class.new(ActiveRecord::Base)
1522
+ k.table_name = "omg"
1523
+ k.table_name = "wtf"
1524
+
1525
+ assert_deprecated do
1526
+ assert_equal "omg", k.original_table_name
1527
+ end
1528
+ end
1529
+
1530
+ def test_set_primary_key_with_value
1531
+ k = Class.new( ActiveRecord::Base )
1532
+ k.primary_key = "foo"
1533
+ assert_equal "foo", k.primary_key
1534
+
1535
+ assert_deprecated do
1536
+ k.set_primary_key "bar"
1537
+ end
1538
+ assert_equal "bar", k.primary_key
1539
+ end
1540
+
1541
+ def test_set_primary_key_with_block
1542
+ k = Class.new( ActiveRecord::Base )
1543
+ k.primary_key = 'id'
1544
+
1545
+ assert_deprecated do
1546
+ k.set_primary_key do
1547
+ "sys_" + ActiveSupport::Deprecation.silence { original_primary_key }
1548
+ end
1549
+ end
1550
+ assert_equal "sys_id", k.primary_key
1551
+ end
1552
+
1553
+ def test_original_primary_key
1554
+ k = Class.new(ActiveRecord::Base)
1555
+ def k.name; "Foo"; end
1556
+ k.table_name = "posts"
1557
+ k.primary_key = "bar"
1558
+
1559
+ assert_deprecated do
1560
+ assert_equal "id", k.original_primary_key
1561
+ end
1562
+
1563
+ k = Class.new(ActiveRecord::Base)
1564
+ k.primary_key = "omg"
1565
+ k.primary_key = "wtf"
1566
+
1567
+ assert_deprecated do
1568
+ assert_equal "omg", k.original_primary_key
1569
+ end
1570
+ end
1571
+
1572
+ def test_set_inheritance_column_with_value
1573
+ k = Class.new( ActiveRecord::Base )
1574
+ k.inheritance_column = "foo"
1575
+ assert_equal "foo", k.inheritance_column
1576
+
1577
+ assert_deprecated do
1578
+ k.set_inheritance_column "bar"
1579
+ end
1580
+ assert_equal "bar", k.inheritance_column
1581
+ end
1582
+
1583
+ def test_set_inheritance_column_with_block
1584
+ k = Class.new( ActiveRecord::Base )
1585
+ assert_deprecated do
1586
+ k.set_inheritance_column do
1587
+ ActiveSupport::Deprecation.silence { original_inheritance_column } + "_id"
1588
+ end
1589
+ end
1590
+ assert_equal "type_id", k.inheritance_column
1591
+ end
1592
+
1593
+ def test_original_inheritance_column
1594
+ k = Class.new(ActiveRecord::Base)
1595
+ def k.name; "Foo"; end
1596
+ k.inheritance_column = "omg"
1597
+
1598
+ assert_deprecated do
1599
+ assert_equal "type", k.original_inheritance_column
1600
+ end
1601
+ end
1602
+
1603
+ def test_set_sequence_name_with_value
1604
+ k = Class.new( ActiveRecord::Base )
1605
+ k.sequence_name = "foo"
1606
+ assert_equal "foo", k.sequence_name
1607
+
1608
+ assert_deprecated do
1609
+ k.set_sequence_name "bar"
1610
+ end
1611
+ assert_equal "bar", k.sequence_name
1612
+ end
1613
+
1614
+ def test_set_sequence_name_with_block
1615
+ k = Class.new( ActiveRecord::Base )
1616
+ k.table_name = "projects"
1617
+ orig_name = k.sequence_name
1618
+ return skip "sequences not supported by db" unless orig_name
1619
+
1620
+ assert_deprecated do
1621
+ k.set_sequence_name do
1622
+ ActiveSupport::Deprecation.silence { original_sequence_name } + "_lol"
1623
+ end
1624
+ end
1625
+ assert_equal orig_name + "_lol", k.sequence_name
1626
+ end
1627
+
1628
+ def test_original_sequence_name
1629
+ k = Class.new(ActiveRecord::Base)
1630
+ k.table_name = "projects"
1631
+ orig_name = k.sequence_name
1632
+ return skip "sequences not supported by db" unless orig_name
1633
+
1634
+ k = Class.new(ActiveRecord::Base)
1635
+ k.table_name = "projects"
1636
+ k.sequence_name = "omg"
1637
+
1638
+ assert_deprecated do
1639
+ assert_equal orig_name, k.original_sequence_name
1640
+ end
1641
+
1642
+ k = Class.new(ActiveRecord::Base)
1643
+ k.table_name = "projects"
1644
+ k.sequence_name = "omg"
1645
+ k.sequence_name = "wtf"
1646
+ assert_deprecated do
1647
+ assert_equal "omg", k.original_sequence_name
1648
+ end
1649
+ end
1650
+
1651
+ def test_sequence_name_with_abstract_class
1652
+ ak = Class.new(ActiveRecord::Base)
1653
+ ak.abstract_class = true
1654
+ k = Class.new(ak)
1655
+ k.table_name = "projects"
1656
+ orig_name = k.sequence_name
1657
+ return skip "sequences not supported by db" unless orig_name
1658
+ assert_equal k.reset_sequence_name, orig_name
1659
+ end
1660
+
1661
+ def test_count_with_join
1662
+ res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1663
+
1664
+ res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1665
+ assert_equal res, res2
1666
+
1667
+ res3 = nil
1668
+ assert_nothing_raised do
1669
+ res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
1670
+ :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1671
+ end
1672
+ assert_equal res, res3
1673
+
1674
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1675
+ res5 = nil
1676
+ assert_nothing_raised do
1677
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1678
+ :joins => "p, comments co",
1679
+ :select => "p.id")
1680
+ end
1681
+
1682
+ assert_equal res4, res5
1683
+
1684
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1685
+ res7 = nil
1686
+ assert_nothing_raised do
1687
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1688
+ :joins => "p, comments co",
1689
+ :select => "p.id",
1690
+ :distinct => true)
1691
+ end
1692
+ assert_equal res6, res7
1693
+ end
1694
+
1695
+ def test_scoped_find_conditions
1696
+ scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do
1697
+ Developer.find(:all, :conditions => 'id < 5')
1698
+ end
1699
+ assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
1700
+ assert_equal 3, scoped_developers.size
1701
+ end
1702
+
1703
+ def test_no_limit_offset
1704
+ assert_nothing_raised do
1705
+ Developer.find(:all, :offset => 2)
1706
+ end
1707
+ end
1708
+
1709
+ def test_scoped_find_limit_offset
1710
+ scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do
1711
+ Developer.find(:all, :order => 'id')
1712
+ end
1713
+ assert !scoped_developers.include?(developers(:david))
1714
+ assert !scoped_developers.include?(developers(:jamis))
1715
+ assert_equal 3, scoped_developers.size
1716
+
1717
+ # Test without scoped find conditions to ensure we get the whole thing
1718
+ developers = Developer.find(:all, :order => 'id')
1719
+ assert_equal Developer.count, developers.size
1720
+ end
1721
+
1722
+ def test_scoped_find_order
1723
+ # Test order in scope
1724
+ scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do
1725
+ Developer.find(:all)
1726
+ end
1727
+ assert_equal 'Jamis', scoped_developers.first.name
1728
+ assert scoped_developers.include?(developers(:jamis))
1729
+ # Test scope without order and order in find
1730
+ scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do
1731
+ Developer.find(:all, :order => 'salary DESC')
1732
+ end
1733
+ # Test scope order + find order, order has priority
1734
+ scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do
1735
+ Developer.find(:all, :order => 'salary ASC')
1736
+ end
1737
+ assert scoped_developers.include?(developers(:poor_jamis))
1738
+ assert ! scoped_developers.include?(developers(:david))
1739
+ assert ! scoped_developers.include?(developers(:jamis))
1740
+ assert_equal 3, scoped_developers.size
1741
+
1742
+ # Test without scoped find conditions to ensure we get the right thing
1743
+ assert ! scoped_developers.include?(Developer.find(1))
1744
+ assert scoped_developers.include?(Developer.find(11))
1745
+ end
1746
+
1747
+ def test_scoped_find_limit_offset_including_has_many_association
1748
+ topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do
1749
+ Topic.find(:all, :order => "topics.id")
1750
+ end
1751
+ assert_equal 1, topics.size
1752
+ assert_equal 2, topics.first.id
1753
+ end
1754
+
1755
+ def test_scoped_find_order_including_has_many_association
1756
+ developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do
1757
+ Developer.find(:all)
1758
+ end
1759
+ assert developers.size >= 2
1760
+ for i in 1...developers.size
1761
+ assert developers[i-1].salary >= developers[i].salary
1762
+ end
1763
+ end
1764
+
1765
+ def test_scoped_find_with_group_and_having
1766
+ developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
1767
+ Developer.find(:all)
1768
+ end
1769
+ assert_equal 3, developers.size
1770
+ end
1771
+
1772
+ def test_find_last
1773
+ last = Developer.find :last
1774
+ assert_equal last, Developer.find(:first, :order => 'id desc')
1775
+ end
1776
+
1777
+ def test_last
1778
+ assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
1779
+ end
1780
+
1781
+ def test_all
1782
+ developers = Developer.all
1783
+ assert_kind_of Array, developers
1784
+ assert_equal Developer.find(:all), developers
1785
+ end
1786
+
1787
+ def test_all_with_conditions
1788
+ assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all
1789
+ end
1790
+
1791
+ def test_find_ordered_last
1792
+ last = Developer.find :last, :order => 'developers.salary ASC'
1793
+ assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
1794
+ end
1795
+
1796
+ def test_find_reverse_ordered_last
1797
+ last = Developer.find :last, :order => 'developers.salary DESC'
1798
+ assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last
1799
+ end
1800
+
1801
+ def test_find_multiple_ordered_last
1802
+ last = Developer.find :last, :order => 'developers.name, developers.salary DESC'
1803
+ assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
1804
+ end
1805
+
1806
+ def test_find_keeps_multiple_order_values
1807
+ combined = Developer.find(:all, :order => 'developers.name, developers.salary')
1808
+ assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary'])
1809
+ end
1810
+
1811
+ def test_find_keeps_multiple_group_values
1812
+ combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at')
1813
+ assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at'])
1814
+ end
1815
+
1816
+ def test_find_symbol_ordered_last
1817
+ last = Developer.find :last, :order => :salary
1818
+ assert_equal last, Developer.find(:all, :order => :salary).last
1819
+ end
1820
+
1821
+ def test_find_scoped_ordered_last
1822
+ last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do
1823
+ Developer.find(:last)
1824
+ end
1825
+ assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
1826
+ end
1827
+
1828
+ def test_abstract_class
1829
+ assert !ActiveRecord::Base.abstract_class?
1830
+ assert LoosePerson.abstract_class?
1831
+ assert !LooseDescendant.abstract_class?
1832
+ end
1833
+
1834
+ def test_abstract_class_table_name
1835
+ assert_nil AbstractCompany.table_name
1836
+ end
1837
+
1838
+ def test_base_class
1839
+ assert_equal LoosePerson, LoosePerson.base_class
1840
+ assert_equal LooseDescendant, LooseDescendant.base_class
1841
+ assert_equal TightPerson, TightPerson.base_class
1842
+ assert_equal TightPerson, TightDescendant.base_class
1843
+
1844
+ assert_equal Post, Post.base_class
1845
+ assert_equal Post, SpecialPost.base_class
1846
+ assert_equal Post, StiPost.base_class
1847
+ assert_equal SubStiPost, SubStiPost.base_class
1848
+ end
1849
+
1850
+ def test_descends_from_active_record
1851
+ # Tries to call Object.abstract_class?
1852
+ assert_raise(NoMethodError) do
1853
+ ActiveRecord::Base.descends_from_active_record?
1854
+ end
1855
+
1856
+ # Abstract subclass of AR::Base.
1857
+ assert LoosePerson.descends_from_active_record?
1858
+
1859
+ # Concrete subclass of an abstract class.
1860
+ assert LooseDescendant.descends_from_active_record?
1861
+
1862
+ # Concrete subclass of AR::Base.
1863
+ assert TightPerson.descends_from_active_record?
1864
+
1865
+ # Concrete subclass of a concrete class but has no type column.
1866
+ assert TightDescendant.descends_from_active_record?
1867
+
1868
+ # Concrete subclass of AR::Base.
1869
+ assert Post.descends_from_active_record?
1870
+
1871
+ # Abstract subclass of a concrete class which has a type column.
1872
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
1873
+ assert !StiPost.descends_from_active_record?
1874
+
1875
+ # Concrete subclasses an abstract class which has a type column.
1876
+ assert !SubStiPost.descends_from_active_record?
1877
+ end
1878
+
1879
+ def test_find_on_abstract_base_class_doesnt_use_type_condition
1880
+ old_class = LooseDescendant
1881
+ Object.send :remove_const, :LooseDescendant
1882
+
1883
+ descendant = old_class.create! :first_name => 'bob'
1884
+ assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
1885
+ ensure
1886
+ unless Object.const_defined?(:LooseDescendant)
1887
+ Object.const_set :LooseDescendant, old_class
1888
+ end
1889
+ end
1890
+
1891
+ def test_assert_queries
1892
+ query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
1893
+ assert_queries(2) { 2.times { query.call } }
1894
+ assert_queries 1, &query
1895
+ assert_no_queries { assert true }
1896
+ end
1897
+
1898
+ def test_to_param_should_return_string
1899
+ assert_kind_of String, Client.find(:first).to_param
1900
+ end
1901
+
1902
+ def test_inspect_class
1903
+ assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
1904
+ assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
1905
+ assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
1906
+ end
1907
+
1908
+ def test_inspect_instance
1909
+ topic = topics(:first)
1910
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
1911
+ end
1912
+
1913
+ def test_inspect_new_instance
1914
+ assert_match(/Topic id: nil/, Topic.new.inspect)
1915
+ end
1916
+
1917
+ def test_inspect_limited_select_instance
1918
+ assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
1919
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
1920
+ end
1921
+
1922
+ def test_inspect_class_without_table
1923
+ assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
1924
+ end
1925
+
1926
+ def test_attribute_for_inspect
1927
+ t = topics(:first)
1928
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
1929
+
1930
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
1931
+ assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
1932
+ end
1933
+
1934
+ def test_becomes
1935
+ assert_kind_of Reply, topics(:first).becomes(Reply)
1936
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
1937
+ end
1938
+
1939
+ def test_becomes_includes_errors
1940
+ company = Company.new(:name => nil)
1941
+ assert !company.valid?
1942
+ original_errors = company.errors
1943
+ client = company.becomes(Client)
1944
+ assert_equal original_errors, client.errors
1945
+ end
1946
+
1947
+ def test_silence_sets_log_level_to_error_in_block
1948
+ original_logger = ActiveRecord::Base.logger
1949
+ log = StringIO.new
1950
+ ActiveRecord::Base.logger = Logger.new(log)
1951
+ ActiveRecord::Base.logger.level = Logger::DEBUG
1952
+ ActiveRecord::Base.silence do
1953
+ ActiveRecord::Base.logger.warn "warn"
1954
+ ActiveRecord::Base.logger.error "error"
1955
+ end
1956
+ assert_equal "error\n", log.string
1957
+ ensure
1958
+ ActiveRecord::Base.logger = original_logger
1959
+ end
1960
+
1961
+ def test_silence_sets_log_level_back_to_level_before_yield
1962
+ original_logger = ActiveRecord::Base.logger
1963
+ log = StringIO.new
1964
+ ActiveRecord::Base.logger = Logger.new(log)
1965
+ ActiveRecord::Base.logger.level = Logger::WARN
1966
+ ActiveRecord::Base.silence do
1967
+ end
1968
+ assert_equal Logger::WARN, ActiveRecord::Base.logger.level
1969
+ ensure
1970
+ ActiveRecord::Base.logger = original_logger
1971
+ end
1972
+
1973
+ def test_benchmark_with_log_level
1974
+ original_logger = ActiveRecord::Base.logger
1975
+ log = StringIO.new
1976
+ ActiveRecord::Base.logger = Logger.new(log)
1977
+ ActiveRecord::Base.logger.level = Logger::WARN
1978
+ ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count }
1979
+ ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count }
1980
+ ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count }
1981
+ assert_no_match(/Debug Topic Count/, log.string)
1982
+ assert_match(/Warn Topic Count/, log.string)
1983
+ assert_match(/Error Topic Count/, log.string)
1984
+ ensure
1985
+ ActiveRecord::Base.logger = original_logger
1986
+ end
1987
+
1988
+ def test_benchmark_with_use_silence
1989
+ original_logger = ActiveRecord::Base.logger
1990
+ log = StringIO.new
1991
+ ActiveRecord::Base.logger = Logger.new(log)
1992
+ assert_deprecated do
1993
+ ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" }
1994
+ end
1995
+ ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" }
1996
+ assert_no_match(/Loud/, log.string)
1997
+ assert_match(/Quiet/, log.string)
1998
+ ensure
1999
+ ActiveRecord::Base.logger = original_logger
2000
+ end
2001
+
2002
+ def test_compute_type_success
2003
+ assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
2004
+ end
2005
+
2006
+ def test_compute_type_nonexistent_constant
2007
+ assert_raises NameError do
2008
+ ActiveRecord::Base.send :compute_type, 'NonexistentModel'
2009
+ end
2010
+ end
2011
+
2012
+ def test_compute_type_no_method_error
2013
+ ActiveSupport::Dependencies.stubs(:constantize).raises(NoMethodError)
2014
+ assert_raises NoMethodError do
2015
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
2016
+ end
2017
+ end
2018
+
2019
+ def test_compute_type_argument_error
2020
+ ActiveSupport::Dependencies.stubs(:constantize).raises(ArgumentError)
2021
+ assert_raises ArgumentError do
2022
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
2023
+ end
2024
+ end
2025
+
2026
+ def test_clear_cache!
2027
+ # preheat cache
2028
+ c1 = Post.connection.schema_cache.columns['posts']
2029
+ ActiveRecord::Base.clear_cache!
2030
+ c2 = Post.connection.schema_cache.columns['posts']
2031
+ assert_not_equal c1, c2
2032
+ end
2033
+
2034
+ def test_current_scope_is_reset
2035
+ Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base)
2036
+ UnloadablePost.send(:current_scope=, UnloadablePost.scoped)
2037
+
2038
+ UnloadablePost.unloadable
2039
+ assert_not_nil Thread.current[:UnloadablePost_current_scope]
2040
+ ActiveSupport::Dependencies.remove_unloadable_constants!
2041
+ assert_nil Thread.current[:UnloadablePost_current_scope]
2042
+ ensure
2043
+ Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
2044
+ end
2045
+
2046
+ def test_marshal_round_trip
2047
+ if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
2048
+ return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
2049
+ "to be a Ruby bug.")
2050
+ end
2051
+
2052
+ expected = posts(:welcome)
2053
+ marshalled = Marshal.dump(expected)
2054
+ actual = Marshal.load(marshalled)
2055
+
2056
+ assert_equal expected.attributes, actual.attributes
2057
+ end
2058
+
2059
+ def test_marshal_new_record_round_trip
2060
+ if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
2061
+ return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
2062
+ "to be a Ruby bug.")
2063
+ end
2064
+
2065
+ marshalled = Marshal.dump(Post.new)
2066
+ post = Marshal.load(marshalled)
2067
+
2068
+ assert post.new_record?, "should be a new record"
2069
+ end
2070
+
2071
+ def test_marshalling_with_associations
2072
+ if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
2073
+ return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
2074
+ "to be a Ruby bug.")
2075
+ end
2076
+
2077
+ post = Post.new
2078
+ post.comments.build
2079
+
2080
+ marshalled = Marshal.dump(post)
2081
+ post = Marshal.load(marshalled)
2082
+
2083
+ assert_equal 1, post.comments.length
2084
+ end
2085
+
2086
+ def test_attribute_names
2087
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
2088
+ Company.attribute_names
2089
+ end
2090
+
2091
+ def test_attribute_names_on_table_not_exists
2092
+ assert_equal [], NonExistentTable.attribute_names
2093
+ end
2094
+
2095
+ def test_attribtue_names_on_abstract_class
2096
+ assert_equal [], AbstractCompany.attribute_names
2097
+ end
2098
+
2099
+ def test_cache_key_for_existing_record_is_not_timezone_dependent
2100
+ ActiveRecord::Base.time_zone_aware_attributes = true
2101
+
2102
+ Time.zone = "UTC"
2103
+ utc_key = Developer.first.cache_key
2104
+
2105
+ Time.zone = "EST"
2106
+ est_key = Developer.first.cache_key
2107
+
2108
+ assert_equal utc_key, est_key
2109
+ ensure
2110
+ ActiveRecord::Base.time_zone_aware_attributes = false
2111
+ end
2112
+
2113
+ def test_cache_key_format_for_existing_record_with_updated_at
2114
+ dev = Developer.first
2115
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
2116
+ end
2117
+
2118
+ def test_cache_key_format_for_existing_record_with_nil_updated_at
2119
+ dev = Developer.first
2120
+ dev.update_attribute(:updated_at, nil)
2121
+ assert_match(/\/#{dev.id}$/, dev.cache_key)
2122
+ end
2123
+
2124
+ def test_uniq_delegates_to_scoped
2125
+ scope = stub
2126
+ Bird.stubs(:scoped).returns(mock(:uniq => scope))
2127
+ assert_equal scope, Bird.uniq
2128
+ end
2129
+
2130
+ def test_table_name_with_2_abstract_subclasses
2131
+ assert_equal "photos", Photo.table_name
2132
+ end
2133
+ end