finder_test.rb 34.7 KB
Newer Older
1
require "cases/helper"
2
require 'models/post'
J
Jeremy Kemper 已提交
3
require 'models/author'
4
require 'models/categorization'
J
Jeremy Kemper 已提交
5 6 7 8 9
require 'models/comment'
require 'models/company'
require 'models/topic'
require 'models/reply'
require 'models/entrant'
10
require 'models/project'
J
Jeremy Kemper 已提交
11
require 'models/developer'
12
require 'models/customer'
13
require 'models/toy'
14
require 'models/matey'
15
require 'models/dog'
D
David Heinemeier Hansson 已提交
16

17
class FinderTest < ActiveRecord::TestCase
18
  fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
D
David Heinemeier Hansson 已提交
19

20 21 22 23 24 25 26 27 28 29 30 31
  def test_find_by_id_with_hash
    assert_raises(ActiveRecord::StatementInvalid) do
      Post.find_by_id(:limit => 1)
    end
  end

  def test_find_by_title_and_id_with_hash
    assert_raises(ActiveRecord::StatementInvalid) do
      Post.find_by_title_and_id('foo', :limit => 1)
    end
  end

D
David Heinemeier Hansson 已提交
32
  def test_find
33
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
34
  end
35

36 37 38 39 40 41 42
  def test_symbols_table_ref
    Post.first # warm up
    x = Symbol.all_symbols.count
    Post.where("title" => {"xxxqqqq" => "bar"})
    assert_equal x, Symbol.all_symbols.count
  end

43 44 45 46 47
  # find should handle strings that come from URLs
  # (example: Category.find(params[:id]))
  def test_find_with_string
    assert_equal(Topic.find(1).title,Topic.find("1").title)
  end
48

D
David Heinemeier Hansson 已提交
49
  def test_exists
50 51 52 53 54 55
    assert_equal true, Topic.exists?(1)
    assert_equal true, Topic.exists?("1")
    assert_equal true, Topic.exists?(title: "The First Topic")
    assert_equal true, Topic.exists?(heading: "The First Topic")
    assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true)
    assert_equal true, Topic.exists?(["parent_id = ?", 1])
56
    assert_equal true, Topic.exists?(id: [1, 9999])
57 58 59

    assert_equal false, Topic.exists?(45)
    assert_equal false, Topic.exists?(Topic.new)
60 61

    begin
62
      assert_equal false, Topic.exists?("foo")
63 64 65 66 67 68
    rescue ActiveRecord::StatementInvalid
      # PostgreSQL complains about string comparison with integer field
    rescue Exception
      flunk
    end

69
    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
D
David Heinemeier Hansson 已提交
70
  end
71

72
  def test_exists_does_not_select_columns_without_alias
73
    assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
74 75 76 77
      Topic.exists?
    end
  end

78
  def test_exists_returns_true_with_one_record_and_no_args
79
    assert_equal true, Topic.exists?
80
  end
81

E
Egor Lynko 已提交
82
  def test_exists_returns_false_with_false_arg
83
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
84 85
  end

86 87 88
  # exists? should handle nil for id's that come from URLs and always return false
  # (example: Topic.exists?(params[:id])) where params[:id] is nil
  def test_exists_with_nil_arg
89 90 91 92 93
    assert_equal false, Topic.exists?(nil)
    assert_equal true, Topic.exists?

    assert_equal false, Topic.first.replies.exists?(nil)
    assert_equal true, Topic.first.replies.exists?
94 95
  end

96 97
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
98
    assert_equal true, Topic.order(:id).distinct.exists?
99 100
  end

101
  def test_exists_with_includes_limit_and_empty_result
102 103
    assert_equal false, Topic.includes(:replies).limit(0).exists?
    assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists?
104 105
  end

106 107
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
108 109
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
110 111 112 113
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
114 115
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
116 117
  end

118
  def test_exists_with_empty_table_and_no_args_given
119
    Topic.delete_all
120
    assert_equal false, Topic.exists?
121
  end
122

123 124
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
125
    assert_equal true, Customer.exists?(:address => existing_address)
126 127 128 129
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
130
    assert_equal false, Customer.exists?(:address =>
131
      Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
132
    assert_equal false, Customer.exists?(:address =>
133
      Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
134
    assert_equal false, Customer.exists?(:address =>
135 136 137
      Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
  end

138 139 140 141 142
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
143 144 145 146
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
147

D
David Heinemeier Hansson 已提交
148
  def test_find_by_ids
149 150 151 152 153
    assert_equal 2, Topic.find(1, 2).size
    assert_equal topics(:second).title, Topic.find([2]).first.title
  end

  def test_find_by_ids_with_limit_and_offset
154 155
    assert_equal 2, Entrant.limit(2).find([1,3,2]).size
    assert_equal 1, Entrant.limit(3).offset(2).find([1,3,2]).size
156 157 158 159

    # Also test an edge case: If you have 11 results, and you set a
    #   limit of 3 and offset of 9, then you should find that there
    #   will be only 2 results, regardless of the limit.
J
Jon Leighton 已提交
160
    devs = Developer.all
161
    last_devs = Developer.limit(3).offset(9).find devs.map(&:id)
162
    assert_equal 2, last_devs.size
D
David Heinemeier Hansson 已提交
163 164
  end

165 166 167 168
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

169 170 171 172
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
173
  def test_find_by_ids_missing_one
174
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
175
  end
176

177
  def test_find_with_group_and_sanitized_having_method
178
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
179 180 181 182 183
    assert_equal 3, developers.size
    assert_equal 3, developers.map(&:salary).uniq.size
    assert developers.all? { |developer| developer.salary > 10000 }
  end

D
David Heinemeier Hansson 已提交
184 185
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
186

D
David Heinemeier Hansson 已提交
187
    assert_equal(1, topics.size)
188
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
189
  end
190

D
David Heinemeier Hansson 已提交
191 192
  def test_find_with_prepared_select_statement
    topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
193

D
David Heinemeier Hansson 已提交
194
    assert_equal(1, topics.size)
195
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
196
  end
197

198 199 200 201
  def test_find_by_sql_with_sti_on_joined_table
    accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id")
    assert_equal [Account], accounts.collect(&:class).uniq
  end
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
  def test_take
    assert_equal topics(:first), Topic.take
  end

  def test_take_failing
    assert_nil Topic.where("title = 'This title does not exist'").take
  end

  def test_take_bang_present
    assert_nothing_raised do
      assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take!
    end
  end

  def test_take_bang_missing
    assert_raises ActiveRecord::RecordNotFound do
      Topic.where("title = 'This title does not exist'").take!
    end
  end

223
  def test_first
224
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
225
  end
226

227
  def test_first_failing
228
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
229
  end
D
David Heinemeier Hansson 已提交
230

231 232 233 234 235 236 237 238 239 240 241 242
  def test_first_bang_present
    assert_nothing_raised do
      assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").first!
    end
  end

  def test_first_bang_missing
    assert_raises ActiveRecord::RecordNotFound do
      Topic.where("title = 'This title does not exist'").first!
    end
  end

243 244 245 246 247 248
  def test_first_have_primary_key_order_by_default
    expected = topics(:first)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.first
  end

249
  def test_model_class_responds_to_first_bang
250 251
    assert Topic.first!
    Topic.delete_all
252 253 254 255 256
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

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 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
  def test_second
    assert_equal topics(:second).title, Topic.second.title
  end

  def test_second_with_offset
    assert_equal topics(:fifth), Topic.offset(3).second
  end

  def test_second_have_primary_key_order_by_default
    expected = topics(:second)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.second
  end

  def test_model_class_responds_to_second_bang
    assert Topic.second!
    Topic.delete_all
    assert_raises ActiveRecord::RecordNotFound do
      Topic.second!
    end
  end

  def test_third
    assert_equal topics(:third).title, Topic.third.title
  end

  def test_third_with_offset
    assert_equal topics(:fifth), Topic.offset(2).third
  end

  def test_third_have_primary_key_order_by_default
    expected = topics(:third)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.third
  end

  def test_model_class_responds_to_third_bang
    assert Topic.third!
    Topic.delete_all
    assert_raises ActiveRecord::RecordNotFound do
      Topic.third!
    end
  end

  def test_fourth
    assert_equal topics(:fourth).title, Topic.fourth.title
  end

  def test_fourth_with_offset
    assert_equal topics(:fifth), Topic.offset(1).fourth
  end

  def test_fourth_have_primary_key_order_by_default
    expected = topics(:fourth)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.fourth
  end

  def test_model_class_responds_to_fourth_bang
    assert Topic.fourth!
    Topic.delete_all
    assert_raises ActiveRecord::RecordNotFound do
      Topic.fourth!
    end
  end

  def test_fifth
    assert_equal topics(:fifth).title, Topic.fifth.title
  end

  def test_fifth_with_offset
    assert_equal topics(:fifth), Topic.offset(0).fifth
  end

  def test_fifth_have_primary_key_order_by_default
    expected = topics(:fifth)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.fifth
  end

  def test_model_class_responds_to_fifth_bang
    assert Topic.fifth!
    Topic.delete_all
    assert_raises ActiveRecord::RecordNotFound do
      Topic.fifth!
    end
  end

345 346 347 348 349 350 351 352 353 354 355 356
  def test_last_bang_present
    assert_nothing_raised do
      assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
    end
  end

  def test_last_bang_missing
    assert_raises ActiveRecord::RecordNotFound do
      Topic.where("title = 'This title does not exist'").last!
    end
  end

357
  def test_model_class_responds_to_last_bang
358
    assert_equal topics(:fifth), Topic.last!
359 360 361 362 363 364
    assert_raises ActiveRecord::RecordNotFound do
      Topic.delete_all
      Topic.last!
    end
  end

365 366
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
367 368
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  end

  def test_last_with_integer_and_order_should_keep_the_order
    assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2)
  end

  def test_last_with_integer_and_order_should_not_use_sql_limit
    query = assert_sql { Topic.order("title").last(5).entries }
    assert_equal 1, query.length
    assert_no_match(/LIMIT/, query.first)
  end

  def test_last_with_integer_and_reorder_should_not_use_sql_limit
    query = assert_sql { Topic.reorder("title").last(5).entries }
    assert_equal 1, query.length
    assert_no_match(/LIMIT/, query.first)
385 386
  end

387 388
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
389 390 391 392
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
393
  def test_unexisting_record_exception_handling
394
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
395 396
      Topic.find(1).parent
    }
397

398
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
399
  end
400

401
  def test_find_only_some_columns
402
    topic = Topic.select("author_name").find(1)
403
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
404
    assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
405
    assert_nil topic.read_attribute("title")
406 407
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
408
    assert !topic.attribute_present?(:title)
409
    assert topic.attribute_present?("author_name")
410
    assert_respond_to topic, "author_name"
411
  end
J
Jeremy Kemper 已提交
412

413
  def test_find_on_array_conditions
414 415
    assert Topic.where(["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
416
  end
417

418
  def test_find_on_hash_conditions
419 420
    assert Topic.where(approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
421
  end
422

423
  def test_find_on_hash_conditions_with_explicit_table_name
424 425
    assert Topic.where('topics.approved' => false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
426 427
  end

428
  def test_find_on_hash_conditions_with_hashed_table_name
429 430
    assert Topic.where(topics: { approved: false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
431 432 433
  end

  def test_find_with_hash_conditions_on_joined_table
434
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
435 436 437 438 439
    assert_equal 1, firms.size
    assert_equal companies(:first_firm), firms.first
  end

  def test_find_with_hash_conditions_on_joined_table_and_with_range
440
    firms = DependentFirm.joins(:account).where(name: 'RailsCore', accounts: { credit_limit: 55..60 })
441 442 443 444
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

445 446 447 448 449 450 451 452
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
    assert Customer.where('customers.name' => david.name, :address => david.address).find(david.id)
    assert_raise(ActiveRecord::RecordNotFound) {
      Customer.where('customers.name' => david.name + "1", :address => david.address).find(david.id)
    }
  end

453
  def test_find_on_association_proxy_conditions
J
Jon Leighton 已提交
454
    assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
455 456
  end

457
  def test_find_on_hash_conditions_with_range
458 459
    assert_equal [1,2], Topic.where(id: 1..2).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
460
  end
461

462
  def test_find_on_hash_conditions_with_end_exclusive_range
463 464 465
    assert_equal [1,2,3], Topic.where(id: 1..3).to_a.map(&:id).sort
    assert_equal [1,2], Topic.where(id: 1...3).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
466 467
  end

468
  def test_find_on_hash_conditions_with_multiple_ranges
469 470
    assert_equal [1,2,3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
    assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
471
  end
472

473
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
474
    assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort
475 476
  end

477
  def test_find_on_multiple_hash_conditions
478 479 480 481
    assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
482
  end
483

484
  def test_condition_interpolation
485
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
486 487 488
    assert_nil Company.where(["name = '%s'", "37signals!"]).first
    assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on
489 490
  end

491
  def test_condition_array_interpolation
492 493 494 495
    assert_kind_of Firm, Company.where(["name = '%s'", "37signals"]).first
    assert_nil Company.where(["name = '%s'", "37signals!"]).first
    assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on
D
David Heinemeier Hansson 已提交
496
  end
497

498
  def test_condition_hash_interpolation
499 500 501
    assert_kind_of Firm, Company.where(name: "37signals").first
    assert_nil Company.where(name: "37signals!").first
    assert_kind_of Time, Topic.where(id: 1).first.written_on
502
  end
503

504
  def test_hash_condition_find_malformed
505
    assert_raise(ActiveRecord::StatementInvalid) {
506
      Company.where(id: 2, dhh: true).first
507 508
    }
  end
509

510 511
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
512
    assert Company.where(name: "Ain't noth'n like' \#stuff").first
513 514 515
  end

  def test_hash_condition_find_with_array
516 517 518
    p1, p2 = Post.limit(2).order('id asc').to_a
    assert_equal [p1, p2], Post.where(id: [p1, p2]).order('id asc').to_a
    assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order('id asc').to_a
519 520 521
  end

  def test_hash_condition_find_with_nil
522
    topic = Topic.where(last_read: nil).first
523 524
    assert_not_nil topic
    assert_nil topic.last_read
525
  end
D
David Heinemeier Hansson 已提交
526

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customer = Customer.where(:balance => balance).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
    gps_location = customers(:david).gps_location
    assert_kind_of GpsLocation, gps_location
    found_customer = Customer.where(:gps_location => gps_location).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customer = Customer.where(:balance => balance.amount).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
    gps_location = customers(:david).gps_location
    assert_kind_of GpsLocation, gps_location
    found_customer = Customer.where(:gps_location => gps_location.gps_location).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_with_aggregate_having_three_mappings
    address = customers(:david).address
    assert_kind_of Address, address
    found_customer = Customer.where(:address => address).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
    address = customers(:david).address
    assert_kind_of Address, address
    found_customer = Customer.where(:address => address, :name => customers(:david).name).first
    assert_equal customers(:david), found_customer
  end

569 570
  def test_condition_utc_time_interpolation_with_default_timezone_local
    with_env_tz 'America/New_York' do
571
      with_timezone_config default: :local do
572
        topic = Topic.first
573
        assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getutc]).first
574 575 576 577 578 579
      end
    end
  end

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
    with_env_tz 'America/New_York' do
580
      with_timezone_config default: :local do
581
        topic = Topic.first
582
        assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
583 584 585 586 587 588
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
589
      with_timezone_config default: :utc do
590
        topic = Topic.first
591
        assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getlocal]).first
592 593 594 595 596 597
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
598
      with_timezone_config default: :utc do
599
        topic = Topic.first
600
        assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
601 602 603 604
      end
    end
  end

D
David Heinemeier Hansson 已提交
605
  def test_bind_variables
606 607 608 609
    assert_kind_of Firm, Company.where(["name = ?", "37signals"]).first
    assert_nil Company.where(["name = ?", "37signals!"]).first
    assert_nil Company.where(["name = ?", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.where(["id = ?", 1]).first.written_on
610
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
611
      Company.where(["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
612
    }
613
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
614
     Company.where(["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
615 616
    }
  end
617

D
David Heinemeier Hansson 已提交
618 619
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
620
    assert Company.where(["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
621 622 623 624
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
625
    assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
626 627 628 629
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
630
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
631

632
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
633
    assert_nothing_raised                                 { bind '?', 1 }
634
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
635
  end
636

D
David Heinemeier Hansson 已提交
637 638 639
  def test_named_bind_variables
    assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
    assert_equal '1 1', bind(':a :a', :a => 1)  # ' ruby-mode
640

641
    assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
642

643 644 645 646
    assert_kind_of Firm, Company.where(["name = :name", { name: "37signals" }]).first
    assert_nil Company.where(["name = :name", { name: "37signals!" }]).first
    assert_nil Company.where(["name = :name", { name: "37signals!' OR 1=1" }]).first
    assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
D
David Heinemeier Hansson 已提交
647 648
  end

649 650 651 652 653 654 655 656 657 658 659 660
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

    def each(&b)
      @ary.each(&b)
    end
  end

661
  def test_bind_enumerable
662 663
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
664
    assert_equal '1,2,3', bind('?', [1, 2, 3])
665
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
666 667

    assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
668
    assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
669

670 671
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
672

673 674
    assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # '
D
David Heinemeier Hansson 已提交
675 676
  end

677 678 679 680 681 682 683
  def test_bind_empty_enumerable
    quoted_nil = ActiveRecord::Base.connection.quote(nil)
    assert_equal quoted_nil, bind('?', [])
    assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
    assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
  end

J
Jeremy Kemper 已提交
684 685 686
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
687 688
  end

689 690 691 692 693
  def test_bind_chars
    quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
    quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi")
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper")
694 695
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
696 697
  end

698 699 700 701 702 703 704 705
  def test_bind_record
    o = Struct.new(:quoted_id).new(1)
    assert_equal '1', bind('?', o)

    os = [o] * 3
    assert_equal '1,1,1', bind('?', os)
  end

706 707 708
  def test_named_bind_with_postgresql_type_casts
    l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
    assert_nothing_raised(&l)
709
    assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
710 711
  end

D
David Heinemeier Hansson 已提交
712
  def test_string_sanitation
713 714
    assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
    assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
D
David Heinemeier Hansson 已提交
715 716 717 718 719 720 721 722 723
  end

  def test_count_by_sql
    assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
    assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
    assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
  end

  def test_find_by_one_attribute
724
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
725 726
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
727

728 729
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
730
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
731 732
  end

733 734 735 736 737 738 739
  def test_find_by_on_attribute_that_is_a_reserved_word
    dog_alias = 'Dog'
    dog = Dog.create(alias: dog_alias)

    assert_equal dog, Dog.find_by_alias(dog_alias)
  end

740 741 742 743 744
  def test_find_by_one_attribute_that_is_an_alias
    assert_equal topics(:first), Topic.find_by_heading("The First Topic")
    assert_nil Topic.find_by_heading("The First Topic!")
  end

N
Nikita Afanasenko 已提交
745
  def test_find_by_one_attribute_bang_with_blank_defined
746 747
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
748 749
  end

750
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
751
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
752 753
  end

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 779 780 781 782 783 784 785 786 787
  def test_find_by_one_attribute_that_is_an_aggregate
    address = customers(:david).address
    assert_kind_of Address, address
    found_customer = Customer.find_by_address(address)
    assert_equal customers(:david), found_customer
  end

  def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference
    address = customers(:david).address
    assert_kind_of Address, address
    missing_address = Address.new(address.street, address.city, address.country + "1")
    assert_nil Customer.find_by_address(missing_address)
    missing_address = Address.new(address.street, address.city + "1", address.country)
    assert_nil Customer.find_by_address(missing_address)
    missing_address = Address.new(address.street + "1", address.city, address.country)
    assert_nil Customer.find_by_address(missing_address)
  end

  def test_find_by_two_attributes_that_are_both_aggregates
    balance = customers(:david).balance
    address = customers(:david).address
    assert_kind_of Money, balance
    assert_kind_of Address, address
    found_customer = Customer.find_by_balance_and_address(balance, address)
    assert_equal customers(:david), found_customer
  end

  def test_find_by_two_attributes_with_one_being_an_aggregate
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name)
    assert_equal customers(:david), found_customer
  end

788 789
  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
A
Akira Matsuda 已提交
790
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
791 792
    a = Account.where('firm_id = ?', 6).find_by_credit_limit(50)
    assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached
793 794
  end

795
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
796
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
797
  end
798

D
David Heinemeier Hansson 已提交
799
  def test_find_by_one_missing_attribute
800
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
801
  end
802

803
  def test_find_by_invalid_method_syntax
804 805 806 807
    assert_raise(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
    assert_raise(NoMethodError) { Topic.find_by_title?("The First Topic") }
    assert_raise(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") }
    assert_raise(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") }
808
  end
D
David Heinemeier Hansson 已提交
809 810

  def test_find_by_two_attributes
811
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
812 813 814
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

815 816 817 818
  def test_find_by_two_attributes_but_passing_only_one
    assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
  end

L
Lauro Caetano 已提交
819
  def test_find_last_with_offset
820
    devs = Developer.order('id')
L
Lauro Caetano 已提交
821 822 823 824 825 826 827

    assert_equal devs[2], Developer.offset(2).first
    assert_equal devs[-3], Developer.offset(2).last
    assert_equal devs[-3], Developer.offset(2).last
    assert_equal devs[-3], Developer.offset(2).order('id DESC').first
  end

D
David Heinemeier Hansson 已提交
828 829 830 831 832
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
833

D
David Heinemeier Hansson 已提交
834 835 836 837 838 839
  def test_find_by_nil_and_not_nil_attributes
    topic = Topic.find_by_last_read_and_author_name nil, "Mary"
    assert_equal "Mary", topic.author_name
  end

  def test_find_with_bad_sql
840
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
841 842
  end

843
  def test_find_all_with_join
844 845 846
    developers_on_project_one = Developer.
      joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
      where('project_id=1').to_a
847
    assert_equal 3, developers_on_project_one.length
848 849 850
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
851
  end
852

853
  def test_joins_dont_clobber_id
854 855 856
    first = Firm.
      joins('INNER JOIN companies clients ON clients.firm_id = companies.id').
      where('companies.id = 1').first
857 858 859
    assert_equal 1, first.id
  end

860
  def test_joins_with_string_array
861 862 863 864
    person_with_reader_and_post = Post.
      joins(["INNER JOIN categorizations ON categorizations.post_id = posts.id",
             "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'"
            ])
865 866 867
    assert_equal 1, person_with_reader_and_post.size
  end

868 869
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
870
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
871 872
    end
  end
873

874 875
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
876
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
877
    assert_equal [], Post.where(id: nil)
878 879
  end

880 881 882 883 884
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
885
    assert_equal [], Post.where('id in (?)', [])
886 887 888
  end

  def test_find_by_records
889 890 891
    p1, p2 = Post.limit(2).order('id asc').to_a
    assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2]]).order('id asc')
    assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2.id]]).order('id asc')
892 893
  end

894 895 896 897 898 899 900 901 902
  def test_select_value
    assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1")
    assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1")
    # make sure we didn't break count...
    assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'")
    assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'")
  end

  def test_select_values
903 904
    assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
    assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
905 906
  end

907 908
  def test_select_rows
    assert_equal(
909
      [["1", "1", nil, "37signals"],
910 911
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
912
      Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}})
913
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
914
      Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
915 916
  end

917
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
918
    assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size
919

920 921
    assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
                              order('author_addresses_authors.id DESC ').limit(3).to_a.size
922 923
  end

924
  def test_find_with_nil_inside_set_passed_for_one_attribute
925 926 927 928 929
    client_of = Company.
      where(client_of: [2, 1, nil],
            name: ['37signals', 'Summit', 'Microsoft']).
      order('client_of DESC').
      map { |x| x.client_of }
930

931 932
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
933 934
  end

935
  def test_find_with_nil_inside_set_passed_for_attribute
936 937 938 939
    client_of = Company.
      where(client_of: [nil]).
      order('client_of DESC').
      map { |x| x.client_of }
940 941 942 943

    assert_equal [], client_of.compact
  end

944
  def test_with_limiting_with_custom_select
945
    posts = Post.references(:authors).merge(
946
      :includes => :author, :select => 'posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
947
      :limit => 3, :order => 'posts.id'
948
    ).to_a
949 950
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
951
  end
952

953
  def test_find_one_message_with_custom_primary_key
954
    Toy.primary_key = :name
955 956 957 958 959
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
960 961
  ensure
    Toy.reset_primary_key
962 963
  end

964 965 966 967 968 969
  def test_find_without_primary_key
    assert_raises(ActiveRecord::UnknownPrimaryKey) do
      Matey.find(1)
    end
  end

970
  def test_finder_with_offset_string
971
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a }
972 973
  end

D
David Heinemeier Hansson 已提交
974 975 976 977 978 979 980 981 982
  protected
    def bind(statement, *vars)
      if vars.first.is_a?(Hash)
        ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
      else
        ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
      end
    end
end