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

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

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

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

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

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

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

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

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

61 62 63 64
    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
  end

  def test_exists_fails_when_parameter_has_invalid_type
65
    if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter)
66 67 68
      assert_raises ActiveRecord::StatementInvalid do
        Topic.exists?(("9"*53).to_i) # number that's bigger than int
      end
69 70 71
    else
      assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
    end
72

73
    if current_adapter?(:PostgreSQLAdapter)
74 75 76 77
      assert_raises ActiveRecord::StatementInvalid do
        Topic.exists?("foo")
      end
    else
78
      assert_equal false, Topic.exists?("foo")
79
    end
D
David Heinemeier Hansson 已提交
80
  end
81

82
  def test_exists_does_not_select_columns_without_alias
83
    assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
84 85 86 87
      Topic.exists?
    end
  end

88
  def test_exists_returns_true_with_one_record_and_no_args
89
    assert_equal true, Topic.exists?
90
  end
91

E
Egor Lynko 已提交
92
  def test_exists_returns_false_with_false_arg
93
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
94 95
  end

96 97 98
  # 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
99 100 101 102 103
    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?
104 105
  end

106 107
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
108
    assert_equal true, Topic.order(:id).distinct.exists?
109 110
  end

111
  def test_exists_with_includes_limit_and_empty_result
112 113
    assert_equal false, Topic.includes(:replies).limit(0).exists?
    assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists?
114 115
  end

116 117
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
118 119
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
120 121 122 123
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
124 125
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
126 127
  end

128
  def test_exists_with_empty_table_and_no_args_given
129
    Topic.delete_all
130
    assert_equal false, Topic.exists?
131
  end
132

133 134
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
135
    assert_equal true, Customer.exists?(:address => existing_address)
136 137 138 139
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
140
    assert_equal false, Customer.exists?(:address =>
141
      Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
142
    assert_equal false, Customer.exists?(:address =>
143
      Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
144
    assert_equal false, Customer.exists?(:address =>
145 146 147
      Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
  end

148 149 150 151 152
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
153 154 155 156
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
157

D
David Heinemeier Hansson 已提交
158
  def test_find_by_ids
159 160 161 162 163
    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
164 165
    assert_equal 2, Entrant.limit(2).find([1,3,2]).size
    assert_equal 1, Entrant.limit(3).offset(2).find([1,3,2]).size
166 167 168 169

    # 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 已提交
170
    devs = Developer.all
171
    last_devs = Developer.limit(3).offset(9).find devs.map(&:id)
172
    assert_equal 2, last_devs.size
D
David Heinemeier Hansson 已提交
173 174
  end

175 176 177 178
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

179 180 181 182
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
183
  def test_find_by_ids_missing_one
184
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
185
  end
186

187
  def test_find_with_group_and_sanitized_having_method
188
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
189 190 191 192 193
    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 已提交
194 195
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
196

D
David Heinemeier Hansson 已提交
197
    assert_equal(1, topics.size)
198
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
199
  end
200

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

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

208 209 210 211
  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
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
  def test_take
    assert_equal topics(:first), Topic.take
  end

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

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

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

233
  def test_first
234
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
235
  end
236

237
  def test_first_failing
238
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
239
  end
D
David Heinemeier Hansson 已提交
240

241 242 243 244 245 246 247 248 249 250 251 252
  def test_first_bang_present
    assert_nothing_raised do
      assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").first!
    end
  end

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

253 254 255 256 257 258
  def test_first_have_primary_key_order_by_default
    expected = topics(:first)
    expected.touch # PostgreSQL changes the default order if no order clause is used
    assert_equal expected, Topic.first
  end

259
  def test_model_class_responds_to_first_bang
260 261
    assert Topic.first!
    Topic.delete_all
262 263 264 265 266
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
  def test_second
    assert_equal topics(:second).title, Topic.second.title
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

355 356 357 358 359 360 361 362 363 364 365 366
  def test_last_bang_present
    assert_nothing_raised do
      assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
    end
  end

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

367
  def test_model_class_responds_to_last_bang
368
    assert_equal topics(:fifth), Topic.last!
369 370 371 372 373 374
    assert_raises ActiveRecord::RecordNotFound do
      Topic.delete_all
      Topic.last!
    end
  end

375 376
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
377 378
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
  end

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

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

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

397 398
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
399 400 401 402
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
403
  def test_unexisting_record_exception_handling
404
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
405 406
      Topic.find(1).parent
    }
407

408
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
409
  end
410

411
  def test_find_only_some_columns
412
    topic = Topic.select("author_name").find(1)
413
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
414
    assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
415
    assert_nil topic.read_attribute("title")
416 417
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
418
    assert !topic.attribute_present?(:title)
419
    assert topic.attribute_present?("author_name")
420
    assert_respond_to topic, "author_name"
421
  end
J
Jeremy Kemper 已提交
422

423
  def test_find_on_array_conditions
424 425
    assert Topic.where(["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
426
  end
427

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

433
  def test_find_on_hash_conditions_with_explicit_table_name
434 435
    assert Topic.where('topics.approved' => false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
436 437
  end

438
  def test_find_on_hash_conditions_with_hashed_table_name
439 440
    assert Topic.where(topics: { approved: false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
441 442 443
  end

  def test_find_with_hash_conditions_on_joined_table
444
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
445 446 447 448 449
    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
450
    firms = DependentFirm.joins(:account).where(name: 'RailsCore', accounts: { credit_limit: 55..60 })
451 452 453 454
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

455 456 457 458 459 460 461 462
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
    assert Customer.where('customers.name' => david.name, :address => david.address).find(david.id)
    assert_raise(ActiveRecord::RecordNotFound) {
      Customer.where('customers.name' => david.name + "1", :address => david.address).find(david.id)
    }
  end

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

467
  def test_find_on_hash_conditions_with_range
468 469
    assert_equal [1,2], Topic.where(id: 1..2).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
470
  end
471

472
  def test_find_on_hash_conditions_with_end_exclusive_range
473 474 475
    assert_equal [1,2,3], Topic.where(id: 1..3).to_a.map(&:id).sort
    assert_equal [1,2], Topic.where(id: 1...3).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
476 477
  end

478
  def test_find_on_hash_conditions_with_multiple_ranges
479 480
    assert_equal [1,2,3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
    assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
481
  end
482

483
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
484
    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
485 486
  end

487
  def test_find_on_multiple_hash_conditions
488 489 490 491
    assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
492
  end
493

494
  def test_condition_interpolation
495
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
496 497 498
    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
499 500
  end

501
  def test_condition_array_interpolation
502 503 504 505
    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 已提交
506
  end
507

508
  def test_condition_hash_interpolation
509 510 511
    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
512
  end
513

514
  def test_hash_condition_find_malformed
515
    assert_raise(ActiveRecord::StatementInvalid) {
516
      Company.where(id: 2, dhh: true).first
517 518
    }
  end
519

520 521
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
522
    assert Company.where(name: "Ain't noth'n like' \#stuff").first
523 524 525
  end

  def test_hash_condition_find_with_array
526 527 528
    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
529 530 531
  end

  def test_hash_condition_find_with_nil
532
    topic = Topic.where(last_read: nil).first
533 534
    assert_not_nil topic
    assert_nil topic.last_read
535
  end
D
David Heinemeier Hansson 已提交
536

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customer = Customer.where(:balance => balance).first
    assert_equal customers(:david), found_customer
  end

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

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

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

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

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

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

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
    with_env_tz 'America/New_York' do
590
      with_timezone_config default: :local do
591
        topic = Topic.first
592
        assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first
593 594 595 596 597 598
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
599
      with_timezone_config default: :utc do
600
        topic = Topic.first
601
        assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getlocal]).first
602 603 604 605 606 607
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
608
      with_timezone_config default: :utc do
609
        topic = Topic.first
610
        assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first
611 612 613 614
      end
    end
  end

D
David Heinemeier Hansson 已提交
615
  def test_bind_variables
616 617 618 619
    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
620
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
621
      Company.where(["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
622
    }
623
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
624
     Company.where(["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
625 626
    }
  end
627

D
David Heinemeier Hansson 已提交
628 629
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
630
    assert Company.where(["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
631 632 633 634
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
635
    assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
636 637 638 639
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
640
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
641

642
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
643
    assert_nothing_raised                                 { bind '?', 1 }
644
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
645
  end
646

D
David Heinemeier Hansson 已提交
647 648 649
  def test_named_bind_variables
    assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
    assert_equal '1 1', bind(':a :a', :a => 1)  # ' ruby-mode
650

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

653 654 655 656
    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 已提交
657 658
  end

659 660 661 662 663 664 665 666 667 668 669 670
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

671
  def test_bind_enumerable
672 673
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
674
    assert_equal '1,2,3', bind('?', [1, 2, 3])
675
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
676 677

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

680 681
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
682

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

687 688 689 690 691 692 693
  def test_bind_empty_enumerable
    quoted_nil = ActiveRecord::Base.connection.quote(nil)
    assert_equal quoted_nil, bind('?', [])
    assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
    assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
  end

J
Jeremy Kemper 已提交
694 695 696
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
697 698
  end

699 700 701 702 703
  def test_bind_chars
    quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
    quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi")
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper")
704 705
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
706 707
  end

708 709 710 711 712 713 714 715
  def test_bind_record
    o = Struct.new(:quoted_id).new(1)
    assert_equal '1', bind('?', o)

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

716 717 718
  def test_named_bind_with_postgresql_type_casts
    l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
    assert_nothing_raised(&l)
719
    assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
720 721
  end

D
David Heinemeier Hansson 已提交
722
  def test_string_sanitation
723 724
    assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
    assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
D
David Heinemeier Hansson 已提交
725 726 727 728 729 730 731 732 733
  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
734
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
735 736
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
737

738 739
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
740
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
741 742
  end

743 744 745 746 747 748 749
  def test_find_by_on_attribute_that_is_a_reserved_word
    dog_alias = 'Dog'
    dog = Dog.create(alias: dog_alias)

    assert_equal dog, Dog.find_by_alias(dog_alias)
  end

750 751 752 753 754
  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 已提交
755
  def test_find_by_one_attribute_bang_with_blank_defined
756 757
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
758 759
  end

760
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
761
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
762 763
  end

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
  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

798 799
  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
A
Akira Matsuda 已提交
800
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
801 802
    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
803 804
  end

805
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
806
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
807
  end
808

D
David Heinemeier Hansson 已提交
809
  def test_find_by_one_missing_attribute
810
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
811
  end
812

813
  def test_find_by_invalid_method_syntax
814 815 816 817
    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") }
818
  end
D
David Heinemeier Hansson 已提交
819 820

  def test_find_by_two_attributes
821
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
822 823 824
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

825 826 827 828
  def test_find_by_two_attributes_but_passing_only_one
    assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
  end

L
Lauro Caetano 已提交
829
  def test_find_last_with_offset
830
    devs = Developer.order('id')
L
Lauro Caetano 已提交
831 832 833 834 835 836 837

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

D
David Heinemeier Hansson 已提交
838 839 840 841 842
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
843

D
David Heinemeier Hansson 已提交
844 845 846 847 848 849
  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
850
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
851 852
  end

853
  def test_find_all_with_join
854 855 856
    developers_on_project_one = Developer.
      joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
      where('project_id=1').to_a
857
    assert_equal 3, developers_on_project_one.length
858 859 860
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
861
  end
862

863
  def test_joins_dont_clobber_id
864 865 866
    first = Firm.
      joins('INNER JOIN companies clients ON clients.firm_id = companies.id').
      where('companies.id = 1').first
867 868 869
    assert_equal 1, first.id
  end

870
  def test_joins_with_string_array
871 872 873 874
    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'"
            ])
875 876 877
    assert_equal 1, person_with_reader_and_post.size
  end

878 879
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
880
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
881 882
    end
  end
883

884 885
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
886
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
887
    assert_equal [], Post.where(id: nil)
888 889
  end

890 891 892 893 894
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
895
    assert_equal [], Post.where('id in (?)', [])
896 897 898
  end

  def test_find_by_records
899 900 901
    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')
902 903
  end

904 905 906 907 908 909 910 911 912
  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
913 914
    assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
    assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
915 916
  end

917 918
  def test_select_rows
    assert_equal(
919
      [["1", "1", nil, "37signals"],
920 921
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
922
      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?}})
923
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
924
      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?}}
925 926
  end

927
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
928
    assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size
929

930 931
    assert_equal 3, Post.includes(author: :author_address, authors: :author_address).
                              order('author_addresses_authors.id DESC ').limit(3).to_a.size
932 933
  end

934
  def test_find_with_nil_inside_set_passed_for_one_attribute
935 936 937 938 939
    client_of = Company.
      where(client_of: [2, 1, nil],
            name: ['37signals', 'Summit', 'Microsoft']).
      order('client_of DESC').
      map { |x| x.client_of }
940

941 942
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
943 944
  end

945
  def test_find_with_nil_inside_set_passed_for_attribute
946 947 948 949
    client_of = Company.
      where(client_of: [nil]).
      order('client_of DESC').
      map { |x| x.client_of }
950 951 952 953

    assert_equal [], client_of.compact
  end

954
  def test_with_limiting_with_custom_select
955
    posts = Post.references(:authors).merge(
956
      :includes => :author, :select => 'posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
957
      :limit => 3, :order => 'posts.id'
958
    ).to_a
959 960
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
961
  end
962

963
  def test_find_one_message_with_custom_primary_key
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979
    table_with_custom_primary_key do |model|
      model.primary_key = :name
      e = assert_raises(ActiveRecord::RecordNotFound) do
        model.find 'Hello World!'
      end
      assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message
    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
        model.find 'Hello', 'World!'
      end
      assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message
980 981 982
    end
  end

983 984 985 986 987 988
  def test_find_without_primary_key
    assert_raises(ActiveRecord::UnknownPrimaryKey) do
      Matey.find(1)
    end
  end

989
  def test_finder_with_offset_string
990
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a }
991 992
  end

D
David Heinemeier Hansson 已提交
993 994 995 996 997 998 999 1000
  protected
    def bind(statement, *vars)
      if vars.first.is_a?(Hash)
        ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
      else
        ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
      end
    end
1001 1002 1003 1004 1005 1006 1007 1008

    def table_with_custom_primary_key
      yield(Class.new(Toy) do
        def self.name
          'MercedesCar'
        end
      end)
    end
D
David Heinemeier Hansson 已提交
1009
end