finder_test.rb 56.6 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 48 49 50 51
  def test_find_with_hash_parameter
    assert_raises(ActiveRecord::RecordNotFound) { Post.find(foo: "bar") }
    assert_raises(ActiveRecord::RecordNotFound) { Post.find(foo: "bar", bar: "baz") }
  end

52
  def test_find_with_proc_parameter_and_block
S
Sean Griffin 已提交
53
    exception = assert_raises(RuntimeError) do
54 55
      Topic.all.find(-> { raise "should happen" }) { |e| e.title == "non-existing-title" }
    end
S
Sean Griffin 已提交
56
    assert_equal "should happen", exception.message
57

58
    assert_nothing_raised do
59 60 61 62
      Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
    end
  end

63
  def test_find_with_ids_returning_ordered
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
    records = Topic.find("4", "2", "5")
80 81 82
    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
83 84 85 86
  end

  def test_find_with_ids_and_order_clause
    # The order clause takes precedence over the informed ids
87
    records = Topic.order(:author_name).find([5, 3, 1])
88 89 90
    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
91

92
    records = Topic.order(:id).find([5, 3, 1])
93 94 95
    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
96 97
  end

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

  def test_find_with_ids_and_limit
107
    records = Topic.limit(3).find([3, 2, 5, 1, 4])
108
    assert_equal 3, records.size
109 110 111
    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
112 113
  end

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

124
  def test_find_with_ids_and_offset
125
    records = Topic.offset(2).find([3, 2, 5, 1, 4])
126
    assert_equal 3, records.size
127 128 129
    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
130 131
  end

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
  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

147
  def test_find_passing_active_record_object_is_not_permitted
148
    assert_raises(ArgumentError) do
149 150 151 152
      Topic.find(Topic.last)
    end
  end

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

163 164 165
  # find should handle strings that come from URLs
  # (example: Category.find(params[:id]))
  def test_find_with_string
166
    assert_equal(Topic.find(1).title, Topic.find("1").title)
167
  end
168

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

    assert_equal false, Topic.exists?(45)
179
    assert_equal false, Topic.exists?(9999999999999999999999999999999)
180
    assert_equal false, Topic.exists?(Topic.new.id)
181

182
    assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
183 184
  end

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  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

211
  def test_exists_with_polymorphic_relation
212 213
    post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")])
    relation = Post.tagged_with_comment("tagging comment")
214

215 216
    assert_equal true, relation.exists?(title: ["Post"])
    assert_equal true, relation.exists?(["title LIKE ?", "Post%"])
217 218 219 220 221 222 223
    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

224 225 226 227 228 229 230 231 232 233 234
  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

235
  def test_exists_with_strong_parameters
236
    assert_equal false, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
237 238 239

    Subscriber.create!(nick: "foo")

240
    assert_equal true, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
241 242

    assert_raises(ActiveModel::ForbiddenAttributesError) do
243
      Subscriber.exists?(ProtectedParams.new(nick: "foo"))
244
    end
245 246
  end

247
  def test_exists_passing_active_record_object_is_not_permitted
248
    assert_raises(ArgumentError) do
249 250 251 252
      Topic.exists?(Topic.new)
    end
  end

253
  def test_exists_does_not_select_columns_without_alias
254 255
    c = Topic.connection
    assert_sql(/SELECT 1 AS one FROM #{Regexp.escape(c.quote_table_name("topics"))}/i) do
256 257 258 259
      Topic.exists?
    end
  end

260
  def test_exists_returns_true_with_one_record_and_no_args
261
    assert_equal true, Topic.exists?
262
  end
263

E
Egor Lynko 已提交
264
  def test_exists_returns_false_with_false_arg
265
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
266 267
  end

268 269 270
  # 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
271 272 273 274 275
    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?
276 277
  end

278 279 280 281
  def test_exists_with_empty_hash_arg
    assert_equal true, Topic.exists?({})
  end

282 283 284 285 286 287
  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 已提交
288 289
    assert Post.select(:body).distinct.offset(4).exists?
    assert_not Post.select(:body).distinct.offset(5).exists?
290 291
  end

292 293 294 295 296
  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 已提交
297 298 299
  # 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
300
    assert_equal true, Topic.order(:id).distinct.exists?
301 302
  end

303 304
  # Ensure +exists?+ runs without an error by excluding order value.
  def test_exists_with_order
305
    assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
306 307
  end

308 309 310 311 312 313 314 315 316 317 318
  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 已提交
319
  def test_exists_with_joins
B
Ben Toews 已提交
320
    assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
R
Ryuta Kamizono 已提交
321 322 323
  end

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

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

331
  def test_exists_with_includes_limit_and_empty_result
332 333
    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? }
334 335
  end

336 337
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
338 339 340
    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? }
341 342 343 344
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
345 346 347
    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? }
348 349
  end

350
  def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
351 352
    ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 })
    assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? }
353 354
  end

355
  def test_exists_with_empty_table_and_no_args_given
356
    Topic.delete_all
357
    assert_equal false, Topic.exists?
358
  end
359

360 361
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
362
    assert_equal true, Customer.exists?(address: existing_address)
363 364 365 366
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
R
Ryuta Kamizono 已提交
367 368 369
    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))
370 371
  end

372
  def test_exists_does_not_instantiate_records
373 374 375
    assert_not_called(Developer, :instantiate) do
      Developer.exists?
    end
376 377
  end

D
David Heinemeier Hansson 已提交
378 379 380 381
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
382

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

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

404
  def test_find_with_large_number
R
Ryuta Kamizono 已提交
405
    assert_queries(0) do
406 407
      assert_raises(ActiveRecord::RecordNotFound) { Topic.find("9999999999999999999999999999999") }
    end
408 409 410
  end

  def test_find_by_with_large_number
R
Ryuta Kamizono 已提交
411
    assert_queries(0) do
412 413
      assert_nil Topic.find_by(id: "9999999999999999999999999999999")
    end
414 415 416
  end

  def test_find_by_id_with_large_number
R
Ryuta Kamizono 已提交
417
    assert_queries(0) do
418 419
      assert_nil Topic.find_by_id("9999999999999999999999999999999")
    end
420 421
  end

422
  def test_find_on_relation_with_large_number
423 424 425
    assert_raises(ActiveRecord::RecordNotFound) do
      Topic.where("1=1").find(9999999999999999999999999999999)
    end
426
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find(1)
427 428 429
  end

  def test_find_by_on_relation_with_large_number
430
    assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
431
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by(id: 1)
432 433 434 435
  end

  def test_find_by_bang_on_relation_with_large_number
    assert_raises(ActiveRecord::RecordNotFound) do
436
      Topic.where("1=1").find_by!(id: 9999999999999999999999999999999)
437
    end
438
    assert_equal topics(:first), Topic.where(id: [1, 9999999999999999999999999999999]).find_by!(id: 1)
439 440
  end

441
  def test_find_an_empty_array
442 443 444 445
    empty_array = []
    result = Topic.find(empty_array)
    assert_equal [], result
    assert_not_same empty_array, result
446 447
  end

448 449 450 451
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
452
  def test_find_by_ids_missing_one
453
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
454
  end
455

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

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

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

D
David Heinemeier Hansson 已提交
473
    assert_equal(1, topics.size)
474
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
475
  end
476

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

482
  def test_find_by_association_subquery
483 484 485
    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))
486 487
  end

488
  def test_find_by_and_where_consistency_with_active_record_instance
489 490
    firm = companies(:first_firm)
    assert_equal Account.where(firm_id: firm).take, Account.find_by(firm_id: firm)
491 492
  end

493 494 495 496 497
  def test_find_by_with_alias
    account = accounts(:last_account)
    assert_equal account, Account.find_by(available_credit: account.available_credit)
  end

498
  def test_take
499
    assert_equal topics(:first), Topic.where("title = 'The First Topic'").take
500 501 502 503 504 505 506 507 508 509 510 511 512
  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
513
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
514 515 516 517
      Topic.where("title = 'This title does not exist'").take!
    end
  end

518
  def test_first
519
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
520
  end
521

522
  def test_first_failing
523
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
524
  end
D
David Heinemeier Hansson 已提交
525

526 527 528 529 530 531 532
  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
533
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
534 535 536 537
      Topic.where("title = 'This title does not exist'").first!
    end
  end

538 539 540 541
  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
542
    assert_equal expected, Topic.limit(5).first
543
    assert_equal expected, Topic.order(nil).first
544 545
  end

546
  def test_model_class_responds_to_first_bang
547 548
    assert Topic.first!
    Topic.delete_all
549
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
550 551 552 553
      Topic.first!
    end
  end

554 555 556 557 558 559 560 561 562 563 564 565
  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
566
    assert_equal expected, Topic.limit(5).second
567
    assert_equal expected, Topic.order(nil).second
568 569 570 571 572
  end

  def test_model_class_responds_to_second_bang
    assert Topic.second!
    Topic.delete_all
573
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
      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
590
    assert_equal expected, Topic.limit(5).third
591
    assert_equal expected, Topic.order(nil).third
592 593 594 595 596
  end

  def test_model_class_responds_to_third_bang
    assert Topic.third!
    Topic.delete_all
597
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
      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
614
    assert_equal expected, Topic.limit(5).fourth
615
    assert_equal expected, Topic.order(nil).fourth
616 617 618 619 620
  end

  def test_model_class_responds_to_fourth_bang
    assert Topic.fourth!
    Topic.delete_all
621
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
      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
638
    assert_equal expected, Topic.limit(5).fifth
639
    assert_equal expected, Topic.order(nil).fifth
640 641 642 643 644
  end

  def test_model_class_responds_to_fifth_bang
    assert Topic.fifth!
    Topic.delete_all
645
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
646 647 648 649
      Topic.fifth!
    end
  end

650 651
  def test_second_to_last
    assert_equal topics(:fourth).title, Topic.second_to_last.title
652 653 654

    # test with offset
    assert_equal topics(:fourth), Topic.offset(1).second_to_last
655 656
    assert_equal topics(:fourth), Topic.offset(2).second_to_last
    assert_equal topics(:fourth), Topic.offset(3).second_to_last
657 658
    assert_nil Topic.offset(4).second_to_last
    assert_nil Topic.offset(5).second_to_last
659

660
    # test with limit
661
    assert_nil Topic.limit(1).second
662
    assert_nil Topic.limit(1).second_to_last
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
  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
681 682 683

    # test with offset
    assert_equal topics(:third), Topic.offset(1).third_to_last
684
    assert_equal topics(:third), Topic.offset(2).third_to_last
685 686 687
    assert_nil Topic.offset(3).third_to_last
    assert_nil Topic.offset(4).third_to_last
    assert_nil Topic.offset(5).third_to_last
688 689

    # test with limit
690
    assert_nil Topic.limit(1).third
691
    assert_nil Topic.limit(1).third_to_last
692
    assert_nil Topic.limit(2).third
693
    assert_nil Topic.limit(2).third_to_last
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
  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

710 711 712 713 714 715 716
  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
717
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
718 719 720 721
      Topic.where("title = 'This title does not exist'").last!
    end
  end

722
  def test_model_class_responds_to_last_bang
723
    assert_equal topics(:fifth), Topic.last!
724
    assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
725 726 727 728 729
      Topic.delete_all
      Topic.last!
    end
  end

730
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
731 732 733
    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 }
734 735 736 737 738 739
  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

740 741 742
  def test_last_with_integer_and_order_should_use_sql_limit
    relation = Topic.order("title")
    assert_queries(1) { relation.last(5) }
743
    assert_not_predicate relation, :loaded?
744 745
  end

746 747 748
  def test_last_with_integer_and_reorder_should_use_sql_limit
    relation = Topic.reorder("title")
    assert_queries(1) { relation.last(5) }
749
    assert_not_predicate relation, :loaded?
750 751 752
  end

  def test_last_on_loaded_relation_should_not_use_sql
753
    relation = Topic.limit(10).load
754 755 756 757 758 759 760
    assert_no_queries do
      relation.last
      relation.last(2)
    end
  end

  def test_last_with_irreversible_order
761
    assert_raises(ActiveRecord::IrreversibleOrderError) do
762
      Topic.order(Arel.sql("coalesce(author_name, title)")).last
763
    end
764
  end
765 766

  def test_last_on_relation_with_limit_and_offset
767
    post = posts("sti_comments")
768 769 770 771 772 773

    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)

774 775 776 777
    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)

778 779 780 781
    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)
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
  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)
800 801
  end

802 803 804 805 806 807
  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)
808
    assert_equal expected, clients.order(nil).first(2)
809 810
  end

811 812 813 814 815 816
  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
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833

    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
    }
834 835 836 837
  ensure
    Topic.implicit_order_column = old_implicit_order_column
  end

838 839 840 841 842 843 844 845 846 847 848 849
  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

850 851
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
852 853 854 855
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
856
  def test_unexisting_record_exception_handling
857
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
858 859
      Topic.find(1).parent
    }
860

861
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
862
  end
863

864
  def test_find_only_some_columns
865
    topic = Topic.select("author_name").find(1)
866 867
    assert_raise(ActiveModel::MissingAttributeError) { topic.title }
    assert_raise(ActiveModel::MissingAttributeError) { topic.title? }
868
    assert_nil topic.read_attribute("title")
869
    assert_equal "David", topic.author_name
870 871
    assert_not topic.attribute_present?("title")
    assert_not topic.attribute_present?(:title)
872
    assert topic.attribute_present?("author_name")
873
    assert_respond_to topic, "author_name"
874
  end
J
Jeremy Kemper 已提交
875

876
  def test_find_on_array_conditions
877 878
    assert Topic.where(["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
879
  end
880

881
  def test_find_on_hash_conditions
882 883
    assert Topic.where(approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
884
  end
885

886
  def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
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 892 893 894 895
  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

896
  def test_find_on_hash_conditions_with_hashed_table_name
897 898
    assert Topic.where(topics: { approved: false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
899 900
  end

901
  def test_find_on_combined_explicit_and_hashed_table_names
902 903 904
    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) }
905 906
  end

907
  def test_find_with_hash_conditions_on_joined_table
908
    firms = Firm.joins(:account).where(accounts: { credit_limit: 50 })
909 910 911 912 913
    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
914
    firms = DependentFirm.joins(:account).where(name: "RailsCore", accounts: { credit_limit: 55..60 })
915 916 917 918
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

919 920
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
921
    assert Customer.where("customers.name" => david.name, :address => david.address).find(david.id)
922
    assert_raise(ActiveRecord::RecordNotFound) {
923
      Customer.where("customers.name" => david.name + "1", :address => david.address).find(david.id)
924 925 926
    }
  end

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

931
  def test_find_on_hash_conditions_with_range
932
    assert_equal [1, 2], Topic.where(id: 1..2).to_a.map(&:id).sort
933
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
934
  end
935

936
  def test_find_on_hash_conditions_with_end_exclusive_range
937 938
    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
939
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
940 941
  end

942
  def test_find_on_hash_conditions_with_multiple_ranges
943
    assert_equal [1, 2, 3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
944
    assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
945
  end
946

947
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
948
    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
949 950
  end

951
  def test_find_on_hash_conditions_with_array_of_ranges
952
    assert_equal [1, 2, 6, 7, 8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
953 954
  end

955 956 957 958 959 960 961 962 963
  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

964
  def test_find_on_multiple_hash_conditions
965 966 967
    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) }
968
  end
969

970
  def test_condition_interpolation
971
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
972 973 974
    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
975 976
  end

977
  def test_condition_array_interpolation
978 979 980 981
    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 已提交
982
  end
983

984
  def test_condition_hash_interpolation
985 986 987
    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
988
  end
989

990
  def test_hash_condition_find_malformed
991
    assert_raise(ActiveRecord::StatementInvalid) {
992
      Company.where(id: 2, dhh: true).first
993 994
    }
  end
995

996 997
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
998
    assert Company.where(name: "Ain't noth'n like' \#stuff").first
999 1000 1001
  end

  def test_hash_condition_find_with_array
1002 1003 1004
    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
1005 1006 1007
  end

  def test_hash_condition_find_with_nil
1008
    topic = Topic.where(last_read: nil).first
1009 1010
    assert_not_nil topic
    assert_nil topic.last_read
1011
  end
D
David Heinemeier Hansson 已提交
1012

1013 1014 1015
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
1016
    found_customer = Customer.where(balance: balance).first
1017 1018 1019
    assert_equal customers(:david), found_customer
  end

1020
  def test_hash_condition_find_with_aggregate_having_three_mappings_array
1021 1022
    david_address = customers(:david).address
    zaphod_address = customers(:zaphod).address
1023
    barney_address = customers(:barney).address
1024 1025
    assert_kind_of Address, david_address
    assert_kind_of Address, zaphod_address
1026 1027
    found_customers = Customer.where(address: [david_address, zaphod_address, barney_address])
    assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id)
1028 1029 1030 1031 1032 1033 1034 1035
  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])
1036
    assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
1037
    assert_equal Customer.where(balance: [david_balance.amount, zaphod_balance.amount]).to_sql, found_customers.to_sql
1038 1039
  end

1040 1041 1042
  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
1043
    found_customer = Customer.where(gps_location: gps_location).first
1044 1045 1046 1047 1048 1049
    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
1050
    found_customer = Customer.where(balance: balance.amount).first
1051 1052 1053 1054 1055 1056
    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
1057
    found_customer = Customer.where(gps_location: gps_location.gps_location).first
1058 1059 1060 1061 1062 1063
    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
1064 1065 1066
    customers = Customer.where(address: address).order(:id)
    assert_equal [customers(:david)], customers
    assert_equal customers(:david, :mary), customers.unscope(where: [:address_city, :address_country])
1067 1068 1069 1070 1071
  end

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

1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
  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

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

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
1104
    with_env_tz "America/New_York" do
1105
      with_timezone_config default: :local do
1106
        topic = Topic.first
1107
        assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
1108 1109 1110 1111 1112
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
1113
    with_env_tz "America/New_York" do
1114
      with_timezone_config default: :utc do
1115
        topic = Topic.first
1116
        assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getlocal]).first
1117 1118 1119 1120 1121
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
1122
    with_env_tz "America/New_York" do
1123
      with_timezone_config default: :utc do
1124
        topic = Topic.first
1125
        assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
1126 1127 1128 1129
      end
    end
  end

D
David Heinemeier Hansson 已提交
1130
  def test_bind_variables
1131 1132 1133 1134
    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
1135
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1136
      Company.where(["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
1137
    }
1138
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
1139
      Company.where(["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
1140 1141
    }
  end
1142

D
David Heinemeier Hansson 已提交
1143
  def test_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1144 1145
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = ?", "37signals' go'es against"]).first
D
David Heinemeier Hansson 已提交
1146 1147 1148
  end

  def test_named_bind_variables_with_quotes
B
Benjamin Fleischer 已提交
1149 1150
    Company.create("name" => "37signals' go'es against")
    assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first
D
David Heinemeier Hansson 已提交
1151 1152 1153
  end

  def test_named_bind_variables
1154 1155 1156 1157
    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 已提交
1158 1159 1160 1161 1162 1163 1164 1165 1166
  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
1167
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
1168 1169
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
1170

1171 1172
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
1173 1174 1175
    assert_raises_with_message(ActiveRecord::RecordNotFound, "Couldn't find Topic") do
      Topic.find_by_title!("The First Topic!")
    end
1176 1177
  end

1178
  def test_find_by_on_attribute_that_is_a_reserved_word
1179
    dog_alias = "Dog"
1180 1181 1182 1183 1184
    dog = Dog.create(alias: dog_alias)

    assert_equal dog, Dog.find_by_alias(dog_alias)
  end

1185 1186 1187 1188 1189
  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 已提交
1190
  def test_find_by_one_attribute_bang_with_blank_defined
1191 1192
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
1193 1194
  end

1195
  def test_find_by_one_attribute_with_conditions
1196
    assert_equal accounts(:rails_core_account), Account.where("firm_id = ?", 6).find_by_credit_limit(50)
1197 1198
  end

1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232
  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

1233 1234
  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
1235
    Account.singleton_class.remove_method :find_by_credit_limit if Account.public_methods.include?(:find_by_credit_limit)
1236 1237
    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
1238 1239
  end

1240
  def test_find_by_one_attribute_with_several_options
1241
    assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50)
1242
  end
1243

D
David Heinemeier Hansson 已提交
1244
  def test_find_by_one_missing_attribute
1245
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
1246
  end
1247

1248
  def test_find_by_invalid_method_syntax
1249 1250 1251 1252
    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") }
1253
  end
D
David Heinemeier Hansson 已提交
1254 1255

  def test_find_by_two_attributes
1256
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
1257 1258 1259
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

1260 1261 1262 1263
  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 已提交
1264 1265 1266 1267 1268
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
1269

D
David Heinemeier Hansson 已提交
1270 1271 1272 1273 1274 1275
  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
1276
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
1277 1278
  end

1279
  def test_joins_dont_clobber_id
1280
    first = Firm.
1281 1282
      joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
      where("companies.id = 1").first
1283 1284 1285
    assert_equal 1, first.id
  end

1286
  def test_joins_with_string_array
1287 1288 1289 1290
    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'"
            ])
1291 1292 1293
    assert_equal 1, person_with_reader_and_post.size
  end

1294 1295
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
1296
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1, 2, 3])
1297 1298
    end
  end
1299

1300
  def test_find_ignores_previously_inserted_record
1301
    Post.create!(title: "test", body: "it out")
J
Jon Leighton 已提交
1302
    assert_equal [], Post.where(id: nil)
1303 1304
  end

1305 1306 1307 1308 1309
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
1310
    assert_equal [], Post.where("id in (?)", [])
1311 1312 1313
  end

  def test_find_by_records
1314 1315 1316
    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")
1317 1318
  end

1319 1320 1321 1322 1323 1324 1325 1326 1327
  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
1328 1329
    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")
1330 1331
  end

1332 1333
  def test_select_rows
    assert_equal(
1334
      [["1", "1", nil, "37signals"],
1335 1336
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
1337
      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? } })
1338
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
1339
      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? } }
1340 1341
  end

1342
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
R
Fix...  
Ryuta Kamizono 已提交
1343 1344
    assert_equal 2, Post.includes(authors: :author_address).
      where.not(author_addresses: { id: nil }).
B
Ben Toews 已提交
1345
      order("author_addresses.id DESC").limit(2).to_a.size
1346

1347
    assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
R
Fix...  
Ryuta Kamizono 已提交
1348
      where.not(author_addresses_authors: { id: nil }).
B
Ben Toews 已提交
1349
      order("author_addresses_authors.id DESC").limit(3).to_a.size
1350 1351
  end

1352 1353 1354 1355 1356
  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

1357
  def test_find_with_nil_inside_set_passed_for_one_attribute
1358 1359
    client_of = Company.
      where(client_of: [2, 1, nil],
1360
            name: ["37signals", "Summit", "Microsoft"]).
1361
      order("client_of DESC").
1362
      map(&:client_of)
1363

1364
    assert_includes client_of, nil
1365
    assert_equal [2, 1].sort, client_of.compact.sort
1366 1367
  end

1368
  def test_find_with_nil_inside_set_passed_for_attribute
1369 1370
    client_of = Company.
      where(client_of: [nil]).
1371
      order("client_of DESC").
1372
      map(&:client_of)
1373 1374 1375 1376

    assert_equal [], client_of.compact
  end

1377
  def test_with_limiting_with_custom_select
1378
    posts = Post.references(:authors).merge(
1379
      includes: :author, select: 'posts.*, authors.id as "author_id"',
1380
      limit: 3, order: "posts.id"
1381
    ).to_a
1382
    assert_equal 3, posts.size
1383 1384 1385 1386 1387 1388 1389 1390
    assert_equal [1, 1, nil], posts.map(&:author_id)
  end

  def test_custom_select_takes_precedence_over_original_value
    posts = Post.select("UPPER(title) AS title")
    assert_equal "WELCOME TO THE WEBLOG", posts.first.title
    assert_equal "WELCOME TO THE WEBLOG", posts.preload(:comments).first.title
    assert_equal "WELCOME TO THE WEBLOG", posts.eager_load(:comments).first.title
1391
  end
1392

1393
  def test_eager_load_for_no_has_many_with_limit_and_joins_for_has_many
1394
    relation = Post.eager_load(:author).joins(comments: :post)
1395
    assert_equal 5, relation.to_a.size
1396 1397 1398 1399 1400 1401 1402
    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
1403 1404
  end

1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
  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

1415
  def test_find_one_message_with_custom_primary_key
1416 1417 1418
    table_with_custom_primary_key do |model|
      model.primary_key = :name
      e = assert_raises(ActiveRecord::RecordNotFound) do
1419
        model.find "Hello World!"
1420
      end
1421
      assert_equal "Couldn't find MercedesCar with 'name'=Hello World!", e.message
1422 1423 1424 1425 1426 1427 1428
    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
1429
        model.find "Hello", "World!"
1430
      end
1431
      assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2).", e.message
1432 1433 1434
    end
  end

1435 1436 1437 1438 1439 1440
  def test_find_without_primary_key
    assert_raises(ActiveRecord::UnknownPrimaryKey) do
      Matey.find(1)
    end
  end

1441
  def test_finder_with_offset_string
1442
    assert_nothing_raised { Topic.offset("3").to_a }
1443 1444
  end

1445 1446 1447 1448 1449 1450 1451 1452 1453
  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
1454
    assert_equal posts(:eager_other), Post.find_by("id = ?", posts(:eager_other).id)
1455 1456
  end

1457 1458 1459 1460
  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

1461
  test "find_by returns nil if the record is missing" do
1462
    assert_nil Post.find_by("1 = 0")
1463 1464
  end

1465 1466
  test "find_by with associations" do
    assert_equal authors(:david), Post.find_by(author: authors(:david)).author
1467
    assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author
1468 1469
  end

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

1474 1475 1476 1477 1478 1479 1480 1481 1482
  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
1483
    assert_equal posts(:eager_other), Post.find_by!("id = ?", posts(:eager_other).id)
1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495
  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

1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515
  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

1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531
  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
1532
      assert_queries(1) do
1533 1534 1535 1536
        Topic.eager_load(:replies).limit(1).exists?
        Topic.eager_load(:replies).limit(1).exists?
      end

1537
      assert_queries(2) do
1538 1539 1540 1541 1542 1543
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
        Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
      end
    end
  end

1544
  private
1545 1546 1547
    def table_with_custom_primary_key
      yield(Class.new(Toy) do
        def self.name
1548
          "MercedesCar"
1549 1550 1551
        end
      end)
    end
1552 1553 1554 1555 1556

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