base_test.rb 51.1 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 55
  def test_select_symbol
    topic_ids = Topic.select(:id).map(&:id).sort
    assert_equal Topic.find(:all).map(&:id), topic_ids
  end

56 57 58 59
  def test_table_exists
    assert !NonExistentTable.table_exists?
    assert Topic.table_exists?
  end
J
Jeremy Kemper 已提交
60

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

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
  def test_use_table_engine_for_quoting_where
    relation = Topic.where(Topic.arel_table[:id].eq(1))
    engine = relation.table.engine

    fakepool = Class.new(Struct.new(:spec)) {
      def with_connection; yield self; end
      def connection_pool; self; end
      def quote_table_name(*args); raise "lol quote_table_name"; end
    }

    relation.table.engine = fakepool.new(engine.connection_pool.spec)

    error = assert_raises(RuntimeError) { relation.to_a }
    assert_match('lol', error.message)
  ensure
    relation.table.engine = engine
  end

95
  def test_preserving_time_objects
96 97 98 99
    assert_kind_of(
      Time, Topic.find(1).bonus_time,
      "The bonus_time attribute should be of the Time class"
    )
D
Initial  
David Heinemeier Hansson 已提交
100 101 102 103 104

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

    # For adapters which support microsecond resolution.
107
    if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter)
108 109
      assert_equal 11, Topic.find(1).written_on.sec
      assert_equal 223300, Topic.find(1).written_on.usec
110
      assert_equal 9900, Topic.find(2).written_on.usec
111
    end
D
Initial  
David Heinemeier Hansson 已提交
112
  end
J
Jeremy Kemper 已提交
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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
  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

168 169 170 171 172 173
  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 已提交
174

D
Initial  
David Heinemeier Hansson 已提交
175
  def test_initialize_with_attributes
J
Jeremy Kemper 已提交
176
    topic = Topic.new({
D
Initial  
David Heinemeier Hansson 已提交
177 178
      "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
    })
J
Jeremy Kemper 已提交
179

D
Initial  
David Heinemeier Hansson 已提交
180 181
    assert_equal("initialized from attributes", topic.title)
  end
J
Jeremy Kemper 已提交
182

183 184
  def test_initialize_with_invalid_attribute
    begin
J
Jeremy Kemper 已提交
185
      topic = Topic.new({ "title" => "test",
186 187 188 189 190 191
        "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 已提交
192

D
Initial  
David Heinemeier Hansson 已提交
193
  def test_load
J
Jeremy Kemper 已提交
194
    topics = Topic.find(:all, :order => 'id')
195
    assert_equal(4, topics.size)
196
    assert_equal(topics(:first).title, topics.first.title)
D
Initial  
David Heinemeier Hansson 已提交
197
  end
J
Jeremy Kemper 已提交
198

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

D
Initial  
David Heinemeier Hansson 已提交
202
    assert_equal(1, topics.size)
203
    assert_equal(topics(:second).title, topics.first.title)
D
Initial  
David Heinemeier Hansson 已提交
204 205
  end

206
  GUESSED_CLASSES = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
207

208
  def test_table_name_guesses
D
Initial  
David Heinemeier Hansson 已提交
209
    assert_equal "topics", Topic.table_name
210

D
Initial  
David Heinemeier Hansson 已提交
211 212 213
    assert_equal "categories", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_cards", CreditCard.table_name
214
    assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
215 216 217
    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 已提交
218
    assert_equal "master_credit_cards", MasterCreditCard.table_name
219 220 221
  ensure
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
222

223
  def test_singular_table_name_guesses
D
Initial  
David Heinemeier Hansson 已提交
224
    ActiveRecord::Base.pluralize_table_names = false
225
    GUESSED_CLASSES.each(&:reset_table_name)
226

D
Initial  
David Heinemeier Hansson 已提交
227 228 229
    assert_equal "category", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_card", CreditCard.table_name
230
    assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
231 232 233
    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 已提交
234
    assert_equal "master_credit_card", MasterCreditCard.table_name
235
  ensure
D
Initial  
David Heinemeier Hansson 已提交
236
    ActiveRecord::Base.pluralize_table_names = true
237 238
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
239

240
  def test_table_name_guesses_with_prefixes_and_suffixes
D
Initial  
David Heinemeier Hansson 已提交
241
    ActiveRecord::Base.table_name_prefix = "test_"
242
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
243 244
    assert_equal "test_categories", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
245
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
246 247
    assert_equal "test_categories_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
248
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
249 250
    assert_equal "categories_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
251
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
252
    assert_equal "categories", Category.table_name
253 254 255 256 257
  ensure
    ActiveRecord::Base.table_name_prefix = ""
    ActiveRecord::Base.table_name_suffix = ""
    GUESSED_CLASSES.each(&:reset_table_name)
  end
D
Initial  
David Heinemeier Hansson 已提交
258

259
  def test_singular_table_name_guesses_with_prefixes_and_suffixes
D
Initial  
David Heinemeier Hansson 已提交
260
    ActiveRecord::Base.pluralize_table_names = false
261

D
Initial  
David Heinemeier Hansson 已提交
262
    ActiveRecord::Base.table_name_prefix = "test_"
263
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
264 265
    assert_equal "test_category", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
266
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
267 268
    assert_equal "test_category_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
269
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
270 271
    assert_equal "category_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
272
    Category.reset_table_name
D
Initial  
David Heinemeier Hansson 已提交
273
    assert_equal "category", Category.table_name
274
  ensure
D
Initial  
David Heinemeier Hansson 已提交
275
    ActiveRecord::Base.pluralize_table_names = true
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    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 已提交
308
  end
J
Jeremy Kemper 已提交
309 310


B
Brian Lopez 已提交
311
  if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
312 313 314 315 316
    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 已提交
317 318 319 320
  def test_null_fields
    assert_nil Topic.find(1).parent_id
    assert_nil Topic.create("title" => "Hey you").parent_id
  end
J
Jeremy Kemper 已提交
321

D
Initial  
David Heinemeier Hansson 已提交
322 323
  def test_default_values
    topic = Topic.new
J
Jeremy Kemper 已提交
324
    assert topic.approved?
D
Initial  
David Heinemeier Hansson 已提交
325
    assert_nil topic.written_on
326
    assert_nil topic.bonus_time
D
Initial  
David Heinemeier Hansson 已提交
327
    assert_nil topic.last_read
J
Jeremy Kemper 已提交
328

D
Initial  
David Heinemeier Hansson 已提交
329 330 331
    topic.save

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

J
Jeremy Kemper 已提交
335
    # Oracle has some funky default handling, so it requires a bit of
336
    # extra testing. See ticket #2788.
337 338
    if current_adapter?(:OracleAdapter)
      test = TestOracleDefault.new
339 340 341 342
      assert_equal "X", test.test_char
      assert_equal "hello", test.test_string
      assert_equal 3, test.test_int
    end
D
Initial  
David Heinemeier Hansson 已提交
343
  end
344

345 346
  # Oracle, and Sybase do not have a TIME datatype.
  unless current_adapter?(:OracleAdapter, :SybaseAdapter)
347 348 349 350 351 352 353 354
    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
355

356 357 358 359 360 361 362 363 364 365 366 367
    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
368 369
  end

D
Initial  
David Heinemeier Hansson 已提交
370 371 372 373 374 375 376 377 378
  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
379 380 381 382 383 384 385

    # 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 已提交
386
  end
387

D
Initial  
David Heinemeier Hansson 已提交
388
  def test_equality
389
    assert_equal Topic.find(1), Topic.find(2).topic
D
Initial  
David Heinemeier Hansson 已提交
390
  end
J
Jeremy Kemper 已提交
391

392 393 394 395
  def test_find_by_slug
    assert_equal Topic.find('1-meowmeow'), Topic.find(1)
  end

396 397 398
  def test_equality_of_new_records
    assert_not_equal Topic.new, Topic.new
  end
J
Jeremy Kemper 已提交
399

D
Initial  
David Heinemeier Hansson 已提交
400
  def test_hashing
401
    assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
D
Initial  
David Heinemeier Hansson 已提交
402
  end
J
Jeremy Kemper 已提交
403

404
  def test_readonly_attributes
405
    assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
J
Jeremy Kemper 已提交
406

407 408 409
    post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
    post.reload
    assert_equal "cannot change this", post.title
J
Jeremy Kemper 已提交
410

411 412 413 414 415
    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 已提交
416 417 418 419 420

  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 已提交
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, 24), 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
  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 已提交
445 446 447
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
J
Jeremy Kemper 已提交
448
    # note that extra #to_date call allows test to pass for Oracle, which
449
    # treats dates/times the same
450
    assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
D
Initial  
David Heinemeier Hansson 已提交
451 452
  end

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
  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 已提交
480 481 482 483 484 485 486 487
  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 已提交
488 489
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
D
Initial  
David Heinemeier Hansson 已提交
490 491 492 493 494 495
      "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
496

497 498 499 500 501 502 503 504 505 506
  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 已提交
507

508 509 510 511 512 513 514 515 516 517 518 519 520
  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

521 522 523
  def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
    ActiveRecord::Base.time_zone_aware_attributes = true
    ActiveRecord::Base.default_timezone = :utc
524
    Time.zone = ActiveSupport::TimeZone[-28800]
525 526 527 528 529 530 531 532 533 534 535 536 537
    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
538
  end
539

540 541
  def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
    ActiveRecord::Base.time_zone_aware_attributes = false
542
    Time.zone = ActiveSupport::TimeZone[-28800]
543 544 545 546 547 548 549 550 551 552 553 554
    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

555 556 557
  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
558
    Time.zone = ActiveSupport::TimeZone[-28800]
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
    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
574

575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
  # 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
594
  end
595

D
Initial  
David Heinemeier Hansson 已提交
596
  def test_multiparameter_attributes_on_time_with_empty_seconds
J
Jeremy Kemper 已提交
597 598
    attributes = {
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
D
Initial  
David Heinemeier Hansson 已提交
599 600 601 602 603 604 605
      "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

606 607 608 609 610 611 612
  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
613

614
  def test_attributes_on_dummy_time
615 616
    # Oracle, and Sybase do not have a TIME datatype.
    return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
617

618 619 620 621 622 623 624 625
    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 已提交
626
  def test_boolean
627
    b_nil = Boolean.create({ "value" => nil })
628
    nil_id = b_nil.id
629
    b_false = Boolean.create({ "value" => false })
D
Initial  
David Heinemeier Hansson 已提交
630
    false_id = b_false.id
631
    b_true = Boolean.create({ "value" => true })
D
Initial  
David Heinemeier Hansson 已提交
632 633
    true_id = b_true.id

634
    b_nil = Boolean.find(nil_id)
635
    assert_nil b_nil.value
636
    b_false = Boolean.find(false_id)
D
Initial  
David Heinemeier Hansson 已提交
637
    assert !b_false.value?
638
    b_true = Boolean.find(true_id)
D
Initial  
David Heinemeier Hansson 已提交
639 640
    assert b_true.value?
  end
641 642

  def test_boolean_cast_from_string
643
    b_blank = Boolean.create({ "value" => "" })
644
    blank_id = b_blank.id
645
    b_false = Boolean.create({ "value" => "0" })
646
    false_id = b_false.id
647
    b_true = Boolean.create({ "value" => "1" })
648 649
    true_id = b_true.id

650
    b_blank = Boolean.find(blank_id)
651
    assert_nil b_blank.value
652
    b_false = Boolean.find(false_id)
653
    assert !b_false.value?
654
    b_true = Boolean.find(true_id)
J
Jeremy Kemper 已提交
655
    assert b_true.value?
656
  end
J
Jeremy Kemper 已提交
657

658
  def test_new_record_returns_boolean
659 660
    assert_equal true, Topic.new.new_record?
    assert_equal false, Topic.find(1).new_record?
661 662
  end

D
Initial  
David Heinemeier Hansson 已提交
663 664
  def test_clone
    topic = Topic.find(1)
665 666
    cloned_topic = nil
    assert_nothing_raised { cloned_topic = topic.clone }
D
Initial  
David Heinemeier Hansson 已提交
667
    assert_equal topic.title, cloned_topic.title
668
    assert cloned_topic.new_record?
D
Initial  
David Heinemeier Hansson 已提交
669 670

    # test if the attributes have been cloned
J
Jeremy Kemper 已提交
671 672
    topic.title = "a"
    cloned_topic.title = "b"
D
Initial  
David Heinemeier Hansson 已提交
673 674 675 676 677 678
    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 已提交
679
    cloned_topic.title["a"] = "c"
D
Initial  
David Heinemeier Hansson 已提交
680
    assert_equal "b", topic.title["a"]
681

682
    # test if attributes set as part of after_initialize are cloned correctly
683 684 685
    assert_equal topic.author_email_address, cloned_topic.author_email_address

    # test if saved clone object differs from original
686
    cloned_topic.save
687
    assert !cloned_topic.new_record?
688
    assert_not_equal cloned_topic.id, topic.id
689 690 691

    cloned_topic.reload
    # FIXME: I think this is poor behavior, and will fix it with #5686
692
    assert_equal({'a' => 'c'}.to_s, cloned_topic.title)
D
Initial  
David Heinemeier Hansson 已提交
693
  end
694 695 696 697 698 699 700 701 702

  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
703
    assert clone.new_record?
704 705 706 707 708 709 710

    # 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
711
    assert !clone.new_record?
712
    assert_not_equal clone.id, dev.id
713 714
  end

715 716 717 718 719 720 721 722
  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

723 724 725 726 727 728
  def test_clone_preserves_subtype
    clone = nil
    assert_nothing_raised { clone = Company.find(3).clone }
    assert_kind_of Client, clone
  end

729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
  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 已提交
779 780 781 782 783 784 785 786
  def test_bignum
    company = Company.find(1)
    company.rating = 2147483647
    company.save
    company = Company.find(1)
    assert_equal 2147483647, company.rating
  end

787
  # TODO: extend defaults tests to other databases!
788
  if current_adapter?(:PostgreSQLAdapter)
789
    def test_default
D
Initial  
David Heinemeier Hansson 已提交
790
      default = Default.new
J
Jeremy Kemper 已提交
791

D
Initial  
David Heinemeier Hansson 已提交
792 793 794
      # 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 已提交
795

D
Initial  
David Heinemeier Hansson 已提交
796 797 798 799 800
      # char types
      assert_equal 'Y', default.char1
      assert_equal 'a varchar field', default.char2
      assert_equal 'a text field', default.char3
    end
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817

    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 已提交
818

819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
      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 已提交
836

837 838 839 840 841 842 843 844 845 846 847 848 849 850
      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 已提交
851

852 853 854 855 856 857 858 859 860 861 862
      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 已提交
863 864
  end

865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896
  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 已提交
897 898 899
  def test_auto_id
    auto = AutoId.new
    auto.save
900
    assert(auto.id > 0)
D
Initial  
David Heinemeier Hansson 已提交
901
  end
902

D
Initial  
David Heinemeier Hansson 已提交
903 904 905 906 907 908 909 910 911 912 913 914 915
  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

916
  def test_sql_injection_via_find
917
    assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
918 919 920 921
      Topic.find("123456 OR id > 0")
    end
  end

D
Initial  
David Heinemeier Hansson 已提交
922 923 924
  def test_column_name_properly_quoted
    col_record = ColumnName.new
    col_record.references = 40
925
    assert col_record.save
D
Initial  
David Heinemeier Hansson 已提交
926
    col_record.references = 41
927 928
    assert col_record.save
    assert_not_nil c2 = ColumnName.find(col_record.id)
D
Initial  
David Heinemeier Hansson 已提交
929 930 931
    assert_equal(41, c2.references)
  end

932
  def test_quoting_arrays
933
    replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
934 935
    assert_equal topics(:first).replies.size, replies.size

936
    replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
937 938 939
    assert_equal 0, replies.size
  end

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

D
Initial  
David Heinemeier Hansson 已提交
942
  def test_serialized_attribute
943 944
    Topic.serialize("content", MyObject)

D
Initial  
David Heinemeier Hansson 已提交
945
    myobj = MyObject.new('value1', 'value2')
J
Jeremy Kemper 已提交
946
    topic = Topic.create("content" => myobj)
947 948 949
    assert_equal(myobj, topic.content)

    topic.reload
D
Initial  
David Heinemeier Hansson 已提交
950 951 952
    assert_equal(myobj, topic.content)
  end

953 954 955 956 957
  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
958

959 960 961 962 963
  def test_serialized_string_attribute
    myobj = "Yes"
    topic = Topic.create("content" => myobj).reload
    assert_equal(myobj, topic.content)
  end
964

965
  def test_nil_serialized_attribute_with_class_constraint
D
Initial  
David Heinemeier Hansson 已提交
966
    myobj = MyObject.new('value1', 'value2')
967 968 969
    topic = Topic.new
    assert_nil topic.content
  end
D
Initial  
David Heinemeier Hansson 已提交
970

971 972 973 974 975
  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)
976
    assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
977 978 979
  ensure
    Topic.serialize(:content)
  end
D
Initial  
David Heinemeier Hansson 已提交
980

981
  def test_serialized_attribute_with_class_constraint
D
Initial  
David Heinemeier Hansson 已提交
982
    settings = { "color" => "blue" }
983 984 985
    Topic.serialize(:content, Hash)
    topic = Topic.new(:content => settings)
    assert topic.save
D
Initial  
David Heinemeier Hansson 已提交
986
    assert_equal(settings, Topic.find(topic.id).content)
987
  ensure
D
Initial  
David Heinemeier Hansson 已提交
988 989 990 991
    Topic.serialize(:content)
  end

  def test_quote
992 993 994
    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 已提交
995
  end
996 997 998

  if RUBY_VERSION < '1.9'
    def test_quote_chars
999 1000 1001 1002
      with_kcode('UTF8') do
        str = 'The Narrator'
        topic = Topic.create(:author_name => str)
        assert_equal str, topic.author_name
1003

1004 1005
        assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars
        topic = Topic.find_by_author_name(str.mb_chars)
1006

1007 1008 1009
        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
1010
    end
1011
  end
1012

1013
  def test_toggle_attribute
1014 1015 1016
    assert !topics(:first).approved?
    topics(:first).toggle!(:approved)
    assert topics(:first).approved?
1017 1018 1019 1020 1021
    topic = topics(:first)
    topic.toggle(:approved)
    assert !topic.approved?
    topic.reload
    assert topic.approved?
1022
  end
1023 1024 1025 1026 1027 1028 1029 1030 1031

  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
1032

1033 1034 1035 1036 1037 1038
  def test_reload_with_exclusive_scope
    dev = DeveloperCalledDavid.first
    dev.update_attributes!( :name => "NotDavid" )
    assert_equal dev, dev.reload
  end

1039 1040
  def test_define_attr_method_with_value
    k = Class.new( ActiveRecord::Base )
1041
    k.send(:define_attr_method, :table_name, "foo")
1042 1043 1044 1045 1046
    assert_equal "foo", k.table_name
  end

  def test_define_attr_method_with_block
    k = Class.new( ActiveRecord::Base )
1047
    k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058
    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

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
  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

1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
  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
1104 1105

  def test_count_with_join
1106
    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 已提交
1107

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

1111
    res3 = nil
1112 1113 1114 1115 1116
    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 已提交
1117

1118
    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"
1119 1120
    res5 = nil
    assert_nothing_raised do
1121 1122
      res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
                        :joins => "p, comments co",
1123 1124 1125
                        :select => "p.id")
    end

J
Jeremy Kemper 已提交
1126
    assert_equal res4, res5
1127

P
Pratik Naik 已提交
1128 1129 1130 1131 1132 1133 1134
    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)
1135
    end
P
Pratik Naik 已提交
1136
    assert_equal res6, res7
1137
  end
J
Jeremy Kemper 已提交
1138

1139 1140 1141 1142 1143 1144
  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

1145
  def test_scoped_find_conditions
1146
    scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do
1147 1148
      Developer.find(:all, :conditions => 'id < 5')
    end
M
Marcel Molina 已提交
1149 1150
    assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
    assert_equal 3, scoped_developers.size
1151
  end
J
Jeremy Kemper 已提交
1152

1153
  def test_scoped_find_limit_offset
1154
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do
1155
      Developer.find(:all, :order => 'id')
J
Jeremy Kemper 已提交
1156
    end
M
Marcel Molina 已提交
1157 1158 1159
    assert !scoped_developers.include?(developers(:david))
    assert !scoped_developers.include?(developers(:jamis))
    assert_equal 3, scoped_developers.size
J
Jeremy Kemper 已提交
1160

1161
    # Test without scoped find conditions to ensure we get the whole thing
1162
    developers = Developer.find(:all, :order => 'id')
M
Marcel Molina 已提交
1163
    assert_equal Developer.count, developers.size
1164
  end
1165

J
Jeremy Kemper 已提交
1166 1167
  def test_scoped_find_order
    # Test order in scope
1168
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do
1169
      Developer.find(:all)
J
Jeremy Kemper 已提交
1170
    end
1171 1172 1173
    assert_equal 'Jamis', scoped_developers.first.name
    assert scoped_developers.include?(developers(:jamis))
    # Test scope without order and order in find
1174
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do
1175
      Developer.find(:all, :order => 'salary DESC')
J
Jeremy Kemper 已提交
1176
    end
1177
    # Test scope order + find order, order has priority
1178
    scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do
1179 1180 1181
      Developer.find(:all, :order => 'salary ASC')
    end
    assert scoped_developers.include?(developers(:poor_jamis))
1182
    assert ! scoped_developers.include?(developers(:david))
1183 1184 1185
    assert ! scoped_developers.include?(developers(:jamis))
    assert_equal 3, scoped_developers.size

1186
    # Test without scoped find conditions to ensure we get the right thing
1187 1188
    assert ! scoped_developers.include?(Developer.find(1))
    assert scoped_developers.include?(Developer.find(11))
1189 1190
  end

1191
  def test_scoped_find_limit_offset_including_has_many_association
1192
    topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do
1193 1194 1195 1196 1197 1198
      Topic.find(:all, :order => "topics.id")
    end
    assert_equal 1, topics.size
    assert_equal 2, topics.first.id
  end

1199
  def test_scoped_find_order_including_has_many_association
1200
    developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do
1201 1202 1203 1204 1205 1206 1207 1208
      Developer.find(:all)
    end
    assert developers.size >= 2
    for i in 1...developers.size
      assert developers[i-1].salary >= developers[i].salary
    end
  end

1209
  def test_scoped_find_with_group_and_having
1210
    developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
1211 1212 1213 1214 1215
      Developer.find(:all)
    end
    assert_equal 3, developers.size
  end

1216 1217 1218 1219
  def test_find_last
    last  = Developer.find :last
    assert_equal last, Developer.find(:first, :order => 'id desc')
  end
1220

1221 1222 1223
  def test_last
    assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
  end
1224

1225 1226 1227 1228 1229 1230
  def test_all
    developers = Developer.all
    assert_kind_of Array, developers
    assert_equal Developer.find(:all), developers
  end

1231
  def test_all_with_conditions
1232
    assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all
1233
  end
1234

1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
  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
1249

1250 1251 1252 1253 1254 1255 1256 1257 1258 1259
  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

1260 1261 1262 1263 1264
  def test_find_symbol_ordered_last
    last  = Developer.find :last, :order => :salary
    assert_equal last, Developer.find(:all, :order => :salary).last
  end

1265
  def test_find_scoped_ordered_last
1266
    last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do
1267 1268 1269 1270
      Developer.find(:last)
    end
    assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
  end
1271

1272
  def test_abstract_class
1273
    assert !ActiveRecord::Base.abstract_class?
1274 1275
    assert LoosePerson.abstract_class?
    assert !LooseDescendant.abstract_class?
1276 1277 1278
  end

  def test_base_class
1279 1280 1281 1282
    assert_equal LoosePerson,     LoosePerson.base_class
    assert_equal LooseDescendant, LooseDescendant.base_class
    assert_equal TightPerson,     TightPerson.base_class
    assert_equal TightPerson,     TightDescendant.base_class
1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315

    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.
1316
    assert !SubStiPost.descends_from_active_record?
1317 1318 1319 1320 1321 1322
  end

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

1323
    descendant = old_class.create! :first_name => 'bob'
1324 1325 1326 1327 1328
    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
1329 1330
  end

1331 1332 1333 1334 1335 1336 1337
  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

1338 1339 1340
  def test_to_param_should_return_string
    assert_kind_of String, Client.find(:first).to_param
  end
J
Jeremy Kemper 已提交
1341

1342 1343 1344 1345 1346 1347 1348
  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
1349
    topic = topics(:first)
1350
    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
1351
  end
1352

1353
  def test_inspect_new_instance
1354
    assert_match(/Topic id: nil/, Topic.new.inspect)
1355 1356
  end

1357 1358 1359 1360
  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 已提交
1361

1362 1363 1364
  def test_inspect_class_without_table
    assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
  end
1365

1366 1367
  def test_attribute_for_inspect
    t = topics(:first)
1368
    t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
1369 1370

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

1374 1375 1376 1377
  def test_becomes
    assert_kind_of Reply, topics(:first).becomes(Reply)
    assert_equal "The First Topic", topics(:first).becomes(Reply).title
  end
1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409

  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 已提交
1410 1411 1412
    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 }
1413 1414 1415
    assert_no_match(/Debug Topic Count/, log.string)
    assert_match(/Warn Topic Count/, log.string)
    assert_match(/Error Topic Count/, log.string)
1416 1417 1418 1419 1420 1421 1422 1423
  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 已提交
1424 1425
    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" }
1426 1427
    assert_no_match(/Loud/, log.string)
    assert_match(/Quiet/, log.string)
1428 1429 1430
  ensure
    ActiveRecord::Base.logger = original_logger
  end
1431

1432 1433 1434
  def test_dup
    assert !Minimalistic.new.freeze.dup.frozen?
  end
1435

1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
  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
1447
    ActiveSupport::Dependencies.stubs(:constantize).raises(NoMethodError)
1448 1449 1450 1451 1452
    assert_raises NoMethodError do
      ActiveRecord::Base.send :compute_type, 'InvalidModel'
    end
  end

1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466
  def test_default_scope_is_reset
    Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base)
    UnloadablePost.table_name = 'posts'
    UnloadablePost.class_eval do
      default_scope order('posts.comments_count ASC')
    end

    UnloadablePost.unloadable
    assert_not_nil Thread.current[:UnloadablePost_scoped_methods]
    ActiveSupport::Dependencies.remove_unloadable_constants!
    assert_nil Thread.current[:UnloadablePost_scoped_methods]
  ensure
    Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
  end
1467
end