base_test.rb 50.0 KB
Newer Older
1
require "cases/helper"
2
require 'models/post'
3
require 'models/author'
J
Jeremy Kemper 已提交
4 5
require 'models/topic'
require 'models/reply'
6
require 'models/category'
J
Jeremy Kemper 已提交
7 8 9 10 11 12
require 'models/company'
require 'models/customer'
require 'models/developer'
require 'models/project'
require 'models/default'
require 'models/auto_id'
13
require 'models/boolean'
J
Jeremy Kemper 已提交
14 15 16
require 'models/column_name'
require 'models/subscriber'
require 'models/keyboard'
17
require 'models/comment'
J
Jeremy Kemper 已提交
18 19
require 'models/minimalistic'
require 'models/warehouse_thing'
20
require 'models/parrot'
21
require 'models/loose_person'
22
require 'rexml/document'
J
Jeremy Kemper 已提交
23
require 'active_support/core_ext/exception'
D
Initial  
David Heinemeier Hansson 已提交
24 25

class Category < ActiveRecord::Base; end
26
class Categorization < ActiveRecord::Base; end
D
Initial  
David Heinemeier Hansson 已提交
27
class Smarts < ActiveRecord::Base; end
28
class CreditCard < ActiveRecord::Base
29 30 31 32 33 34
  class PinNumber < ActiveRecord::Base
    class CvvCode < ActiveRecord::Base; end
    class SubCvvCode < CvvCode; end
  end
  class SubPinNumber < PinNumber; end
  class Brand < Category; end
35
end
D
Initial  
David Heinemeier Hansson 已提交
36
class MasterCreditCard < ActiveRecord::Base; end
37
class Post < ActiveRecord::Base; end
38
class Computer < ActiveRecord::Base; end
39
class NonExistentTable < ActiveRecord::Base; end
40
class TestOracleDefault < ActiveRecord::Base; end
D
Initial  
David Heinemeier Hansson 已提交
41

42 43 44 45
class ReadonlyTitlePost < Post
  attr_readonly :title
end

46
class Boolean < ActiveRecord::Base; end
D
Initial  
David Heinemeier Hansson 已提交
47

48
class BasicsTest < ActiveRecord::TestCase
49
  fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
D
Initial  
David Heinemeier Hansson 已提交
50

51 52 53 54
  def test_table_exists
    assert !NonExistentTable.table_exists?
    assert Topic.table_exists?
  end
J
Jeremy Kemper 已提交
55

D
Initial  
David Heinemeier Hansson 已提交
56
  def test_preserving_date_objects
57
    if current_adapter?(:SybaseAdapter)
58 59
      # Sybase ctlib does not (yet?) support the date type; use datetime instead.
      assert_kind_of(
J
Jeremy Kemper 已提交
60
        Time, Topic.find(1).last_read,
61 62 63
        "The last_read attribute should be of the Time class"
      )
    else
64
      # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
65
      assert_kind_of(
J
Jeremy Kemper 已提交
66
        Date, Topic.find(1).last_read,
67 68 69
        "The last_read attribute should be of the Date class"
      )
    end
70
  end
71

72
  def test_preserving_time_objects
73 74 75 76
    assert_kind_of(
      Time, Topic.find(1).bonus_time,
      "The bonus_time attribute should be of the Time class"
    )
D
Initial  
David Heinemeier Hansson 已提交
77 78 79 80 81

    assert_kind_of(
      Time, Topic.find(1).written_on,
      "The written_on attribute should be of the Time class"
    )
82 83

    # For adapters which support microsecond resolution.
84
    if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter)
85 86
      assert_equal 11, Topic.find(1).written_on.sec
      assert_equal 223300, Topic.find(1).written_on.usec
87
      assert_equal 9900, Topic.find(2).written_on.usec
88
    end
D
Initial  
David Heinemeier Hansson 已提交
89
  end
J
Jeremy Kemper 已提交
90

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
  def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :utc do
        time = Time.local(2000)
        topic = Topic.create('written_on' => time)
        saved_time = Topic.find(topic.id).written_on
        assert_equal time, saved_time
        assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
        assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
      end
    end
  end

  def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :utc do
        Time.use_zone 'Central Time (US & Canada)' do
          time = Time.zone.local(2000)
          topic = Topic.create('written_on' => time)
          saved_time = Topic.find(topic.id).written_on
          assert_equal time, saved_time
          assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
          assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
        end
      end
    end
  end

  def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
    with_env_tz 'America/New_York' do
      time = Time.utc(2000)
      topic = Topic.create('written_on' => time)
      saved_time = Topic.find(topic.id).written_on
      assert_equal time, saved_time
      assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
      assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
    end
  end

  def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :local do
        Time.use_zone 'Central Time (US & Canada)' do
          time = Time.zone.local(2000)
          topic = Topic.create('written_on' => time)
          saved_time = Topic.find(topic.id).written_on
          assert_equal time, saved_time
          assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
          assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
        end
      end
    end
  end

145 146 147 148 149 150
  def test_custom_mutator
    topic = Topic.find(1)
    # This mutator is protected in the class definition
    topic.send(:approved=, true)
    assert topic.instance_variable_get("@custom_approved")
  end
J
Jeremy Kemper 已提交
151

D
Initial  
David Heinemeier Hansson 已提交
152
  def test_initialize_with_attributes
J
Jeremy Kemper 已提交
153
    topic = Topic.new({
D
Initial  
David Heinemeier Hansson 已提交
154 155
      "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
    })
J
Jeremy Kemper 已提交
156

D
Initial  
David Heinemeier Hansson 已提交
157 158
    assert_equal("initialized from attributes", topic.title)
  end
J
Jeremy Kemper 已提交
159

160 161
  def test_initialize_with_invalid_attribute
    begin
J
Jeremy Kemper 已提交
162
      topic = Topic.new({ "title" => "test",
163 164 165 166 167 168
        "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
    rescue ActiveRecord::MultiparameterAssignmentErrors => ex
      assert_equal(1, ex.errors.size)
      assert_equal("last_read", ex.errors[0].attribute)
    end
  end
J
Jeremy Kemper 已提交
169

D
Initial  
David Heinemeier Hansson 已提交
170
  def test_load
J
Jeremy Kemper 已提交
171
    topics = Topic.find(:all, :order => 'id')
172
    assert_equal(4, topics.size)
173
    assert_equal(topics(:first).title, topics.first.title)
D
Initial  
David Heinemeier Hansson 已提交
174
  end
J
Jeremy Kemper 已提交
175

D
Initial  
David Heinemeier Hansson 已提交
176
  def test_load_with_condition
177
    topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
J
Jeremy Kemper 已提交
178

D
Initial  
David Heinemeier Hansson 已提交
179
    assert_equal(1, topics.size)
180
    assert_equal(topics(:second).title, topics.first.title)
D
Initial  
David Heinemeier Hansson 已提交
181 182
  end

183
  GUESSED_CLASSES = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
184

185
  def test_table_name_guesses
D
Initial  
David Heinemeier Hansson 已提交
186
    assert_equal "topics", Topic.table_name
187

D
Initial  
David Heinemeier Hansson 已提交
188 189 190
    assert_equal "categories", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_cards", CreditCard.table_name
191
    assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
192 193 194
    assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name
    assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name
    assert_equal "categories", CreditCard::Brand.table_name
D
Initial  
David Heinemeier Hansson 已提交
195
    assert_equal "master_credit_cards", MasterCreditCard.table_name
196 197 198
  ensure
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
199

200
  def test_singular_table_name_guesses
D
Initial  
David Heinemeier Hansson 已提交
201
    ActiveRecord::Base.pluralize_table_names = false
202
    GUESSED_CLASSES.each(&:reset_table_name)
203

D
Initial  
David Heinemeier Hansson 已提交
204 205 206
    assert_equal "category", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_card", CreditCard.table_name
207
    assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
208 209 210
    assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name
    assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name
    assert_equal "category", CreditCard::Brand.table_name
D
Initial  
David Heinemeier Hansson 已提交
211
    assert_equal "master_credit_card", MasterCreditCard.table_name
212
  ensure
D
Initial  
David Heinemeier Hansson 已提交
213
    ActiveRecord::Base.pluralize_table_names = true
214 215
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
216

217
  def test_table_name_guesses_with_prefixes_and_suffixes
D
Initial  
David Heinemeier Hansson 已提交
218
    ActiveRecord::Base.table_name_prefix = "test_"
219
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
220 221
    assert_equal "test_categories", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
222
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
223 224
    assert_equal "test_categories_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
225
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
226 227
    assert_equal "categories_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
228
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
229
    assert_equal "categories", Category.table_name
230 231 232 233 234
  ensure
    ActiveRecord::Base.table_name_prefix = ""
    ActiveRecord::Base.table_name_suffix = ""
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
235

236
  def test_singular_table_name_guesses_with_prefixes_and_suffixes
D
Initial  
David Heinemeier Hansson 已提交
237
    ActiveRecord::Base.pluralize_table_names = false
238

D
Initial  
David Heinemeier Hansson 已提交
239
    ActiveRecord::Base.table_name_prefix = "test_"
240
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
241 242
    assert_equal "test_category", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
243
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
244 245
    assert_equal "test_category_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
246
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
247 248
    assert_equal "category_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
249
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
250
    assert_equal "category", Category.table_name
251
  ensure
D
Initial  
David Heinemeier Hansson 已提交
252
    ActiveRecord::Base.pluralize_table_names = true
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
    ActiveRecord::Base.table_name_prefix = ""
    ActiveRecord::Base.table_name_suffix = ""
    GUESSED_CLASSES.each(&:reset_table_name)
  end

  def test_table_name_guesses_with_inherited_prefixes_and_suffixes
    GUESSED_CLASSES.each(&:reset_table_name)

    CreditCard.table_name_prefix = "test_"
    CreditCard.reset_table_name
    Category.reset_table_name
    assert_equal "test_credit_cards", CreditCard.table_name
    assert_equal "categories", Category.table_name
    CreditCard.table_name_suffix = "_test"
    CreditCard.reset_table_name
    Category.reset_table_name
    assert_equal "test_credit_cards_test", CreditCard.table_name
    assert_equal "categories", Category.table_name
    CreditCard.table_name_prefix = ""
    CreditCard.reset_table_name
    Category.reset_table_name
    assert_equal "credit_cards_test", CreditCard.table_name
    assert_equal "categories", Category.table_name
    CreditCard.table_name_suffix = ""
    CreditCard.reset_table_name
    Category.reset_table_name
    assert_equal "credit_cards", CreditCard.table_name
    assert_equal "categories", Category.table_name
  ensure
    CreditCard.table_name_prefix = ""
    CreditCard.table_name_suffix = ""
    GUESSED_CLASSES.each(&:reset_table_name)
D
Initial  
David Heinemeier Hansson 已提交
285
  end
J
Jeremy Kemper 已提交
286 287


B
Brian Lopez 已提交
288
  if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
289 290 291 292 293
    def test_update_all_with_order_and_limit
      assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
    end
  end

D
Initial  
David Heinemeier Hansson 已提交
294 295 296 297
  def test_null_fields
    assert_nil Topic.find(1).parent_id
    assert_nil Topic.create("title" => "Hey you").parent_id
  end
J
Jeremy Kemper 已提交
298

D
Initial  
David Heinemeier Hansson 已提交
299 300
  def test_default_values
    topic = Topic.new
J
Jeremy Kemper 已提交
301
    assert topic.approved?
D
Initial  
David Heinemeier Hansson 已提交
302
    assert_nil topic.written_on
303
    assert_nil topic.bonus_time
D
Initial  
David Heinemeier Hansson 已提交
304
    assert_nil topic.last_read
J
Jeremy Kemper 已提交
305

D
Initial  
David Heinemeier Hansson 已提交
306 307 308
    topic.save

    topic = Topic.find(topic.id)
J
Jeremy Kemper 已提交
309
    assert topic.approved?
D
Initial  
David Heinemeier Hansson 已提交
310
    assert_nil topic.last_read
311

J
Jeremy Kemper 已提交
312
    # Oracle has some funky default handling, so it requires a bit of
313
    # extra testing. See ticket #2788.
314 315
    if current_adapter?(:OracleAdapter)
      test = TestOracleDefault.new
316 317 318 319
      assert_equal "X", test.test_char
      assert_equal "hello", test.test_string
      assert_equal 3, test.test_int
    end
D
Initial  
David Heinemeier Hansson 已提交
320
  end
321

322 323
  # Oracle, and Sybase do not have a TIME datatype.
  unless current_adapter?(:OracleAdapter, :SybaseAdapter)
324 325 326 327 328 329 330 331
    def test_utc_as_time_zone
      Topic.default_timezone = :utc
      attributes = { "bonus_time" => "5:42:00AM" }
      topic = Topic.find(1)
      topic.attributes = attributes
      assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
      Topic.default_timezone = :local
    end
332

333 334 335 336 337 338 339 340 341 342 343 344
    def test_utc_as_time_zone_and_new
      Topic.default_timezone = :utc
      attributes = { "bonus_time(1i)"=>"2000",
                     "bonus_time(2i)"=>"1",
                     "bonus_time(3i)"=>"1",
                     "bonus_time(4i)"=>"10",
                     "bonus_time(5i)"=>"35",
                     "bonus_time(6i)"=>"50" }
      topic = Topic.new(attributes)
      assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
      Topic.default_timezone = :local
    end
345 346
  end

D
Initial  
David Heinemeier Hansson 已提交
347 348 349 350 351 352 353 354 355
  def test_default_values_on_empty_strings
    topic = Topic.new
    topic.approved  = nil
    topic.last_read = nil

    topic.save

    topic = Topic.find(topic.id)
    assert_nil topic.last_read
356 357 358 359 360 361 362

    # Sybase adapter does not allow nulls in boolean columns
    if current_adapter?(:SybaseAdapter)
      assert topic.approved == false
    else
      assert_nil topic.approved
    end
D
Initial  
David Heinemeier Hansson 已提交
363
  end
364

D
Initial  
David Heinemeier Hansson 已提交
365
  def test_equality
366
    assert_equal Topic.find(1), Topic.find(2).topic
D
Initial  
David Heinemeier Hansson 已提交
367
  end
J
Jeremy Kemper 已提交
368

369 370 371
  def test_equality_of_new_records
    assert_not_equal Topic.new, Topic.new
  end
J
Jeremy Kemper 已提交
372

D
Initial  
David Heinemeier Hansson 已提交
373
  def test_hashing
374
    assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
D
Initial  
David Heinemeier Hansson 已提交
375
  end
J
Jeremy Kemper 已提交
376

377
  def test_readonly_attributes
378
    assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
J
Jeremy Kemper 已提交
379

380 381 382
    post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
    post.reload
    assert_equal "cannot change this", post.title
J
Jeremy Kemper 已提交
383

384 385 386 387 388
    post.update_attributes(:title => "try to change", :body => "changed")
    post.reload
    assert_equal "cannot change this", post.title
    assert_equal "changed", post.body
  end
D
Initial  
David Heinemeier Hansson 已提交
389 390 391 392 393

  def test_multiparameter_attributes_on_date
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
    topic = Topic.find(1)
    topic.attributes = attributes
J
Jeremy Kemper 已提交
394
    # note that extra #to_date call allows test to pass for Oracle, which
395
    # treats dates/times the same
396
    assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
D
Initial  
David Heinemeier Hansson 已提交
397 398
  end

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
  def test_multiparameter_attributes_on_date_with_empty_year
    attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which
    # treats dates/times the same
    assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_empty_month
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which
    # treats dates/times the same
    assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_empty_day
D
Initial  
David Heinemeier Hansson 已提交
418 419 420
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
J
Jeremy Kemper 已提交
421
    # note that extra #to_date call allows test to pass for Oracle, which
422
    # treats dates/times the same
423
    assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
D
Initial  
David Heinemeier Hansson 已提交
424 425
  end

426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
  def test_multiparameter_attributes_on_date_with_empty_day_and_year
    attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which
    # treats dates/times the same
    assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_empty_day_and_month
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which
    # treats dates/times the same
    assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_empty_year_and_month
    attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which
    # treats dates/times the same
    assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date
  end

D
Initial  
David Heinemeier Hansson 已提交
453 454 455 456 457 458 459 460
  def test_multiparameter_attributes_on_date_with_all_empty
    attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_nil topic.last_read
  end

  def test_multiparameter_attributes_on_time
J
Jeremy Kemper 已提交
461 462
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
D
Initial  
David Heinemeier Hansson 已提交
463 464 465 466 467 468
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  end
469

470 471 472 473 474 475 476 477 478 479
  def test_multiparameter_attributes_on_time_with_old_date
    attributes = {
      "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform
    assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
  end
D
Initial  
David Heinemeier Hansson 已提交
480

481 482 483 484 485 486 487 488 489 490 491 492 493
  def test_multiparameter_attributes_on_time_with_utc
    ActiveRecord::Base.default_timezone = :utc
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
  ensure
    ActiveRecord::Base.default_timezone = :local
  end

494 495 496
  def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
    ActiveRecord::Base.time_zone_aware_attributes = true
    ActiveRecord::Base.default_timezone = :utc
497
    Time.zone = ActiveSupport::TimeZone[-28800]
498 499 500 501 502 503 504 505 506 507 508 509 510
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
    assert_equal Time.zone, topic.written_on.time_zone
  ensure
    ActiveRecord::Base.time_zone_aware_attributes = false
    ActiveRecord::Base.default_timezone = :local
    Time.zone = nil
511
  end
512

513 514
  def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
    ActiveRecord::Base.time_zone_aware_attributes = false
515
    Time.zone = ActiveSupport::TimeZone[-28800]
516 517 518 519 520 521 522 523 524 525 526 527
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
    assert_equal false, topic.written_on.respond_to?(:time_zone)
  ensure
    Time.zone = nil
  end

528 529 530
  def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
    ActiveRecord::Base.time_zone_aware_attributes = true
    ActiveRecord::Base.default_timezone = :utc
531
    Time.zone = ActiveSupport::TimeZone[-28800]
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
    Topic.skip_time_zone_conversion_for_attributes = [:written_on]
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
    assert_equal false, topic.written_on.respond_to?(:time_zone)
  ensure
    ActiveRecord::Base.time_zone_aware_attributes = false
    ActiveRecord::Base.default_timezone = :local
    Time.zone = nil
    Topic.skip_time_zone_conversion_for_attributes = []
  end
547

548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
  # Oracle, and Sybase do not have a TIME datatype.
  unless current_adapter?(:OracleAdapter, :SybaseAdapter)
    def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
      ActiveRecord::Base.time_zone_aware_attributes = true
      ActiveRecord::Base.default_timezone = :utc
      Time.zone = ActiveSupport::TimeZone[-28800]
      attributes = {
        "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
        "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
      }
      topic = Topic.find(1)
      topic.attributes = attributes
      assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
      assert topic.bonus_time.utc?
    ensure
      ActiveRecord::Base.time_zone_aware_attributes = false
      ActiveRecord::Base.default_timezone = :local
      Time.zone = nil
    end
567
  end
568

D
Initial  
David Heinemeier Hansson 已提交
569
  def test_multiparameter_attributes_on_time_with_empty_seconds
J
Jeremy Kemper 已提交
570 571
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
D
Initial  
David Heinemeier Hansson 已提交
572 573 574 575 576 577 578
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  end

579 580 581 582 583 584 585
  def test_multiparameter_assignment_of_aggregation
    customer = Customer.new
    address = Address.new("The Street", "The City", "The Country")
    attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
    customer.attributes = attributes
    assert_equal address, customer.address
  end
586

587
  def test_attributes_on_dummy_time
588 589
    # Oracle, and Sybase do not have a TIME datatype.
    return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
590

591 592 593 594 595 596 597 598
    attributes = {
      "bonus_time" => "5:42:00AM"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
  end

D
Initial  
David Heinemeier Hansson 已提交
599
  def test_boolean
600
    b_nil = Boolean.create({ "value" => nil })
601
    nil_id = b_nil.id
602
    b_false = Boolean.create({ "value" => false })
D
Initial  
David Heinemeier Hansson 已提交
603
    false_id = b_false.id
604
    b_true = Boolean.create({ "value" => true })
D
Initial  
David Heinemeier Hansson 已提交
605 606
    true_id = b_true.id

607
    b_nil = Boolean.find(nil_id)
608
    assert_nil b_nil.value
609
    b_false = Boolean.find(false_id)
D
Initial  
David Heinemeier Hansson 已提交
610
    assert !b_false.value?
611
    b_true = Boolean.find(true_id)
D
Initial  
David Heinemeier Hansson 已提交
612 613
    assert b_true.value?
  end
614 615

  def test_boolean_cast_from_string
616
    b_blank = Boolean.create({ "value" => "" })
617
    blank_id = b_blank.id
618
    b_false = Boolean.create({ "value" => "0" })
619
    false_id = b_false.id
620
    b_true = Boolean.create({ "value" => "1" })
621 622
    true_id = b_true.id

623
    b_blank = Boolean.find(blank_id)
624
    assert_nil b_blank.value
625
    b_false = Boolean.find(false_id)
626
    assert !b_false.value?
627
    b_true = Boolean.find(true_id)
J
Jeremy Kemper 已提交
628
    assert b_true.value?
629
  end
J
Jeremy Kemper 已提交
630

631
  def test_new_record_returns_boolean
632 633
    assert_equal true, Topic.new.new_record?
    assert_equal false, Topic.find(1).new_record?
634 635
  end

D
Initial  
David Heinemeier Hansson 已提交
636 637
  def test_clone
    topic = Topic.find(1)
638 639
    cloned_topic = nil
    assert_nothing_raised { cloned_topic = topic.clone }
D
Initial  
David Heinemeier Hansson 已提交
640
    assert_equal topic.title, cloned_topic.title
641
    assert cloned_topic.new_record?
D
Initial  
David Heinemeier Hansson 已提交
642 643

    # test if the attributes have been cloned
J
Jeremy Kemper 已提交
644 645
    topic.title = "a"
    cloned_topic.title = "b"
D
Initial  
David Heinemeier Hansson 已提交
646 647 648 649 650 651
    assert_equal "a", topic.title
    assert_equal "b", cloned_topic.title

    # test if the attribute values have been cloned
    topic.title = {"a" => "b"}
    cloned_topic = topic.clone
J
Jeremy Kemper 已提交
652
    cloned_topic.title["a"] = "c"
D
Initial  
David Heinemeier Hansson 已提交
653
    assert_equal "b", topic.title["a"]
654

655
    # test if attributes set as part of after_initialize are cloned correctly
656 657 658
    assert_equal topic.author_email_address, cloned_topic.author_email_address

    # test if saved clone object differs from original
659
    cloned_topic.save
660
    assert !cloned_topic.new_record?
661
    assert_not_equal cloned_topic.id, topic.id
D
Initial  
David Heinemeier Hansson 已提交
662
  end
663 664 665 666 667 668 669 670 671

  def test_clone_with_aggregate_of_same_name_as_attribute
    dev = DeveloperWithAggregate.find(1)
    assert_kind_of DeveloperSalary, dev.salary

    clone = nil
    assert_nothing_raised { clone = dev.clone }
    assert_kind_of DeveloperSalary, clone.salary
    assert_equal dev.salary.amount, clone.salary.amount
672
    assert clone.new_record?
673 674 675 676 677 678 679

    # test if the attributes have been cloned
    original_amount = clone.salary.amount
    dev.salary.amount = 1
    assert_equal original_amount, clone.salary.amount

    assert clone.save
680
    assert !clone.new_record?
681
    assert_not_equal clone.id, dev.id
682 683
  end

684 685 686 687 688 689 690 691
  def test_clone_does_not_clone_associations
    author = authors(:david)
    assert_not_equal [], author.posts

    author_clone = author.clone
    assert_equal [], author_clone.posts
  end

692 693 694 695 696 697
  def test_clone_preserves_subtype
    clone = nil
    assert_nothing_raised { clone = Company.find(3).clone }
    assert_kind_of Client, clone
  end

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
  def test_clone_of_new_object_with_defaults
    developer = Developer.new
    assert !developer.name_changed?
    assert !developer.salary_changed?

    cloned_developer = developer.clone
    assert !cloned_developer.name_changed?
    assert !cloned_developer.salary_changed?
  end

  def test_clone_of_new_object_marks_attributes_as_dirty
    developer = Developer.new :name => 'Bjorn', :salary => 100000
    assert developer.name_changed?
    assert developer.salary_changed?

    cloned_developer = developer.clone
    assert cloned_developer.name_changed?
    assert cloned_developer.salary_changed?
  end

  def test_clone_of_new_object_marks_as_dirty_only_changed_attributes
    developer = Developer.new :name => 'Bjorn'
    assert developer.name_changed?            # obviously
    assert !developer.salary_changed?         # attribute has non-nil default value, so treated as not changed

    cloned_developer = developer.clone
    assert cloned_developer.name_changed?
    assert !cloned_developer.salary_changed?  # ... and cloned instance should behave same
  end

  def test_clone_of_saved_object_marks_attributes_as_dirty
    developer = Developer.create! :name => 'Bjorn', :salary => 100000
    assert !developer.name_changed?
    assert !developer.salary_changed?

    cloned_developer = developer.clone
    assert cloned_developer.name_changed?     # both attributes differ from defaults
    assert cloned_developer.salary_changed?
  end

  def test_clone_of_saved_object_marks_as_dirty_only_changed_attributes
    developer = Developer.create! :name => 'Bjorn'
    assert !developer.name_changed?           # both attributes of saved object should be threated as not changed
    assert !developer.salary_changed?

    cloned_developer = developer.clone
    assert cloned_developer.name_changed?     # ... but on cloned object should be
    assert !cloned_developer.salary_changed?  # ... BUT salary has non-nil default which should be threated as not changed on cloned instance
  end

D
Initial  
David Heinemeier Hansson 已提交
748 749 750 751 752 753 754 755
  def test_bignum
    company = Company.find(1)
    company.rating = 2147483647
    company.save
    company = Company.find(1)
    assert_equal 2147483647, company.rating
  end

756
  # TODO: extend defaults tests to other databases!
757
  if current_adapter?(:PostgreSQLAdapter)
758
    def test_default
D
Initial  
David Heinemeier Hansson 已提交
759
      default = Default.new
J
Jeremy Kemper 已提交
760

D
Initial  
David Heinemeier Hansson 已提交
761 762 763
      # fixed dates / times
      assert_equal Date.new(2004, 1, 1), default.fixed_date
      assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
J
Jeremy Kemper 已提交
764

D
Initial  
David Heinemeier Hansson 已提交
765 766 767 768 769
      # char types
      assert_equal 'Y', default.char1
      assert_equal 'a varchar field', default.char2
      assert_equal 'a text field', default.char3
    end
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786

    class Geometric < ActiveRecord::Base; end
    def test_geometric_content

      # accepted format notes:
      # ()'s aren't required
      # values can be a mix of float or integer

      g = Geometric.new(
        :a_point        => '(5.0, 6.1)',
        #:a_line         => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
        :a_line_segment => '(2.0, 3), (5.5, 7.0)',
        :a_box          => '2.0, 3, 5.5, 7.0',
        :a_path         => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]',  # [ ] is an open path
        :a_polygon      => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
        :a_circle       => '<(5.3, 10.4), 2>'
      )
J
Jeremy Kemper 已提交
787

788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
      assert g.save

      # Reload and check that we have all the geometric attributes.
      h = Geometric.find(g.id)

      assert_equal '(5,6.1)', h.a_point
      assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
      assert_equal '(5.5,7),(2,3)', h.a_box   # reordered to store upper right corner then bottom left corner
      assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
      assert_equal '<(5.3,10.4),2>', h.a_circle

      # use a geometric function to test for an open path
      objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
      assert_equal objs[0].isopen, 't'

      # test alternate formats when defining the geometric types
J
Jeremy Kemper 已提交
805

806 807 808 809 810 811 812 813 814 815 816 817 818 819
      g = Geometric.new(
        :a_point        => '5.0, 6.1',
        #:a_line         => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
        :a_line_segment => '((2.0, 3), (5.5, 7.0))',
        :a_box          => '(2.0, 3), (5.5, 7.0)',
        :a_path         => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',  # ( ) is a closed path
        :a_polygon      => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
        :a_circle       => '((5.3, 10.4), 2)'
      )

      assert g.save

      # Reload and check that we have all the geometric attributes.
      h = Geometric.find(g.id)
J
Jeremy Kemper 已提交
820

821 822 823 824 825 826 827 828 829 830 831
      assert_equal '(5,6.1)', h.a_point
      assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
      assert_equal '(5.5,7),(2,3)', h.a_box   # reordered to store upper right corner then bottom left corner
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
      assert_equal '<(5.3,10.4),2>', h.a_circle

      # use a geometric function to test for an closed path
      objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
      assert_equal objs[0].isclosed, 't'
    end
D
Initial  
David Heinemeier Hansson 已提交
832 833
  end

834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
  class NumericData < ActiveRecord::Base
    self.table_name = 'numeric_data'
  end

  def test_numeric_fields
    m = NumericData.new(
      :bank_balance => 1586.43,
      :big_bank_balance => BigDecimal("1000234000567.95"),
      :world_population => 6000000000,
      :my_house_population => 3
    )
    assert m.save

    m1 = NumericData.find(m.id)
    assert_not_nil m1

    # As with migration_test.rb, we should make world_population >= 2**62
    # to cover 64-bit platforms and test it is a Bignum, but the main thing
    # is that it's an Integer.
    assert_kind_of Integer, m1.world_population
    assert_equal 6000000000, m1.world_population

    assert_kind_of Fixnum, m1.my_house_population
    assert_equal 3, m1.my_house_population

    assert_kind_of BigDecimal, m1.bank_balance
    assert_equal BigDecimal("1586.43"), m1.bank_balance

    assert_kind_of BigDecimal, m1.big_bank_balance
    assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
  end

D
Initial  
David Heinemeier Hansson 已提交
866 867 868
  def test_auto_id
    auto = AutoId.new
    auto.save
869
    assert(auto.id > 0)
D
Initial  
David Heinemeier Hansson 已提交
870
  end
871

D
Initial  
David Heinemeier Hansson 已提交
872 873 874 875 876 877 878 879 880 881 882 883 884
  def quote_column_name(name)
    "<#{name}>"
  end

  def test_quote_keys
    ar = AutoId.new
    source = {"foo" => "bar", "baz" => "quux"}
    actual = ar.send(:quote_columns, self, source)
    inverted = actual.invert
    assert_equal("<foo>", inverted["bar"])
    assert_equal("<baz>", inverted["quux"])
  end

885
  def test_sql_injection_via_find
886
    assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
887 888 889 890
      Topic.find("123456 OR id > 0")
    end
  end

D
Initial  
David Heinemeier Hansson 已提交
891 892 893
  def test_column_name_properly_quoted
    col_record = ColumnName.new
    col_record.references = 40
894
    assert col_record.save
D
Initial  
David Heinemeier Hansson 已提交
895
    col_record.references = 41
896 897
    assert col_record.save
    assert_not_nil c2 = ColumnName.find(col_record.id)
D
Initial  
David Heinemeier Hansson 已提交
898 899 900
    assert_equal(41, c2.references)
  end

901
  def test_quoting_arrays
902
    replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
903 904
    assert_equal topics(:first).replies.size, replies.size

905
    replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
906 907 908
    assert_equal 0, replies.size
  end

D
Initial  
David Heinemeier Hansson 已提交
909
  MyObject = Struct.new :attribute1, :attribute2
J
Jeremy Kemper 已提交
910

D
Initial  
David Heinemeier Hansson 已提交
911 912
  def test_serialized_attribute
    myobj = MyObject.new('value1', 'value2')
J
Jeremy Kemper 已提交
913
    topic = Topic.create("content" => myobj)
D
Initial  
David Heinemeier Hansson 已提交
914 915 916 917
    Topic.serialize("content", MyObject)
    assert_equal(myobj, topic.content)
  end

918 919 920 921 922
  def test_serialized_time_attribute
    myobj = Time.local(2008,1,1,1,0)
    topic = Topic.create("content" => myobj).reload
    assert_equal(myobj, topic.content)
  end
923

924 925 926 927 928
  def test_serialized_string_attribute
    myobj = "Yes"
    topic = Topic.create("content" => myobj).reload
    assert_equal(myobj, topic.content)
  end
929

930
  def test_nil_serialized_attribute_with_class_constraint
D
Initial  
David Heinemeier Hansson 已提交
931
    myobj = MyObject.new('value1', 'value2')
932 933 934
    topic = Topic.new
    assert_nil topic.content
  end
D
Initial  
David Heinemeier Hansson 已提交
935

936 937 938 939 940
  def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
    myobj = MyObject.new('value1', 'value2')
    topic = Topic.new(:content => myobj)
    assert topic.save
    Topic.serialize(:content, Hash)
941
    assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
942 943 944
  ensure
    Topic.serialize(:content)
  end
D
Initial  
David Heinemeier Hansson 已提交
945

946
  def test_serialized_attribute_with_class_constraint
D
Initial  
David Heinemeier Hansson 已提交
947
    settings = { "color" => "blue" }
948 949 950
    Topic.serialize(:content, Hash)
    topic = Topic.new(:content => settings)
    assert topic.save
D
Initial  
David Heinemeier Hansson 已提交
951
    assert_equal(settings, Topic.find(topic.id).content)
952
  ensure
D
Initial  
David Heinemeier Hansson 已提交
953 954 955 956
    Topic.serialize(:content)
  end

  def test_quote
957 958 959
    author_name = "\\ \001 ' \n \\n \""
    topic = Topic.create('author_name' => author_name)
    assert_equal author_name, Topic.find(topic.id).author_name
D
Initial  
David Heinemeier Hansson 已提交
960
  end
961 962 963

  if RUBY_VERSION < '1.9'
    def test_quote_chars
964 965 966 967
      with_kcode('UTF8') do
        str = 'The Narrator'
        topic = Topic.create(:author_name => str)
        assert_equal str, topic.author_name
968

969 970
        assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars
        topic = Topic.find_by_author_name(str.mb_chars)
971

972 973 974
        assert_kind_of Topic, topic
        assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
      end
975
    end
976
  end
977

978
  def test_toggle_attribute
979 980 981
    assert !topics(:first).approved?
    topics(:first).toggle!(:approved)
    assert topics(:first).approved?
982 983 984 985 986
    topic = topics(:first)
    topic.toggle(:approved)
    assert !topic.approved?
    topic.reload
    assert topic.approved?
987
  end
988 989 990 991 992 993 994 995 996

  def test_reload
    t1 = Topic.find(1)
    t2 = Topic.find(1)
    t1.title = "something else"
    t1.save
    t2.reload
    assert_equal t1.title, t2.title
  end
997

998 999 1000 1001 1002 1003
  def test_reload_with_exclusive_scope
    dev = DeveloperCalledDavid.first
    dev.update_attributes!( :name => "NotDavid" )
    assert_equal dev, dev.reload
  end

1004 1005
  def test_define_attr_method_with_value
    k = Class.new( ActiveRecord::Base )
1006
    k.send(:define_attr_method, :table_name, "foo")
1007 1008 1009 1010 1011
    assert_equal "foo", k.table_name
  end

  def test_define_attr_method_with_block
    k = Class.new( ActiveRecord::Base )
1012
    k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
    assert_equal "sys_id", k.primary_key
  end

  def test_set_table_name_with_value
    k = Class.new( ActiveRecord::Base )
    k.table_name = "foo"
    assert_equal "foo", k.table_name
    k.set_table_name "bar"
    assert_equal "bar", k.table_name
  end

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
  def test_quoted_table_name_after_set_table_name
    klass = Class.new(ActiveRecord::Base)

    klass.set_table_name "foo"
    assert_equal "foo", klass.table_name
    assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name

    klass.set_table_name "bar"
    assert_equal "bar", klass.table_name
    assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name
  end

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
  def test_set_table_name_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_table_name { "ks" }
    assert_equal "ks", k.table_name
  end

  def test_set_primary_key_with_value
    k = Class.new( ActiveRecord::Base )
    k.primary_key = "foo"
    assert_equal "foo", k.primary_key
    k.set_primary_key "bar"
    assert_equal "bar", k.primary_key
  end

  def test_set_primary_key_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_primary_key { "sys_" + original_primary_key }
    assert_equal "sys_id", k.primary_key
  end

  def test_set_inheritance_column_with_value
    k = Class.new( ActiveRecord::Base )
    k.inheritance_column = "foo"
    assert_equal "foo", k.inheritance_column
    k.set_inheritance_column "bar"
    assert_equal "bar", k.inheritance_column
  end

  def test_set_inheritance_column_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_inheritance_column { original_inheritance_column + "_id" }
    assert_equal "type_id", k.inheritance_column
  end
1069 1070

  def test_count_with_join
1071
    res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
J
Jeremy Kemper 已提交
1072

1073
    res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1074
    assert_equal res, res2
J
Jeremy Kemper 已提交
1075

1076
    res3 = nil
1077 1078 1079 1080 1081
    assert_nothing_raised do
      res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
                        :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
    end
    assert_equal res, res3
J
Jeremy Kemper 已提交
1082

1083
    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"
1084 1085
    res5 = nil
    assert_nothing_raised do
1086 1087
      res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
                        :joins => "p, comments co",
1088 1089 1090
                        :select => "p.id")
    end

J
Jeremy Kemper 已提交
1091
    assert_equal res4, res5
1092

P
Pratik Naik 已提交
1093 1094 1095 1096 1097 1098 1099
    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"
    res7 = nil
    assert_nothing_raised do
      res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
                        :joins => "p, comments co",
                        :select => "p.id",
                        :distinct => true)
1100
    end
P
Pratik Naik 已提交
1101
    assert_equal res6, res7
1102
  end
J
Jeremy Kemper 已提交
1103

1104 1105 1106 1107 1108 1109
  def test_interpolate_sql
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
  end

1110
  def test_scoped_find_conditions
1111
    scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do
1112 1113
      Developer.find(:all, :conditions => 'id < 5')
    end
M
Marcel Molina 已提交
1114 1115
    assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
    assert_equal 3, scoped_developers.size
1116
  end
J
Jeremy Kemper 已提交
1117

1118
  def test_scoped_find_limit_offset
1119
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do
1120
      Developer.find(:all, :order => 'id')
J
Jeremy Kemper 已提交
1121
    end
M
Marcel Molina 已提交
1122 1123 1124
    assert !scoped_developers.include?(developers(:david))
    assert !scoped_developers.include?(developers(:jamis))
    assert_equal 3, scoped_developers.size
J
Jeremy Kemper 已提交
1125

1126
    # Test without scoped find conditions to ensure we get the whole thing
1127
    developers = Developer.find(:all, :order => 'id')
M
Marcel Molina 已提交
1128
    assert_equal Developer.count, developers.size
1129
  end
1130

J
Jeremy Kemper 已提交
1131 1132
  def test_scoped_find_order
    # Test order in scope
1133
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do
1134
      Developer.find(:all)
J
Jeremy Kemper 已提交
1135
    end
1136 1137 1138
    assert_equal 'Jamis', scoped_developers.first.name
    assert scoped_developers.include?(developers(:jamis))
    # Test scope without order and order in find
1139
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do
1140
      Developer.find(:all, :order => 'salary DESC')
J
Jeremy Kemper 已提交
1141
    end
1142
    # Test scope order + find order, find has priority
1143
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do
1144 1145 1146 1147
      Developer.find(:all, :order => 'salary ASC')
    end
    assert scoped_developers.include?(developers(:poor_jamis))
    assert scoped_developers.include?(developers(:david))
1148 1149 1150
    assert ! scoped_developers.include?(developers(:jamis))
    assert_equal 3, scoped_developers.size

1151 1152 1153 1154 1155
    # Test without scoped find conditions to ensure we get the right thing
    developers = Developer.find(:all, :order => 'id', :limit => 1)
    assert scoped_developers.include?(developers(:david))
  end

1156
  def test_scoped_find_limit_offset_including_has_many_association
1157
    topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do
1158 1159 1160 1161 1162 1163
      Topic.find(:all, :order => "topics.id")
    end
    assert_equal 1, topics.size
    assert_equal 2, topics.first.id
  end

1164
  def test_scoped_find_order_including_has_many_association
1165
    developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do
1166 1167 1168 1169 1170 1171 1172 1173
      Developer.find(:all)
    end
    assert developers.size >= 2
    for i in 1...developers.size
      assert developers[i-1].salary >= developers[i].salary
    end
  end

1174
  def test_scoped_find_with_group_and_having
1175
    developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
1176 1177 1178 1179 1180
      Developer.find(:all)
    end
    assert_equal 3, developers.size
  end

1181 1182 1183 1184
  def test_find_last
    last  = Developer.find :last
    assert_equal last, Developer.find(:first, :order => 'id desc')
  end
1185

1186 1187 1188
  def test_last
    assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
  end
1189

1190 1191 1192 1193 1194 1195
  def test_all
    developers = Developer.all
    assert_kind_of Array, developers
    assert_equal Developer.find(:all), developers
  end

1196
  def test_all_with_conditions
1197
    assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all
1198
  end
1199

1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
  def test_find_ordered_last
    last  = Developer.find :last, :order => 'developers.salary ASC'
    assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
  end

  def test_find_reverse_ordered_last
    last  = Developer.find :last, :order => 'developers.salary DESC'
    assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last
  end

  def test_find_multiple_ordered_last
    last  = Developer.find :last, :order => 'developers.name, developers.salary DESC'
    assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
  end
1214

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
  def test_find_keeps_multiple_order_values
    combined = Developer.find(:all, :order => 'developers.name, developers.salary')
    assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary'])
  end

  def test_find_keeps_multiple_group_values
    combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at')
    assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at'])
  end

1225 1226 1227 1228 1229
  def test_find_symbol_ordered_last
    last  = Developer.find :last, :order => :salary
    assert_equal last, Developer.find(:all, :order => :salary).last
  end

1230
  def test_find_scoped_ordered_last
1231
    last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do
1232 1233 1234 1235
      Developer.find(:last)
    end
    assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
  end
1236

1237
  def test_abstract_class
1238
    assert !ActiveRecord::Base.abstract_class?
1239 1240
    assert LoosePerson.abstract_class?
    assert !LooseDescendant.abstract_class?
1241 1242 1243
  end

  def test_base_class
1244 1245 1246 1247
    assert_equal LoosePerson,     LoosePerson.base_class
    assert_equal LooseDescendant, LooseDescendant.base_class
    assert_equal TightPerson,     TightPerson.base_class
    assert_equal TightPerson,     TightDescendant.base_class
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280

    assert_equal Post, Post.base_class
    assert_equal Post, SpecialPost.base_class
    assert_equal Post, StiPost.base_class
    assert_equal SubStiPost, SubStiPost.base_class
  end

  def test_descends_from_active_record
    # Tries to call Object.abstract_class?
    assert_raise(NoMethodError) do
      ActiveRecord::Base.descends_from_active_record?
    end

    # Abstract subclass of AR::Base.
    assert LoosePerson.descends_from_active_record?

    # Concrete subclass of an abstract class.
    assert LooseDescendant.descends_from_active_record?

    # Concrete subclass of AR::Base.
    assert TightPerson.descends_from_active_record?

    # Concrete subclass of a concrete class but has no type column.
    assert TightDescendant.descends_from_active_record?

    # Concrete subclass of AR::Base.
    assert Post.descends_from_active_record?

    # Abstract subclass of a concrete class which has a type column.
    # This is pathological, as you'll never have Sub < Abstract < Concrete.
    assert !StiPost.descends_from_active_record?

    # Concrete subclasses an abstract class which has a type column.
1281
    assert !SubStiPost.descends_from_active_record?
1282 1283 1284 1285 1286 1287
  end

  def test_find_on_abstract_base_class_doesnt_use_type_condition
    old_class = LooseDescendant
    Object.send :remove_const, :LooseDescendant

1288
    descendant = old_class.create! :first_name => 'bob'
1289 1290 1291 1292 1293
    assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
  ensure
    unless Object.const_defined?(:LooseDescendant)
      Object.const_set :LooseDescendant, old_class
    end
1294 1295
  end

1296 1297 1298 1299 1300 1301 1302
  def test_assert_queries
    query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
    assert_queries(2) { 2.times { query.call } }
    assert_queries 1, &query
    assert_no_queries { assert true }
  end

1303 1304 1305
  def test_to_param_should_return_string
    assert_kind_of String, Client.find(:first).to_param
  end
J
Jeremy Kemper 已提交
1306

1307 1308 1309 1310 1311 1312 1313
  def test_inspect_class
    assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
    assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
    assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
  end

  def test_inspect_instance
1314
    topic = topics(:first)
1315
    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", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect
1316
  end
1317

1318
  def test_inspect_new_instance
1319
    assert_match(/Topic id: nil/, Topic.new.inspect)
1320 1321
  end

1322 1323 1324 1325
  def test_inspect_limited_select_instance
    assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
    assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
  end
J
Jeremy Kemper 已提交
1326

1327 1328 1329
  def test_inspect_class_without_table
    assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
  end
1330

1331 1332
  def test_attribute_for_inspect
    t = topics(:first)
1333
    t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
1334 1335

    assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
1336
    assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
1337
  end
J
Jeremy Kemper 已提交
1338

1339 1340 1341 1342
  def test_becomes
    assert_kind_of Reply, topics(:first).becomes(Reply)
    assert_equal "The First Topic", topics(:first).becomes(Reply).title
  end
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374

  def test_silence_sets_log_level_to_error_in_block
    original_logger = ActiveRecord::Base.logger
    log = StringIO.new
    ActiveRecord::Base.logger = Logger.new(log)
    ActiveRecord::Base.logger.level = Logger::DEBUG
    ActiveRecord::Base.silence do
      ActiveRecord::Base.logger.warn "warn"
      ActiveRecord::Base.logger.error "error"
    end
    assert_equal "error\n", log.string
  ensure
    ActiveRecord::Base.logger = original_logger
  end

  def test_silence_sets_log_level_back_to_level_before_yield
    original_logger = ActiveRecord::Base.logger
    log = StringIO.new
    ActiveRecord::Base.logger = Logger.new(log)
    ActiveRecord::Base.logger.level = Logger::WARN
    ActiveRecord::Base.silence do
    end
    assert_equal Logger::WARN, ActiveRecord::Base.logger.level
  ensure
    ActiveRecord::Base.logger = original_logger
  end

  def test_benchmark_with_log_level
    original_logger = ActiveRecord::Base.logger
    log = StringIO.new
    ActiveRecord::Base.logger = Logger.new(log)
    ActiveRecord::Base.logger.level = Logger::WARN
J
José Valim 已提交
1375 1376 1377
    ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count }
    ActiveRecord::Base.benchmark("Warn Topic Count",  :level => :warn)  { Topic.count }
    ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count }
1378 1379 1380
    assert_no_match(/Debug Topic Count/, log.string)
    assert_match(/Warn Topic Count/, log.string)
    assert_match(/Error Topic Count/, log.string)
1381 1382 1383 1384 1385 1386 1387 1388
  ensure
    ActiveRecord::Base.logger = original_logger
  end

  def test_benchmark_with_use_silence
    original_logger = ActiveRecord::Base.logger
    log = StringIO.new
    ActiveRecord::Base.logger = Logger.new(log)
J
José Valim 已提交
1389 1390
    ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" }
    ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false)  { ActiveRecord::Base.logger.debug "Quiet" }
1391 1392
    assert_no_match(/Loud/, log.string)
    assert_match(/Quiet/, log.string)
1393 1394 1395
  ensure
    ActiveRecord::Base.logger = original_logger
  end
1396

1397 1398 1399
  def test_dup
    assert !Minimalistic.new.freeze.dup.frozen?
  end
1400

1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417
  def test_compute_type_success
    assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
  end

  def test_compute_type_nonexistent_constant
    assert_raises NameError do
      ActiveRecord::Base.send :compute_type, 'NonexistentModel'
    end
  end

  def test_compute_type_no_method_error
    String.any_instance.stubs(:constantize).raises(NoMethodError)
    assert_raises NoMethodError do
      ActiveRecord::Base.send :compute_type, 'InvalidModel'
    end
  end

1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431
  protected
    def with_env_tz(new_tz = 'US/Eastern')
      old_tz, ENV['TZ'] = ENV['TZ'], new_tz
      yield
    ensure
      old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
    end

    def with_active_record_default_timezone(zone)
      old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
      yield
    ensure
      ActiveRecord::Base.default_timezone = old_zone
    end
1432
end