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

3
require "cases/helper"
4 5
require "models/post"
require "models/author"
6
require "models/account"
7 8 9 10 11 12
require "models/categorization"
require "models/comment"
require "models/company"
require "models/tagging"
require "models/topic"
require "models/reply"
13
require "models/rating"
14 15 16 17 18 19 20 21 22 23
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"
24
require "models/subscriber"
25
require "models/non_primary_key"
26
require "support/stubs/strong_parameters"
D
David Heinemeier Hansson 已提交
27

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

31
  def test_find_by_id_with_hash
32
    assert_nothing_raised do
33
      Post.find_by_id(limit: 1)
34 35 36 37
    end
  end

  def test_find_by_title_and_id_with_hash
38
    assert_nothing_raised do
39
      Post.find_by_title_and_id("foo", limit: 1)
40 41 42
    end
  end

D
David Heinemeier Hansson 已提交
43
  def test_find
44
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
45
  end
46

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

53
    assert_nothing_raised do
54 55 56 57
      Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
    end
  end

58
  def test_find_with_ids_returning_ordered
59
    records = Topic.find([4, 2, 5])
60 61 62
    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
63

64
    records = Topic.find(4, 2, 5)
65 66 67
    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
68

69
    records = Topic.find(["4", "2", "5"])
70 71 72
    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
73

74
    records = Topic.find("4", "2", "5")
75 76 77
    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
78 79 80 81
  end

  def test_find_with_ids_and_order_clause
    # The order clause takes precedence over the informed ids
82
    records = Topic.order(:author_name).find([5, 3, 1])
83 84 85
    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
86

87
    records = Topic.order(:id).find([5, 3, 1])
88 89 90
    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
91 92
  end

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

  def test_find_with_ids_and_limit
102
    records = Topic.limit(3).find([3, 2, 5, 1, 4])
103
    assert_equal 3, records.size
104 105 106
    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
107 108
  end

109 110
  def test_find_with_ids_where_and_limit
    # Please note that Topic 1 is the only not approved so
111
    # if it were among the first 3 it would raise an ActiveRecord::RecordNotFound
112
    records = Topic.where(approved: true).limit(3).find([3, 2, 5, 1, 4])
113
    assert_equal 3, records.size
114 115 116
    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
117 118
  end

119
  def test_find_with_ids_and_offset
120
    records = Topic.offset(2).find([3, 2, 5, 1, 4])
121
    assert_equal 3, records.size
122 123 124
    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
125 126
  end

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
  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

142
  def test_find_passing_active_record_object_is_not_permitted
143
    assert_raises(ArgumentError) do
144 145 146 147
      Topic.find(Topic.last)
    end
  end

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

158 159 160
  # find should handle strings that come from URLs
  # (example: Category.find(params[:id]))
  def test_find_with_string
161
    assert_equal(Topic.find(1).title, Topic.find("1").title)
162
  end
163

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

    assert_equal false, Topic.exists?(45)
174
    assert_equal false, Topic.exists?(9999999999999999999999999999999)
175
    assert_equal false, Topic.exists?(Topic.new.id)
176

177
    assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
178 179
  end

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
  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

206
  def test_exists_with_polymorphic_relation
207 208
    post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")])
    relation = Post.tagged_with_comment("tagging comment")
209

210 211
    assert_equal true, relation.exists?(title: ["Post"])
    assert_equal true, relation.exists?(["title LIKE ?", "Post%"])
212 213 214 215 216 217 218
    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

219 220 221 222 223 224 225 226 227 228 229
  def test_exists_with_string
    assert_equal false, Subscriber.exists?("foo")
    assert_equal false, Subscriber.exists?("   ")

    Subscriber.create!(id: "foo")
    Subscriber.create!(id: "   ")

    assert_equal true, Subscriber.exists?("foo")
    assert_equal true, Subscriber.exists?("   ")
  end

230
  def test_exists_with_strong_parameters
231
    assert_equal false, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
232 233 234

    Subscriber.create!(nick: "foo")

235
    assert_equal true, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
236 237

    assert_raises(ActiveModel::ForbiddenAttributesError) do
238
      Subscriber.exists?(ProtectedParams.new(nick: "foo"))
239
    end
240 241
  end

242
  def test_exists_passing_active_record_object_is_not_permitted
243
    assert_raises(ArgumentError) do
244 245 246 247
      Topic.exists?(Topic.new)
    end
  end

248
  def test_exists_does_not_select_columns_without_alias
249 250
    c = Topic.connection
    assert_sql(/SELECT 1 AS one FROM #{Regexp.escape(c.quote_table_name("topics"))}/i) do
251 252 253 254
      Topic.exists?
    end
  end

255
  def test_exists_returns_true_with_one_record_and_no_args
256
    assert_equal true, Topic.exists?
257
  end
258

E
Egor Lynko 已提交
259
  def test_exists_returns_false_with_false_arg
260
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
261 262
  end

263 264 265
  # 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
266 267 268 269 270
    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?
271 272
  end

273 274 275 276
  def test_exists_with_empty_hash_arg
    assert_equal true, Topic.exists?({})
  end

277 278 279 280 281 282
  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
R
Ryuta Kamizono 已提交
283 284
    assert Post.select(:body).distinct.offset(4).exists?
    assert_not Post.select(:body).distinct.offset(5).exists?
285 286
  end

287 288 289 290 291
  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 已提交
292 293 294
  # 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
295
    assert_equal true, Topic.order(:id).distinct.exists?
296 297
  end

298 299
  # Ensure +exists?+ runs without an error by excluding order value.
  def test_exists_with_order
300
    assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
301 302
  end

303 304 305 306 307 308 309 310 311 312 313
  def test_exists_with_large_number
    assert_equal true, Topic.where(id: [1, 9223372036854775808]).exists?
    assert_equal true, Topic.where(id: 1..9223372036854775808).exists?
    assert_equal true, Topic.where(id: -9223372036854775809..9223372036854775808).exists?
    assert_equal false, Topic.where(id: 9223372036854775808..9223372036854775809).exists?
    assert_equal false, Topic.where(id: -9223372036854775810..-9223372036854775809).exists?
    assert_equal false, Topic.where(id: 9223372036854775808..1).exists?
    assert_equal true, Topic.where(id: 1).or(Topic.where(id: 9223372036854775808)).exists?
    assert_equal true, Topic.where.not(id: 9223372036854775808).exists?
  end

R
Ryuta Kamizono 已提交
314
  def test_exists_with_joins
B
Ben Toews 已提交
315
    assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
R
Ryuta Kamizono 已提交
316 317 318
  end

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

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

326
  def test_exists_with_includes_limit_and_empty_result
327 328
    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? }
329 330
  end

331 332
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
333 334 335
    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? }
336 337 338 339
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
340 341 342
    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? }
343 344
  end

345
  def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
346 347
    ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 })
    assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? }
348 349
  end

350
  def test_exists_with_empty_table_and_no_args_given
351
    Topic.delete_all
352
    assert_equal false, Topic.exists?
353
  end
354

355 356
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
357
    assert_equal true, Customer.exists?(address: existing_address)
358 359 360 361
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
R
Ryuta Kamizono 已提交
362 363 364
    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))
365 366
  end

367
  def test_exists_does_not_instantiate_records
368 369 370
    assert_not_called(Developer, :instantiate) do
      Developer.exists?
    end
371 372
  end

D
David Heinemeier Hansson 已提交
373 374 375 376
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
377

D
David Heinemeier Hansson 已提交
378
  def test_find_by_ids
379 380 381 382 383
    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
384 385
    assert_equal 2, Entrant.limit(2).find([1, 3, 2]).size
    entrants = Entrant.limit(3).offset(2).find([1, 3, 2])
386
    assert_equal 1, entrants.size
387
    assert_equal "Ruby Guru", entrants.first.name
388 389 390 391

    # 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 已提交
392
    devs = Developer.all
393
    last_devs = Developer.limit(3).offset(9).find(devs.map(&:id).sort)
394
    assert_equal 2, last_devs.size
395 396
    assert_equal "fixture_10", last_devs[0].name
    assert_equal "Jamis", last_devs[1].name
D
David Heinemeier Hansson 已提交
397 398
  end

399
  def test_find_with_large_number
R
Ryuta Kamizono 已提交
400
    assert_queries(0) do
401 402
      assert_raises(ActiveRecord::RecordNotFound) { Topic.find("9999999999999999999999999999999") }
    end
403 404 405
  end

  def test_find_by_with_large_number
R
Ryuta Kamizono 已提交
406
    assert_queries(0) do
407 408
      assert_nil Topic.find_by(id: "9999999999999999999999999999999")
    end
409 410 411
  end

  def test_find_by_id_with_large_number
R
Ryuta Kamizono 已提交
412
    assert_queries(0) do
413 414
      assert_nil Topic.find_by_id("9999999999999999999999999999999")
    end
415 416
  end

417
  def test_find_on_relation_with_large_number
418 419 420
    assert_raises(ActiveRecord::RecordNotFound) do
      Topic.where("1=1").find(9999999999999999999999999999999)
    end
421
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find(1)
422 423 424
  end

  def test_find_by_on_relation_with_large_number
425
    assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
426
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by(id: 1)
427 428 429 430
  end

  def test_find_by_bang_on_relation_with_large_number
    assert_raises(ActiveRecord::RecordNotFound) do
431
      Topic.where("1=1").find_by!(id: 9999999999999999999999999999999)
432
    end
433
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by!(id: 1)
434 435
  end

436
  def test_find_an_empty_array
437 438 439 440
    empty_array = []
    result = Topic.find(empty_array)
    assert_equal [], result
    assert_not_same empty_array, result
441 442
  end

443 444 445 446
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
447
  def test_find_by_ids_missing_one
448
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
449
  end
450

451
  def test_find_with_group_and_sanitized_having_method
452
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select("salary").to_a
453 454 455 456 457
    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 已提交
458 459
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
460

D
David Heinemeier Hansson 已提交
461
    assert_equal(1, topics.size)
462
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
463
  end
464

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

D
David Heinemeier Hansson 已提交
468
    assert_equal(1, topics.size)
469
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
470
  end
471

472 473 474 475
  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
476

477
  def test_find_by_association_subquery
478 479 480
    firm = companies(:first_firm)
    assert_equal firm.account, Account.find_by(firm: Firm.where(id: firm))
    assert_equal firm.account, Account.find_by(firm_id: Firm.where(id: firm))
481 482
  end

483
  def test_find_by_and_where_consistency_with_active_record_instance
484 485
    firm = companies(:first_firm)
    assert_equal Account.where(firm_id: firm).take, Account.find_by(firm_id: firm)
486 487
  end

488
  def test_take
489
    assert_equal topics(:first), Topic.where("title = 'The First Topic'").take
490 491 492 493 494 495 496 497 498 499 500 501 502
  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
503
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
504 505 506 507
      Topic.where("title = 'This title does not exist'").take!
    end
  end

508
  def test_first
509
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
510
  end
511

512
  def test_first_failing
513
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
514
  end
D
David Heinemeier Hansson 已提交
515

516 517 518 519 520 521 522
  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
523
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
524 525 526 527
      Topic.where("title = 'This title does not exist'").first!
    end
  end

528 529 530 531
  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
532
    assert_equal expected, Topic.limit(5).first
533
    assert_equal expected, Topic.order(nil).first
534 535
  end

536
  def test_model_class_responds_to_first_bang
537 538
    assert Topic.first!
    Topic.delete_all
539
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
540 541 542 543
      Topic.first!
    end
  end

544 545 546 547 548 549 550 551 552 553 554 555
  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
556
    assert_equal expected, Topic.limit(5).second
557
    assert_equal expected, Topic.order(nil).second
558 559 560 561 562
  end

  def test_model_class_responds_to_second_bang
    assert Topic.second!
    Topic.delete_all
563
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579
      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
580
    assert_equal expected, Topic.limit(5).third
581
    assert_equal expected, Topic.order(nil).third
582 583 584 585 586
  end

  def test_model_class_responds_to_third_bang
    assert Topic.third!
    Topic.delete_all
587
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
      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
604
    assert_equal expected, Topic.limit(5).fourth
605
    assert_equal expected, Topic.order(nil).fourth
606 607 608 609 610
  end

  def test_model_class_responds_to_fourth_bang
    assert Topic.fourth!
    Topic.delete_all
611
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
      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
628
    assert_equal expected, Topic.limit(5).fifth
629
    assert_equal expected, Topic.order(nil).fifth
630 631 632 633 634
  end

  def test_model_class_responds_to_fifth_bang
    assert Topic.fifth!
    Topic.delete_all
635
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
636 637 638 639
      Topic.fifth!
    end
  end

640 641
  def test_second_to_last
    assert_equal topics(:fourth).title, Topic.second_to_last.title
642 643 644

    # test with offset
    assert_equal topics(:fourth), Topic.offset(1).second_to_last
645 646
    assert_equal topics(:fourth), Topic.offset(2).second_to_last
    assert_equal topics(:fourth), Topic.offset(3).second_to_last
647 648
    assert_nil Topic.offset(4).second_to_last
    assert_nil Topic.offset(5).second_to_last
649

650
    # test with limit
651
    assert_nil Topic.limit(1).second
652
    assert_nil Topic.limit(1).second_to_last
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
  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
671 672 673

    # test with offset
    assert_equal topics(:third), Topic.offset(1).third_to_last
674
    assert_equal topics(:third), Topic.offset(2).third_to_last
675 676 677
    assert_nil Topic.offset(3).third_to_last
    assert_nil Topic.offset(4).third_to_last
    assert_nil Topic.offset(5).third_to_last
678 679

    # test with limit
680
    assert_nil Topic.limit(1).third
681
    assert_nil Topic.limit(1).third_to_last
682
    assert_nil Topic.limit(2).third
683
    assert_nil Topic.limit(2).third_to_last
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
  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

700 701 702 703 704 705 706
  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
707
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
708 709 710 711
      Topic.where("title = 'This title does not exist'").last!
    end
  end

712
  def test_model_class_responds_to_last_bang
713
    assert_equal topics(:fifth), Topic.last!
714
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
715 716 717 718 719
      Topic.delete_all
      Topic.last!
    end
  end

720
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
721 722 723
    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 }
724 725 726 727 728 729
  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

730 731 732
  def test_last_with_integer_and_order_should_use_sql_limit
    relation = Topic.order("title")
    assert_queries(1) { relation.last(5) }
733
    assert_not_predicate relation, :loaded?
734 735
  end

736 737 738
  def test_last_with_integer_and_reorder_should_use_sql_limit
    relation = Topic.reorder("title")
    assert_queries(1) { relation.last(5) }
739
    assert_not_predicate relation, :loaded?
740 741 742
  end

  def test_last_on_loaded_relation_should_not_use_sql
743
    relation = Topic.limit(10).load
744 745 746 747 748 749 750
    assert_no_queries do
      relation.last
      relation.last(2)
    end
  end

  def test_last_with_irreversible_order
751
    assert_raises(ActiveRecord::IrreversibleOrderError) do
752
      Topic.order(Arel.sql("coalesce(author_name, title)")).last
753
    end
754
  end
755 756

  def test_last_on_relation_with_limit_and_offset
757
    post = posts("sti_comments")
758 759 760 761 762 763

    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)

764 765 766 767
    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)

768 769 770 771
    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)
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  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)
790 791
  end

792 793 794 795 796 797
  def test_first_have_determined_order_by_default
    expected = [companies(:second_client), companies(:another_client)]
    clients = Client.where(name: expected.map(&:name))

    assert_equal expected, clients.first(2)
    assert_equal expected, clients.limit(5).first(2)
798
    assert_equal expected, clients.order(nil).first(2)
799 800
  end

801 802 803 804 805 806
  def test_implicit_order_column_is_configurable
    old_implicit_order_column = Topic.implicit_order_column
    Topic.implicit_order_column = "title"

    assert_equal topics(:fifth), Topic.first
    assert_equal topics(:third), Topic.last
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823

    c = Topic.connection
    assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.title"))} DESC, #{Regexp.escape(c.quote_table_name("topics.id"))} DESC LIMIT/i) {
      Topic.last
    }
  ensure
    Topic.implicit_order_column = old_implicit_order_column
  end

  def test_implicit_order_set_to_primary_key
    old_implicit_order_column = Topic.implicit_order_column
    Topic.implicit_order_column = "id"

    c = Topic.connection
    assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("topics.id"))} DESC LIMIT/i) {
      Topic.last
    }
824 825 826 827
  ensure
    Topic.implicit_order_column = old_implicit_order_column
  end

828 829 830 831 832 833 834 835 836 837 838 839
  def test_implicit_order_for_model_without_primary_key
    old_implicit_order_column = NonPrimaryKey.implicit_order_column
    NonPrimaryKey.implicit_order_column = "created_at"

    c = NonPrimaryKey.connection
    assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("non_primary_keys.created_at"))} DESC LIMIT/i) {
      NonPrimaryKey.last
    }
  ensure
    NonPrimaryKey.implicit_order_column = old_implicit_order_column
  end

840 841
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
842 843 844 845
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
846
  def test_unexisting_record_exception_handling
847
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
848 849
      Topic.find(1).parent
    }
850

851
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
852
  end
853

854
  def test_find_only_some_columns
855
    topic = Topic.select("author_name").find(1)
856 857
    assert_raise(ActiveModel::MissingAttributeError) { topic.title }
    assert_raise(ActiveModel::MissingAttributeError) { topic.title? }
858
    assert_nil topic.read_attribute("title")
859
    assert_equal "David", topic.author_name
860 861
    assert_not topic.attribute_present?("title")
    assert_not topic.attribute_present?(:title)
862
    assert topic.attribute_present?("author_name")
863
    assert_respond_to topic, "author_name"
864
  end
J
Jeremy Kemper 已提交
865

866
  def test_find_on_array_conditions
867 868
    assert Topic.where(["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
869
  end
870

871
  def test_find_on_hash_conditions
872 873
    assert Topic.where(approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
874
  end
875

876
  def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
877 878
    assert Topic.where("topics.approved" => false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => true).find(1) }
879 880
  end

881 882 883 884 885
  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

886
  def test_find_on_hash_conditions_with_hashed_table_name
887 888
    assert Topic.where(topics: { approved: false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
889 890
  end

891
  def test_find_on_combined_explicit_and_hashed_table_names
892 893 894
    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) }
895 896
  end

897
  def test_find_with_hash_conditions_on_joined_table
898
    firms = Firm.joins(:account).where(accounts: { credit_limit: 50 })
899 900 901 902 903
    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
904
    firms = DependentFirm.joins(:account).where(name: "RailsCore", accounts: { credit_limit: 55..60 })
905 906 907 908
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

909 910
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
911
    assert Customer.where("customers.name" => david.name, :address => david.address).find(david.id)
912
    assert_raise(ActiveRecord::RecordNotFound) {
913
      Customer.where("customers.name" => david.name + "1", :address => david.address).find(david.id)
914 915 916
    }
  end

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

921
  def test_find_on_hash_conditions_with_range
922
    assert_equal [1, 2], Topic.where(id: 1..2).to_a.map(&:id).sort
923
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
924
  end
925

926
  def test_find_on_hash_conditions_with_end_exclusive_range
927 928
    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
929
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
930 931
  end

932
  def test_find_on_hash_conditions_with_multiple_ranges
933
    assert_equal [1, 2, 3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
934
    assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
935
  end
936

937
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
938
    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
939 940
  end

941
  def test_find_on_hash_conditions_with_array_of_ranges
942
    assert_equal [1, 2, 6, 7, 8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
943 944
  end

945 946 947 948 949 950 951 952 953
  def test_find_on_hash_conditions_with_open_ended_range
    assert_equal [1, 2, 3], Comment.where(id: Float::INFINITY..3).to_a.map(&:id).sort
  end

  def test_find_on_hash_conditions_with_numeric_range_for_string
    topic = Topic.create!(title: "12 Factor App")
    assert_equal [topic], Topic.where(title: 10..2).to_a
  end

954
  def test_find_on_multiple_hash_conditions
955 956 957
    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) }
958
  end
959

960
  def test_condition_interpolation
961
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
962 963 964
    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
965 966
  end

967
  def test_condition_array_interpolation
968 969 970 971
    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 已提交
972
  end
973

974
  def test_condition_hash_interpolation
975 976 977
    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
978
  end
979

980
  def test_hash_condition_find_malformed
981
    assert_raise(ActiveRecord::StatementInvalid) {
982
      Company.where(id: 2, dhh: true).first
983 984
    }
  end
985

986 987
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
988
    assert Company.where(name: "Ain't noth'n like' \#stuff").first
989 990 991
  end

  def test_hash_condition_find_with_array
992 993 994
    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
995 996 997
  end

  def test_hash_condition_find_with_nil
998
    topic = Topic.where(last_read: nil).first
999 1000
    assert_not_nil topic
    assert_nil topic.last_read
1001
  end
D
David Heinemeier Hansson 已提交
1002

1003 1004 1005
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
1006
    found_customer = Customer.where(balance: balance).first
1007 1008 1009
    assert_equal customers(:david), found_customer
  end

1010
  def test_hash_condition_find_with_aggregate_having_three_mappings_array
1011 1012
    david_address = customers(:david).address
    zaphod_address = customers(:zaphod).address
1013
    barney_address = customers(:barney).address
1014 1015
    assert_kind_of Address, david_address
    assert_kind_of Address, zaphod_address
1016 1017
    found_customers = Customer.where(address: [david_address, zaphod_address, barney_address])
    assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id)
1018 1019 1020 1021 1022 1023 1024 1025
  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])
1026
    assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
1027
    assert_equal Customer.where(balance: [david_balance.amount, zaphod_balance.amount]).to_sql, found_customers.to_sql
1028 1029
  end

1030 1031 1032
  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
1033
    found_customer = Customer.where(gps_location: gps_location).first
1034 1035 1036 1037 1038 1039
    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
1040
    found_customer = Customer.where(balance: balance.amount).first
1041 1042 1043 1044 1045 1046
    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
1047
    found_customer = Customer.where(gps_location: gps_location.gps_location).first
1048 1049 1050 1051 1052 1053
    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
1054
    found_customer = Customer.where(address: address).first
1055 1056 1057 1058 1059 1060
    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
1061
    found_customer = Customer.where(address: address, name: customers(:david).name).first
1062 1063 1064
    assert_equal customers(:david), found_customer
  end

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
  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

1083
  def test_condition_utc_time_interpolation_with_default_timezone_local
1084
    with_env_tz "America/New_York" do
1085
      with_timezone_config default: :local do
1086
        topic = Topic.first
1087
        assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getutc]).first
1088 1089 1090 1091 1092
      end
    end
  end

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
1093
    with_env_tz "America/New_York" do
1094
      with_timezone_config default: :local do
1095
        topic = Topic.first
1096
        assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
1097 1098 1099 1100 1101
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
1102
    with_env_tz "America/New_York" do
1103
      with_timezone_config default: :utc do
1104
        topic = Topic.first
1105
        assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getlocal]).first
1106 1107 1108 1109 1110
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
1111
    with_env_tz "America/New_York" do
1112
      with_timezone_config default: :utc do
1113
        topic = Topic.first
1114
        assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
1115 1116 1117 1118
      end
    end
  end

D
David Heinemeier Hansson 已提交
1119
  def test_bind_variables
1120 1121 1122 1123
    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
1124
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1125
      Company.where(["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
1126
    }
1127
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1128
      Company.where(["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
1129 1130
    }
  end
1131

D
David Heinemeier Hansson 已提交
1132
  def test_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1133 1134
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = ?", "37signals' go'es against"]).first
D
David Heinemeier Hansson 已提交
1135 1136 1137
  end

  def test_named_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1138 1139
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first
D
David Heinemeier Hansson 已提交
1140 1141 1142
  end

  def test_named_bind_variables
1143 1144 1145 1146
    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 已提交
1147 1148 1149 1150 1151 1152 1153 1154 1155
  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
1156
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
1157 1158
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
1159

1160 1161
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
1162 1163 1164
    assert_raises_with_message(ActiveRecord::RecordNotFound, "Couldn't find Topic") do
      Topic.find_by_title!("The First Topic!")
    end
1165 1166
  end

1167
  def test_find_by_on_attribute_that_is_a_reserved_word
1168
    dog_alias = "Dog"
1169 1170 1171 1172 1173
    dog = Dog.create(alias: dog_alias)

    assert_equal dog, Dog.find_by_alias(dog_alias)
  end

1174 1175 1176 1177 1178
  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 已提交
1179
  def test_find_by_one_attribute_bang_with_blank_defined
1180 1181
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
1182 1183
  end

1184
  def test_find_by_one_attribute_with_conditions
1185
    assert_equal accounts(:rails_core_account), Account.where("firm_id = ?", 6).find_by_credit_limit(50)
1186 1187
  end

1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
  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

1222 1223
  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
1224
    Account.singleton_class.remove_method :find_by_credit_limit if Account.public_methods.include?(:find_by_credit_limit)
1225 1226
    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
1227 1228
  end

1229
  def test_find_by_one_attribute_with_several_options
1230
    assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50)
1231
  end
1232

D
David Heinemeier Hansson 已提交
1233
  def test_find_by_one_missing_attribute
1234
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
1235
  end
1236

1237
  def test_find_by_invalid_method_syntax
1238 1239 1240 1241
    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") }
1242
  end
D
David Heinemeier Hansson 已提交
1243 1244

  def test_find_by_two_attributes
1245
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
1246 1247 1248
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

1249 1250 1251 1252
  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 已提交
1253 1254 1255 1256 1257
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
1258

D
David Heinemeier Hansson 已提交
1259 1260 1261 1262 1263 1264
  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
1265
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
1266 1267
  end

1268
  def test_joins_dont_clobber_id
1269
    first = Firm.
1270 1271
      joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
      where("companies.id = 1").first
1272 1273 1274
    assert_equal 1, first.id
  end

1275
  def test_joins_with_string_array
1276 1277 1278 1279
    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'"
            ])
1280 1281 1282
    assert_equal 1, person_with_reader_and_post.size
  end

1283 1284
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
1285
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1, 2, 3])
1286 1287
    end
  end
1288

1289
  def test_find_ignores_previously_inserted_record
1290
    Post.create!(title: "test", body: "it out")
J
Jon Leighton 已提交
1291
    assert_equal [], Post.where(id: nil)
1292 1293
  end

1294 1295 1296 1297 1298
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
1299
    assert_equal [], Post.where("id in (?)", [])
1300 1301 1302
  end

  def test_find_by_records
1303 1304 1305
    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")
1306 1307
  end

1308 1309 1310 1311 1312 1313 1314 1315 1316
  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
1317 1318
    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")
1319 1320
  end

1321 1322
  def test_select_rows
    assert_equal(
1323
      [["1", "1", nil, "37signals"],
1324 1325
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
1326
      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? } })
1327
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
1328
      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? } }
1329 1330
  end

1331
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
R
Fix...  
Ryuta Kamizono 已提交
1332 1333
    assert_equal 2, Post.includes(authors: :author_address).
      where.not(author_addresses: { id: nil }).
B
Ben Toews 已提交
1334
      order("author_addresses.id DESC").limit(2).to_a.size
1335

1336
    assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
R
Fix...  
Ryuta Kamizono 已提交
1337
      where.not(author_addresses_authors: { id: nil }).
B
Ben Toews 已提交
1338
      order("author_addresses_authors.id DESC").limit(3).to_a.size
1339 1340
  end

1341 1342 1343 1344 1345
  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

1346
  def test_find_with_nil_inside_set_passed_for_one_attribute
1347 1348
    client_of = Company.
      where(client_of: [2, 1, nil],
1349
            name: ["37signals", "Summit", "Microsoft"]).
1350
      order("client_of DESC").
1351
      map(&:client_of)
1352

1353
    assert_includes client_of, nil
1354
    assert_equal [2, 1].sort, client_of.compact.sort
1355 1356
  end

1357
  def test_find_with_nil_inside_set_passed_for_attribute
1358 1359
    client_of = Company.
      where(client_of: [nil]).
1360
      order("client_of DESC").
1361
      map(&:client_of)
1362 1363 1364 1365

    assert_equal [], client_of.compact
  end

1366
  def test_with_limiting_with_custom_select
1367
    posts = Post.references(:authors).merge(
1368
      includes: :author, select: 'posts.*, authors.id as "author_id"',
1369
      limit: 3, order: "posts.id"
1370
    ).to_a
1371 1372
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
1373
  end
1374

1375
  def test_eager_load_for_no_has_many_with_limit_and_joins_for_has_many
1376
    relation = Post.eager_load(:author).joins(comments: :post)
1377
    assert_equal 5, relation.to_a.size
1378 1379 1380 1381 1382 1383 1384
    assert_equal 5, relation.limit(5).to_a.size
  end

  def test_eager_load_for_no_has_many_with_limit_and_left_joins_for_has_many
    relation = Post.eager_load(:author).left_joins(comments: :post)
    assert_equal 11, relation.to_a.size
    assert_equal 11, relation.limit(11).to_a.size
1385 1386
  end

1387 1388 1389 1390 1391 1392 1393 1394 1395 1396
  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

1397
  def test_find_one_message_with_custom_primary_key
1398 1399 1400
    table_with_custom_primary_key do |model|
      model.primary_key = :name
      e = assert_raises(ActiveRecord::RecordNotFound) do
1401
        model.find "Hello World!"
1402
      end
1403
      assert_equal "Couldn't find MercedesCar with 'name'=Hello World!", e.message
1404 1405 1406 1407 1408 1409 1410
    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
1411
        model.find "Hello", "World!"
1412
      end
1413
      assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2).", e.message
1414 1415 1416
    end
  end

1417 1418 1419 1420 1421 1422
  def test_find_without_primary_key
    assert_raises(ActiveRecord::UnknownPrimaryKey) do
      Matey.find(1)
    end
  end

1423
  def test_finder_with_offset_string
1424
    assert_nothing_raised { Topic.offset("3").to_a }
1425 1426
  end

1427 1428 1429 1430 1431 1432 1433 1434 1435
  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
1436
    assert_equal posts(:eager_other), Post.find_by("id = ?", posts(:eager_other).id)
1437 1438
  end

1439 1440 1441 1442
  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

1443
  test "find_by returns nil if the record is missing" do
1444
    assert_nil Post.find_by("1 = 0")
1445 1446
  end

1447 1448
  test "find_by with associations" do
    assert_equal authors(:david), Post.find_by(author: authors(:david)).author
1449
    assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author
1450 1451
  end

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

1456 1457 1458 1459 1460 1461 1462 1463 1464
  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
1465
    assert_equal posts(:eager_other), Post.find_by!("id = ?", posts(:eager_other).id)
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477
  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

1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497
  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

1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513
  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
1514
      assert_queries(1) do
1515 1516 1517 1518
        Topic.eager_load(:replies).limit(1).exists?
        Topic.eager_load(:replies).limit(1).exists?
      end

1519
      assert_queries(2) do
1520 1521 1522 1523 1524 1525
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
      end
    end
  end

1526
  private
1527 1528 1529
    def table_with_custom_primary_key
      yield(Class.new(Toy) do
        def self.name
1530
          "MercedesCar"
1531 1532 1533
        end
      end)
    end
1534 1535 1536 1537 1538

    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 已提交
1539
end