finder_test.rb 51.0 KB
Newer Older
1 2
# frozen_string_literal: true

3
require "cases/helper"
4 5 6 7 8 9 10 11
require "models/post"
require "models/author"
require "models/categorization"
require "models/comment"
require "models/company"
require "models/tagging"
require "models/topic"
require "models/reply"
12
require "models/rating"
13 14 15 16 17 18 19 20 21 22
require "models/entrant"
require "models/project"
require "models/developer"
require "models/computer"
require "models/customer"
require "models/toy"
require "models/matey"
require "models/dog"
require "models/car"
require "models/tyre"
D
David Heinemeier Hansson 已提交
23

24
class FinderTest < ActiveRecord::TestCase
R
Fix...  
Ryuta Kamizono 已提交
25
  fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
D
David Heinemeier Hansson 已提交
26

27
  def test_find_by_id_with_hash
28
    assert_nothing_raised do
29
      Post.find_by_id(limit: 1)
30 31 32 33
    end
  end

  def test_find_by_title_and_id_with_hash
34
    assert_nothing_raised do
35
      Post.find_by_title_and_id("foo", limit: 1)
36 37 38
    end
  end

D
David Heinemeier Hansson 已提交
39
  def test_find
40
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
41
  end
42

43
  def test_find_with_proc_parameter_and_block
S
Sean Griffin 已提交
44
    exception = assert_raises(RuntimeError) do
45 46
      Topic.all.find(-> { raise "should happen" }) { |e| e.title == "non-existing-title" }
    end
S
Sean Griffin 已提交
47
    assert_equal "should happen", exception.message
48

49
    assert_nothing_raised do
50 51 52 53
      Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
    end
  end

54
  def test_find_with_ids_returning_ordered
55
    records = Topic.find([4, 2, 5])
56 57 58
    assert_equal "The Fourth Topic of the day", records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
59

60
    records = Topic.find(4, 2, 5)
61 62 63
    assert_equal "The Fourth Topic of the day", records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
64

65
    records = Topic.find(["4", "2", "5"])
66 67 68
    assert_equal "The Fourth Topic of the day", records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
69

70
    records = Topic.find("4", "2", "5")
71 72 73
    assert_equal "The Fourth Topic of the day", records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
74 75 76 77
  end

  def test_find_with_ids_and_order_clause
    # The order clause takes precedence over the informed ids
78
    records = Topic.order(:author_name).find([5, 3, 1])
79 80 81
    assert_equal "The Third Topic of the day", records[0].title
    assert_equal "The First Topic",            records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
82

83
    records = Topic.order(:id).find([5, 3, 1])
84 85 86
    assert_equal "The First Topic",            records[0].title
    assert_equal "The Third Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day", records[2].title
87 88
  end

89 90
  def test_find_with_ids_with_limit_and_order_clause
    # The order clause takes precedence over the informed ids
91
    records = Topic.limit(2).order(:id).find([5, 3, 1])
92
    assert_equal 2, records.size
93 94
    assert_equal "The First Topic",            records[0].title
    assert_equal "The Third Topic of the day", records[1].title
95 96 97
  end

  def test_find_with_ids_and_limit
98
    records = Topic.limit(3).find([3, 2, 5, 1, 4])
99
    assert_equal 3, records.size
100 101 102
    assert_equal "The Third Topic of the day",  records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day",  records[2].title
103 104
  end

105 106
  def test_find_with_ids_where_and_limit
    # Please note that Topic 1 is the only not approved so
107
    # if it were among the first 3 it would raise an ActiveRecord::RecordNotFound
108
    records = Topic.where(approved: true).limit(3).find([3, 2, 5, 1, 4])
109
    assert_equal 3, records.size
110 111 112
    assert_equal "The Third Topic of the day",  records[0].title
    assert_equal "The Second Topic of the day", records[1].title
    assert_equal "The Fifth Topic of the day",  records[2].title
113 114
  end

115
  def test_find_with_ids_and_offset
116
    records = Topic.offset(2).find([3, 2, 5, 1, 4])
117
    assert_equal 3, records.size
118 119 120
    assert_equal "The Fifth Topic of the day",  records[0].title
    assert_equal "The First Topic",             records[1].title
    assert_equal "The Fourth Topic of the day", records[2].title
121 122
  end

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
  def test_find_with_ids_with_no_id_passed
    exception = assert_raises(ActiveRecord::RecordNotFound) { Topic.find }
    assert_equal exception.model, "Topic"
    assert_equal exception.primary_key, "id"
  end

  def test_find_with_ids_with_id_out_of_range
    exception = assert_raises(ActiveRecord::RecordNotFound) do
      Topic.find("9999999999999999999999999999999")
    end

    assert_equal exception.model, "Topic"
    assert_equal exception.primary_key, "id"
  end

138
  def test_find_passing_active_record_object_is_not_permitted
139
    assert_raises(ArgumentError) do
140 141 142 143
      Topic.find(Topic.last)
    end
  end

144
  def test_symbols_table_ref
145
    gc_disabled = GC.disable
146
    Post.where("author_id" => nil)  # warm up
147
    x = Symbol.all_symbols.count
148
    Post.where("title" => { "xxxqqqq" => "bar" })
149
    assert_equal x, Symbol.all_symbols.count
R
Ryuta Kamizono 已提交
150 151
  ensure
    GC.enable if gc_disabled == false
152 153
  end

154 155 156
  # find should handle strings that come from URLs
  # (example: Category.find(params[:id]))
  def test_find_with_string
157
    assert_equal(Topic.find(1).title, Topic.find("1").title)
158
  end
159

D
David Heinemeier Hansson 已提交
160
  def test_exists
161 162 163 164
    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")
165
    assert_equal true, Topic.exists?(author_name: "Mary", approved: true)
166
    assert_equal true, Topic.exists?(["parent_id = ?", 1])
167
    assert_equal true, Topic.exists?(id: [1, 9999])
168 169

    assert_equal false, Topic.exists?(45)
170
    assert_equal false, Topic.exists?(Topic.new.id)
171

172
    assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
173 174
  end

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
  def test_exists_with_scope
    davids = Author.where(name: "David")
    assert_equal true, davids.exists?
    assert_equal true, davids.exists?(authors(:david).id)
    assert_equal false, davids.exists?(authors(:mary).id)
    assert_equal false, davids.exists?("42")
    assert_equal false, davids.exists?(42)
    assert_equal false, davids.exists?(davids.new.id)

    fake = Author.where(name: "fake author")
    assert_equal false, fake.exists?
    assert_equal false, fake.exists?(authors(:david).id)
  end

  def test_exists_uses_existing_scope
    post = authors(:david).posts.first
    authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
    assert_equal true, authors.exists?(authors(:david).id)
  end

  def test_any_with_scope_on_hash_includes
    post = authors(:david).posts.first
    categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
    assert_equal true, categories.exists?
  end

201
  def test_exists_with_polymorphic_relation
202 203
    post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")])
    relation = Post.tagged_with_comment("tagging comment")
204

205 206
    assert_equal true, relation.exists?(title: ["Post"])
    assert_equal true, relation.exists?(["title LIKE ?", "Post%"])
207 208 209 210 211 212 213
    assert_equal true, relation.exists?
    assert_equal true, relation.exists?(post.id)
    assert_equal true, relation.exists?(post.id.to_s)

    assert_equal false, relation.exists?(false)
  end

214
  def test_exists_passing_active_record_object_is_not_permitted
215
    assert_raises(ArgumentError) do
216 217 218 219
      Topic.exists?(Topic.new)
    end
  end

220
  def test_exists_returns_false_when_parameter_has_invalid_type
221
    assert_equal false, Topic.exists?("foo")
222
    assert_equal false, Topic.exists?(("9" * 53).to_i) # number that's bigger than int
D
David Heinemeier Hansson 已提交
223
  end
224

225
  def test_exists_does_not_select_columns_without_alias
226
    assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
227 228 229 230
      Topic.exists?
    end
  end

231
  def test_exists_returns_true_with_one_record_and_no_args
232
    assert_equal true, Topic.exists?
233
  end
234

E
Egor Lynko 已提交
235
  def test_exists_returns_false_with_false_arg
236
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
237 238
  end

239 240 241
  # 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
242 243 244 245 246
    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?
247 248
  end

249 250 251 252
  def test_exists_with_empty_hash_arg
    assert_equal true, Topic.exists?({})
  end

253 254 255 256 257 258 259 260 261 262
  def test_exists_with_distinct_and_offset_and_joins
    assert Post.left_joins(:comments).distinct.offset(10).exists?
    assert_not Post.left_joins(:comments).distinct.offset(11).exists?
  end

  def test_exists_with_distinct_and_offset_and_select
    assert Post.select(:body).distinct.offset(3).exists?
    assert_not Post.select(:body).distinct.offset(4).exists?
  end

263 264 265 266 267
  def test_exists_with_distinct_and_offset_and_eagerload_and_order
    assert Post.eager_load(:comments).distinct.offset(10).merge(Comment.order(post_id: :asc)).exists?
    assert_not Post.eager_load(:comments).distinct.offset(11).merge(Comment.order(post_id: :asc)).exists?
  end

R
Ryuta Kamizono 已提交
268 269 270
  # Ensure +exists?+ runs without an error by excluding distinct value.
  # See https://github.com/rails/rails/pull/26981.
  def test_exists_with_order_and_distinct
271
    assert_equal true, Topic.order(:id).distinct.exists?
272 273
  end

274 275
  # Ensure +exists?+ runs without an error by excluding order value.
  def test_exists_with_order
276
    assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
277 278
  end

R
Ryuta Kamizono 已提交
279
  def test_exists_with_joins
B
Ben Toews 已提交
280
    assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
R
Ryuta Kamizono 已提交
281 282 283
  end

  def test_exists_with_left_joins
B
Ben Toews 已提交
284
    assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
R
Ryuta Kamizono 已提交
285 286 287
  end

  def test_exists_with_eager_load
B
Ben Toews 已提交
288
    assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
R
Ryuta Kamizono 已提交
289 290
  end

291
  def test_exists_with_includes_limit_and_empty_result
292 293
    assert_no_queries { assert_equal false, Topic.includes(:replies).limit(0).exists? }
    assert_queries(1) { assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? }
294 295
  end

296 297
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
298 299 300
    unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments)
    assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
    assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? }
301 302 303 304
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
305 306 307
    unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC")
    assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
    assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? }
308 309
  end

310
  def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
311 312
    ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 })
    assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? }
313 314
  end

315
  def test_exists_with_empty_table_and_no_args_given
316
    Topic.delete_all
317
    assert_equal false, Topic.exists?
318
  end
319

320 321
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
322
    assert_equal true, Customer.exists?(address: existing_address)
323 324 325 326
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
R
Ryuta Kamizono 已提交
327 328 329
    assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
    assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
    assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
330 331
  end

332
  def test_exists_does_not_instantiate_records
333 334 335
    assert_not_called(Developer, :instantiate) do
      Developer.exists?
    end
336 337
  end

D
David Heinemeier Hansson 已提交
338 339 340 341
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
342

D
David Heinemeier Hansson 已提交
343
  def test_find_by_ids
344 345 346 347 348
    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
349 350
    assert_equal 2, Entrant.limit(2).find([1, 3, 2]).size
    entrants = Entrant.limit(3).offset(2).find([1, 3, 2])
351
    assert_equal 1, entrants.size
352
    assert_equal "Ruby Guru", entrants.first.name
353 354 355 356

    # 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 已提交
357
    devs = Developer.all
358
    last_devs = Developer.limit(3).offset(9).find devs.map(&:id)
359
    assert_equal 2, last_devs.size
360 361
    assert_equal "fixture_10", last_devs[0].name
    assert_equal "Jamis", last_devs[1].name
D
David Heinemeier Hansson 已提交
362 363
  end

364
  def test_find_with_large_number
365
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find("9999999999999999999999999999999") }
366 367 368
  end

  def test_find_by_with_large_number
369
    assert_nil Topic.find_by(id: "9999999999999999999999999999999")
370 371 372
  end

  def test_find_by_id_with_large_number
373
    assert_nil Topic.find_by_id("9999999999999999999999999999999")
374 375
  end

376
  def test_find_on_relation_with_large_number
377
    assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
378 379 380 381
  end

  def test_find_by_bang_on_relation_with_large_number
    assert_raises(ActiveRecord::RecordNotFound) do
382
      Topic.where("1=1").find_by!(id: 9999999999999999999999999999999)
383 384 385
    end
  end

386
  def test_find_an_empty_array
387 388 389 390
    empty_array = []
    result = Topic.find(empty_array)
    assert_equal [], result
    assert_not_same empty_array, result
391 392
  end

393 394 395 396
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
397
  def test_find_by_ids_missing_one
398
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
399
  end
400

401
  def test_find_with_group_and_sanitized_having_method
402
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select("salary").to_a
403 404 405 406 407
    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 已提交
408 409
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
410

D
David Heinemeier Hansson 已提交
411
    assert_equal(1, topics.size)
412
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
413
  end
414

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

D
David Heinemeier Hansson 已提交
418
    assert_equal(1, topics.size)
419
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
420
  end
421

422 423 424 425
  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
426

427 428 429 430 431 432
  def test_find_by_association_subquery
    author = authors(:david)
    assert_equal author.post, Post.find_by(author: Author.where(id: author))
    assert_equal author.post, Post.find_by(author_id: Author.where(id: author))
  end

433 434 435 436 437
  def test_find_by_and_where_consistency_with_active_record_instance
    author = authors(:david)
    assert_equal Post.where(author_id: author).take, Post.find_by(author_id: author)
  end

438
  def test_take
439
    assert_equal topics(:first), Topic.where("title = 'The First Topic'").take
440 441 442 443 444 445 446 447 448 449 450 451 452
  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
453
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
454 455 456 457
      Topic.where("title = 'This title does not exist'").take!
    end
  end

458
  def test_first
459
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
460
  end
461

462
  def test_first_failing
463
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
464
  end
D
David Heinemeier Hansson 已提交
465

466 467 468 469 470 471 472
  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
473
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
474 475 476 477
      Topic.where("title = 'This title does not exist'").first!
    end
  end

478 479 480 481 482 483
  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

484
  def test_model_class_responds_to_first_bang
485 486
    assert Topic.first!
    Topic.delete_all
487
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
488 489 490 491
      Topic.first!
    end
  end

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
  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
509
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
      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
531
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
      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
553
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
      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
575
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
576 577 578 579
      Topic.fifth!
    end
  end

580 581
  def test_second_to_last
    assert_equal topics(:fourth).title, Topic.second_to_last.title
582 583 584

    # test with offset
    assert_equal topics(:fourth), Topic.offset(1).second_to_last
585 586
    assert_equal topics(:fourth), Topic.offset(2).second_to_last
    assert_equal topics(:fourth), Topic.offset(3).second_to_last
587 588
    assert_nil Topic.offset(4).second_to_last
    assert_nil Topic.offset(5).second_to_last
589

590
    # test with limit
591
    assert_nil Topic.limit(1).second
592
    assert_nil Topic.limit(1).second_to_last
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
  end

  def test_second_to_last_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.second_to_last
  end

  def test_model_class_responds_to_second_to_last_bang
    assert Topic.second_to_last!
    Topic.delete_all
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
      Topic.second_to_last!
    end
  end

  def test_third_to_last
    assert_equal topics(:third).title, Topic.third_to_last.title
611 612 613

    # test with offset
    assert_equal topics(:third), Topic.offset(1).third_to_last
614
    assert_equal topics(:third), Topic.offset(2).third_to_last
615 616 617
    assert_nil Topic.offset(3).third_to_last
    assert_nil Topic.offset(4).third_to_last
    assert_nil Topic.offset(5).third_to_last
618 619

    # test with limit
620
    assert_nil Topic.limit(1).third
621
    assert_nil Topic.limit(1).third_to_last
622
    assert_nil Topic.limit(2).third
623
    assert_nil Topic.limit(2).third_to_last
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
  end

  def test_third_to_last_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_to_last
  end

  def test_model_class_responds_to_third_to_last_bang
    assert Topic.third_to_last!
    Topic.delete_all
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
      Topic.third_to_last!
    end
  end

640 641 642 643 644 645 646
  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
647
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
648 649 650 651
      Topic.where("title = 'This title does not exist'").last!
    end
  end

652
  def test_model_class_responds_to_last_bang
653
    assert_equal topics(:fifth), Topic.last!
654
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
655 656 657 658 659
      Topic.delete_all
      Topic.last!
    end
  end

660
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
661 662 663
    assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.take(3).entries }
    assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.first(2).entries }
    assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.last(5).entries }
664 665 666 667 668 669
  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

670 671 672
  def test_last_with_integer_and_order_should_use_sql_limit
    relation = Topic.order("title")
    assert_queries(1) { relation.last(5) }
673
    assert_not_predicate relation, :loaded?
674 675
  end

676 677 678
  def test_last_with_integer_and_reorder_should_use_sql_limit
    relation = Topic.reorder("title")
    assert_queries(1) { relation.last(5) }
679
    assert_not_predicate relation, :loaded?
680 681 682
  end

  def test_last_on_loaded_relation_should_not_use_sql
683
    relation = Topic.limit(10).load
684 685 686 687 688 689 690
    assert_no_queries do
      relation.last
      relation.last(2)
    end
  end

  def test_last_with_irreversible_order
691
    assert_raises(ActiveRecord::IrreversibleOrderError) do
692
      Topic.order(Arel.sql("coalesce(author_name, title)")).last
693
    end
694
  end
695 696

  def test_last_on_relation_with_limit_and_offset
697
    post = posts("sti_comments")
698 699 700 701 702 703

    comments = post.comments.order(id: :asc)
    assert_equal comments.limit(2).to_a.last, comments.limit(2).last
    assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
    assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)

704 705 706 707
    assert_equal comments.offset(2).to_a.last, comments.offset(2).last
    assert_equal comments.offset(2).to_a.last(2), comments.offset(2).last(2)
    assert_equal comments.offset(2).to_a.last(3), comments.offset(2).last(3)

708 709 710 711
    comments = comments.offset(1)
    assert_equal comments.limit(2).to_a.last, comments.limit(2).last
    assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
    assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
  end

  def test_first_on_relation_with_limit_and_offset
    post = posts("sti_comments")

    comments = post.comments.order(id: :asc)
    assert_equal comments.limit(2).to_a.first, comments.limit(2).first
    assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
    assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)

    assert_equal comments.offset(2).to_a.first, comments.offset(2).first
    assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2)
    assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3)

    comments = comments.offset(1)
    assert_equal comments.limit(2).to_a.first, comments.limit(2).first
    assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
    assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
730 731
  end

732 733
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
734 735 736 737
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
738
  def test_unexisting_record_exception_handling
739
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
740 741
      Topic.find(1).parent
    }
742

743
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
744
  end
745

746
  def test_find_only_some_columns
747
    topic = Topic.select("author_name").find(1)
748 749
    assert_raise(ActiveModel::MissingAttributeError) { topic.title }
    assert_raise(ActiveModel::MissingAttributeError) { topic.title? }
750
    assert_nil topic.read_attribute("title")
751 752
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
753
    assert !topic.attribute_present?(:title)
754
    assert topic.attribute_present?("author_name")
755
    assert_respond_to topic, "author_name"
756
  end
J
Jeremy Kemper 已提交
757

758
  def test_find_on_array_conditions
759 760
    assert Topic.where(["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
761
  end
762

763
  def test_find_on_hash_conditions
764 765
    assert Topic.where(approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
766
  end
767

768
  def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
769 770
    assert Topic.where("topics.approved" => false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => true).find(1) }
771 772
  end

773 774 775 776 777
  def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol
    assert Topic.where('topics.approved': false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) }
  end

778
  def test_find_on_hash_conditions_with_hashed_table_name
779 780
    assert Topic.where(topics: { approved: false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
781 782
  end

783
  def test_find_on_combined_explicit_and_hashed_table_names
784 785 786
    assert Topic.where("topics.approved" => false, topics: { author_name: "David" }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => true, topics: { author_name: "David" }).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => false, topics: { author_name: "Melanie" }).find(1) }
787 788
  end

789
  def test_find_with_hash_conditions_on_joined_table
790
    firms = Firm.joins(:account).where(accounts: { credit_limit: 50 })
791 792 793 794 795
    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
796
    firms = DependentFirm.joins(:account).where(name: "RailsCore", accounts: { credit_limit: 55..60 })
797 798 799 800
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

801 802
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
803
    assert Customer.where("customers.name" => david.name, :address => david.address).find(david.id)
804
    assert_raise(ActiveRecord::RecordNotFound) {
805
      Customer.where("customers.name" => david.name + "1", :address => david.address).find(david.id)
806 807 808
    }
  end

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

813
  def test_find_on_hash_conditions_with_range
814
    assert_equal [1, 2], Topic.where(id: 1..2).to_a.map(&:id).sort
815
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
816
  end
817

818
  def test_find_on_hash_conditions_with_end_exclusive_range
819 820
    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
821
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
822 823
  end

824
  def test_find_on_hash_conditions_with_multiple_ranges
825
    assert_equal [1, 2, 3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
826
    assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
827
  end
828

829
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
830
    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
831 832
  end

833
  def test_find_on_hash_conditions_with_array_of_ranges
834
    assert_equal [1, 2, 6, 7, 8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
835 836
  end

837
  def test_find_on_multiple_hash_conditions
838 839 840
    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) }
841
  end
842

843
  def test_condition_interpolation
844
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
845 846 847
    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
848 849
  end

850
  def test_condition_array_interpolation
851 852 853 854
    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 已提交
855
  end
856

857
  def test_condition_hash_interpolation
858 859 860
    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
861
  end
862

863
  def test_hash_condition_find_malformed
864
    assert_raise(ActiveRecord::StatementInvalid) {
865
      Company.where(id: 2, dhh: true).first
866 867
    }
  end
868

869 870
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
871
    assert Company.where(name: "Ain't noth'n like' \#stuff").first
872 873 874
  end

  def test_hash_condition_find_with_array
875 876 877
    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
878 879 880
  end

  def test_hash_condition_find_with_nil
881
    topic = Topic.where(last_read: nil).first
882 883
    assert_not_nil topic
    assert_nil topic.last_read
884
  end
D
David Heinemeier Hansson 已提交
885

886 887 888
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
889
    found_customer = Customer.where(balance: balance).first
890 891 892
    assert_equal customers(:david), found_customer
  end

893
  def test_hash_condition_find_with_aggregate_having_three_mappings_array
894 895
    david_address = customers(:david).address
    zaphod_address = customers(:zaphod).address
896
    barney_address = customers(:barney).address
897 898
    assert_kind_of Address, david_address
    assert_kind_of Address, zaphod_address
899 900
    found_customers = Customer.where(address: [david_address, zaphod_address, barney_address])
    assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id)
901 902 903 904 905 906 907 908
  end

  def test_hash_condition_find_with_aggregate_having_one_mapping_array
    david_balance = customers(:david).balance
    zaphod_balance = customers(:zaphod).balance
    assert_kind_of Money, david_balance
    assert_kind_of Money, zaphod_balance
    found_customers = Customer.where(balance: [david_balance, zaphod_balance])
909
    assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
910
    assert_equal Customer.where(balance: [david_balance.amount, zaphod_balance.amount]).to_sql, found_customers.to_sql
911 912
  end

913 914 915
  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
916
    found_customer = Customer.where(gps_location: gps_location).first
917 918 919 920 921 922
    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
923
    found_customer = Customer.where(balance: balance.amount).first
924 925 926 927 928 929
    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
930
    found_customer = Customer.where(gps_location: gps_location.gps_location).first
931 932 933 934 935 936
    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
937
    found_customer = Customer.where(address: address).first
938 939 940 941 942 943
    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
944
    found_customer = Customer.where(address: address, name: customers(:david).name).first
945 946 947
    assert_equal customers(:david), found_customer
  end

948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
  def test_hash_condition_find_nil_with_aggregate_having_one_mapping
    assert_nil customers(:zaphod).gps_location
    found_customer = Customer.where(gps_location: nil, name: customers(:zaphod).name).first
    assert_equal customers(:zaphod), found_customer
  end

  def test_hash_condition_find_nil_with_aggregate_having_multiple_mappings
    customers(:david).update(address: nil)
    assert_nil customers(:david).address_street
    assert_nil customers(:david).address_city
    found_customer = Customer.where(address: nil, name: customers(:david).name).first
    assert_equal customers(:david), found_customer
  end

  def test_hash_condition_find_empty_array_with_aggregate_having_multiple_mappings
    assert_nil Customer.where(address: []).first
  end

966
  def test_condition_utc_time_interpolation_with_default_timezone_local
967
    with_env_tz "America/New_York" do
968
      with_timezone_config default: :local do
969
        topic = Topic.first
970
        assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getutc]).first
971 972 973 974 975
      end
    end
  end

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
976
    with_env_tz "America/New_York" do
977
      with_timezone_config default: :local do
978
        topic = Topic.first
979
        assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
980 981 982 983 984
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
985
    with_env_tz "America/New_York" do
986
      with_timezone_config default: :utc do
987
        topic = Topic.first
988
        assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getlocal]).first
989 990 991 992 993
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
994
    with_env_tz "America/New_York" do
995
      with_timezone_config default: :utc do
996
        topic = Topic.first
997
        assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
998 999 1000 1001
      end
    end
  end

D
David Heinemeier Hansson 已提交
1002
  def test_bind_variables
1003 1004 1005 1006
    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
1007
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1008
      Company.where(["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
1009
    }
1010
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1011
      Company.where(["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
1012 1013
    }
  end
1014

D
David Heinemeier Hansson 已提交
1015
  def test_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1016 1017
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = ?", "37signals' go'es against"]).first
D
David Heinemeier Hansson 已提交
1018 1019 1020
  end

  def test_named_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1021 1022
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first
D
David Heinemeier Hansson 已提交
1023 1024 1025
  end

  def test_named_bind_variables
1026 1027 1028 1029
    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 已提交
1030 1031 1032 1033 1034 1035 1036 1037 1038
  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
1039
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
1040 1041
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
1042

1043 1044
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
1045 1046 1047
    assert_raises_with_message(ActiveRecord::RecordNotFound, "Couldn't find Topic") do
      Topic.find_by_title!("The First Topic!")
    end
1048 1049
  end

1050
  def test_find_by_on_attribute_that_is_a_reserved_word
1051
    dog_alias = "Dog"
1052 1053 1054 1055 1056
    dog = Dog.create(alias: dog_alias)

    assert_equal dog, Dog.find_by_alias(dog_alias)
  end

1057 1058 1059 1060 1061
  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 已提交
1062
  def test_find_by_one_attribute_bang_with_blank_defined
1063 1064
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
1065 1066
  end

1067
  def test_find_by_one_attribute_with_conditions
1068
    assert_equal accounts(:rails_core_account), Account.where("firm_id = ?", 6).find_by_credit_limit(50)
1069 1070
  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 1104
  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

1105 1106
  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 已提交
1107
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
1108 1109
    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
1110 1111
  end

1112
  def test_find_by_one_attribute_with_several_options
1113
    assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50)
1114
  end
1115

D
David Heinemeier Hansson 已提交
1116
  def test_find_by_one_missing_attribute
1117
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
1118
  end
1119

1120
  def test_find_by_invalid_method_syntax
1121 1122 1123 1124
    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") }
1125
  end
D
David Heinemeier Hansson 已提交
1126 1127

  def test_find_by_two_attributes
1128
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
1129 1130 1131
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

1132 1133 1134 1135
  def test_find_by_two_attributes_but_passing_only_one
    assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
  end

D
David Heinemeier Hansson 已提交
1136 1137 1138 1139 1140
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
1141

D
David Heinemeier Hansson 已提交
1142 1143 1144 1145 1146 1147
  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
1148
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
1149 1150
  end

1151
  def test_joins_dont_clobber_id
1152
    first = Firm.
1153 1154
      joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
      where("companies.id = 1").first
1155 1156 1157
    assert_equal 1, first.id
  end

1158
  def test_joins_with_string_array
1159 1160 1161 1162
    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'"
            ])
1163 1164 1165
    assert_equal 1, person_with_reader_and_post.size
  end

1166 1167
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
1168
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1, 2, 3])
1169 1170
    end
  end
1171

1172
  def test_find_ignores_previously_inserted_record
1173
    Post.create!(title: "test", body: "it out")
J
Jon Leighton 已提交
1174
    assert_equal [], Post.where(id: nil)
1175 1176
  end

1177 1178 1179 1180 1181
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
1182
    assert_equal [], Post.where("id in (?)", [])
1183 1184 1185
  end

  def test_find_by_records
1186 1187 1188
    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")
1189 1190
  end

1191 1192 1193 1194 1195 1196 1197 1198 1199
  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
1200 1201
    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!(&: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")
1202 1203
  end

1204 1205
  def test_select_rows
    assert_equal(
1206
      [["1", "1", nil, "37signals"],
1207 1208
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
1209
      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? } })
1210
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
1211
      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? } }
1212 1213
  end

1214
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
R
Fix...  
Ryuta Kamizono 已提交
1215 1216
    assert_equal 2, Post.includes(authors: :author_address).
      where.not(author_addresses: { id: nil }).
B
Ben Toews 已提交
1217
      order("author_addresses.id DESC").limit(2).to_a.size
1218

1219
    assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
R
Fix...  
Ryuta Kamizono 已提交
1220
      where.not(author_addresses_authors: { id: nil }).
B
Ben Toews 已提交
1221
      order("author_addresses_authors.id DESC").limit(3).to_a.size
1222 1223
  end

1224 1225 1226 1227 1228
  def test_find_with_eager_loading_collection_and_ordering_by_collection_primary_key
    assert_equal Post.first, Post.eager_load(comments: :ratings).
      order("posts.id, ratings.id, comments.id").first
  end

1229
  def test_find_with_nil_inside_set_passed_for_one_attribute
1230 1231
    client_of = Company.
      where(client_of: [2, 1, nil],
1232
            name: ["37signals", "Summit", "Microsoft"]).
1233
      order("client_of DESC").
1234
      map(&:client_of)
1235

1236
    assert_includes client_of, nil
1237
    assert_equal [2, 1].sort, client_of.compact.sort
1238 1239
  end

1240
  def test_find_with_nil_inside_set_passed_for_attribute
1241 1242
    client_of = Company.
      where(client_of: [nil]).
1243
      order("client_of DESC").
1244
      map(&:client_of)
1245 1246 1247 1248

    assert_equal [], client_of.compact
  end

1249
  def test_with_limiting_with_custom_select
1250
    posts = Post.references(:authors).merge(
1251
      includes: :author, select: 'posts.*, authors.id as "author_id"',
1252
      limit: 3, order: "posts.id"
1253
    ).to_a
1254 1255
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
1256
  end
1257

1258 1259 1260 1261 1262 1263 1264 1265 1266 1267
  def test_find_one_message_on_primary_key
    e = assert_raises(ActiveRecord::RecordNotFound) do
      Car.find(0)
    end
    assert_equal 0, e.id
    assert_equal "id", e.primary_key
    assert_equal "Car", e.model
    assert_equal "Couldn't find Car with 'id'=0", e.message
  end

1268
  def test_find_one_message_with_custom_primary_key
1269 1270 1271
    table_with_custom_primary_key do |model|
      model.primary_key = :name
      e = assert_raises(ActiveRecord::RecordNotFound) do
1272
        model.find "Hello World!"
1273
      end
1274
      assert_equal "Couldn't find MercedesCar with 'name'=Hello World!", e.message
1275 1276 1277 1278 1279 1280 1281
    end
  end

  def test_find_some_message_with_custom_primary_key
    table_with_custom_primary_key do |model|
      model.primary_key = :name
      e = assert_raises(ActiveRecord::RecordNotFound) do
1282
        model.find "Hello", "World!"
1283
      end
1284
      assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2).", e.message
1285 1286 1287
    end
  end

1288 1289 1290 1291 1292 1293
  def test_find_without_primary_key
    assert_raises(ActiveRecord::UnknownPrimaryKey) do
      Matey.find(1)
    end
  end

1294
  def test_finder_with_offset_string
1295
    assert_nothing_raised { Topic.offset("3").to_a }
1296 1297
  end

1298 1299 1300 1301 1302 1303 1304 1305 1306
  test "find_by with hash conditions returns the first matching record" do
    assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id)
  end

  test "find_by with non-hash conditions returns the first matching record" do
    assert_equal posts(:eager_other), Post.find_by("id = #{posts(:eager_other).id}")
  end

  test "find_by with multi-arg conditions returns the first matching record" do
1307
    assert_equal posts(:eager_other), Post.find_by("id = ?", posts(:eager_other).id)
1308 1309
  end

1310 1311 1312 1313
  test "find_by with range conditions returns the first matching record" do
    assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id...posts(:misc_by_bob).id)
  end

1314
  test "find_by returns nil if the record is missing" do
1315
    assert_nil Post.find_by("1 = 0")
1316 1317
  end

1318 1319
  test "find_by with associations" do
    assert_equal authors(:david), Post.find_by(author: authors(:david)).author
1320
    assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author
1321 1322
  end

1323 1324 1325 1326
  test "find_by doesn't have implicit ordering" do
    assert_sql(/^((?!ORDER).)*$/) { Post.find_by(id: posts(:eager_other).id) }
  end

1327 1328 1329 1330 1331 1332 1333 1334 1335
  test "find_by! with hash conditions returns the first matching record" do
    assert_equal posts(:eager_other), Post.find_by!(id: posts(:eager_other).id)
  end

  test "find_by! with non-hash conditions returns the first matching record" do
    assert_equal posts(:eager_other), Post.find_by!("id = #{posts(:eager_other).id}")
  end

  test "find_by! with multi-arg conditions returns the first matching record" do
1336
    assert_equal posts(:eager_other), Post.find_by!("id = ?", posts(:eager_other).id)
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348
  end

  test "find_by! doesn't have implicit ordering" do
    assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(id: posts(:eager_other).id) }
  end

  test "find_by! raises RecordNotFound if the record is missing" do
    assert_raises(ActiveRecord::RecordNotFound) do
      Post.find_by!("1 = 0")
    end
  end

1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
  test "find on a scope does not perform statement caching" do
    honda = cars(:honda)
    zyke = cars(:zyke)
    tyre = honda.tyres.create!
    tyre2 = zyke.tyres.create!

    assert_equal tyre, honda.tyres.custom_find(tyre.id)
    assert_equal tyre2, zyke.tyres.custom_find(tyre2.id)
  end

  test "find_by on a scope does not perform statement caching" do
    honda = cars(:honda)
    zyke = cars(:zyke)
    tyre = honda.tyres.create!
    tyre2 = zyke.tyres.create!

    assert_equal tyre, honda.tyres.custom_find_by(id: tyre.id)
    assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
  end

1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384
  test "#skip_query_cache! for #exists?" do
    Topic.cache do
      assert_queries(1) do
        Topic.exists?
        Topic.exists?
      end

      assert_queries(2) do
        Topic.all.skip_query_cache!.exists?
        Topic.all.skip_query_cache!.exists?
      end
    end
  end

  test "#skip_query_cache! for #exists? with a limited eager load" do
    Topic.cache do
1385
      assert_queries(1) do
1386 1387 1388 1389
        Topic.eager_load(:replies).limit(1).exists?
        Topic.eager_load(:replies).limit(1).exists?
      end

1390
      assert_queries(2) do
1391 1392 1393 1394 1395 1396
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
      end
    end
  end

1397
  private
1398 1399 1400
    def table_with_custom_primary_key
      yield(Class.new(Toy) do
        def self.name
1401
          "MercedesCar"
1402 1403 1404
        end
      end)
    end
1405 1406 1407 1408 1409

    def assert_raises_with_message(exception_class, message, &block)
      err = assert_raises(exception_class) { block.call }
      assert_match message, err.message
    end
D
David Heinemeier Hansson 已提交
1410
end