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

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

18 19 20 21 22 23 24 25 26 27 28 29
  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 已提交
30
  def test_find
31
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
32
  end
33

34 35 36 37 38 39 40
  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

41 42 43 44 45
  # 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
46

D
David Heinemeier Hansson 已提交
47
  def test_exists
48 49
    assert Topic.exists?(1)
    assert Topic.exists?("1")
50 51
    assert Topic.exists?(title: "The First Topic")
    assert Topic.exists?(heading: "The First Topic")
52 53 54
    assert Topic.exists?(:author_name => "Mary", :approved => true)
    assert Topic.exists?(["parent_id = ?", 1])
    assert !Topic.exists?(45)
55
    assert !Topic.exists?(Topic.new)
56 57 58 59 60 61 62 63 64

    begin
      assert !Topic.exists?("foo")
    rescue ActiveRecord::StatementInvalid
      # PostgreSQL complains about string comparison with integer field
    rescue Exception
      flunk
    end

65
    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
D
David Heinemeier Hansson 已提交
66
  end
67

68
  def test_exists_does_not_select_columns_without_alias
69
    assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
70 71 72 73
      Topic.exists?
    end
  end

74 75 76
  def test_exists_returns_true_with_one_record_and_no_args
    assert Topic.exists?
  end
77

E
Egor Lynko 已提交
78 79 80 81
  def test_exists_returns_false_with_false_arg
    assert !Topic.exists?(false)
  end

82 83 84 85 86 87 88 89 90
  # 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
    assert !Topic.exists?(nil)
    assert Topic.exists?
    assert !Topic.first.replies.exists?(nil)
    assert Topic.first.replies.exists?
  end

91 92
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
93
    assert Topic.order(:id).distinct.exists?
94 95
  end

96 97 98 99 100 101
  def test_exists_with_includes_limit_and_empty_result
    assert !Topic.includes(:replies).limit(0).exists?
    assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
  end

  def test_exists_with_empty_table_and_no_args_given
102 103 104
    Topic.delete_all
    assert !Topic.exists?
  end
105

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
    assert Customer.exists?(:address => existing_address)
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
    assert !Customer.exists?(:address =>
      Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
    assert !Customer.exists?(:address =>
      Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
    assert !Customer.exists?(:address =>
      Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
  end

121 122 123 124 125
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
126 127 128 129
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
130

D
David Heinemeier Hansson 已提交
131
  def test_find_by_ids
132 133 134 135 136
    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
137 138
    assert_equal 2, Entrant.all.merge!(:limit => 2).find([1,3,2]).size
    assert_equal 1, Entrant.all.merge!(:limit => 3, :offset => 2).find([1,3,2]).size
139 140 141 142

    # 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 已提交
143
    devs = Developer.all
144
    last_devs = Developer.all.merge!(:limit => 3, :offset => 9).find devs.map(&:id)
145
    assert_equal 2, last_devs.size
D
David Heinemeier Hansson 已提交
146 147
  end

148 149 150 151
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

152 153 154 155
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
156
  def test_find_by_ids_missing_one
157
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
158
  end
159

160
  def test_find_with_group_and_sanitized_having_method
161
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
162 163 164 165 166
    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 已提交
167 168
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
169

D
David Heinemeier Hansson 已提交
170
    assert_equal(1, topics.size)
171
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
172
  end
173

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

D
David Heinemeier Hansson 已提交
177
    assert_equal(1, topics.size)
178
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
179
  end
180

181 182 183 184
  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
185

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

206
  def test_first
207
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
208
  end
209

210
  def test_first_failing
211
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
212
  end
D
David Heinemeier Hansson 已提交
213

214 215 216 217 218 219 220 221 222 223 224 225
  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

226 227 228 229 230 231
  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

232
  def test_model_class_responds_to_first_bang
233 234
    assert Topic.first!
    Topic.delete_all
235 236 237 238 239
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

240 241 242 243 244 245 246 247 248 249 250 251
  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

252 253 254 255 256 257 258 259
  def test_model_class_responds_to_last_bang
    assert_equal topics(:fourth), Topic.last!
    assert_raises ActiveRecord::RecordNotFound do
      Topic.delete_all
      Topic.last!
    end
  end

260 261
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
262 263
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
  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)
280 281
  end

282 283
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
284 285 286 287
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
288
  def test_unexisting_record_exception_handling
289
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
290 291
      Topic.find(1).parent
    }
292

293
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
294
  end
295

296
  def test_find_only_some_columns
297
    topic = Topic.all.merge!(:select => "author_name").find(1)
298
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
299
    assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
300
    assert_nil topic.read_attribute("title")
301 302
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
303
    assert !topic.attribute_present?(:title)
304
    assert topic.attribute_present?("author_name")
305
    assert_respond_to topic, "author_name"
306
  end
J
Jeremy Kemper 已提交
307

308
  def test_find_on_array_conditions
309 310
    assert Topic.all.merge!(:where => ["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => ["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
311
  end
312

313
  def test_find_on_hash_conditions
314 315
    assert Topic.all.merge!(:where => { :approved => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :approved => true }).find(1) }
316
  end
317

318
  def test_find_on_hash_conditions_with_explicit_table_name
319 320
    assert Topic.all.merge!(:where => { 'topics.approved' => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { 'topics.approved' => true }).find(1) }
321 322
  end

323
  def test_find_on_hash_conditions_with_hashed_table_name
324 325
    assert Topic.all.merge!(:where => {:topics => { :approved => false }}).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => {:topics => { :approved => true }}).find(1) }
326 327 328
  end

  def test_find_with_hash_conditions_on_joined_table
329
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
330 331 332 333 334
    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
335
    firms = DependentFirm.all.merge!(:joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }})
336 337 338 339
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

340 341 342 343 344 345 346 347
  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

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

352
  def test_find_on_hash_conditions_with_range
353 354
    assert_equal [1,2], Topic.all.merge!(:where => { :id => 1..2 }).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :id => 2..3 }).find(1) }
355
  end
356

357
  def test_find_on_hash_conditions_with_end_exclusive_range
358 359 360
    assert_equal [1,2,3], Topic.all.merge!(:where => { :id => 1..3 }).to_a.map(&:id).sort
    assert_equal [1,2], Topic.all.merge!(:where => { :id => 1...3 }).to_a.map(&:id).sort
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :id => 2...3 }).find(3) }
361 362
  end

363
  def test_find_on_hash_conditions_with_multiple_ranges
364 365
    assert_equal [1,2,3], Comment.all.merge!(:where => { :id => 1..3, :post_id => 1..2 }).to_a.map(&:id).sort
    assert_equal [1], Comment.all.merge!(:where => { :id => 1..1, :post_id => 1..10 }).to_a.map(&:id).sort
366
  end
367

368
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
369
    assert_equal [1,2,3,5,6,7,8,9], Comment.all.merge!(:where => {:id => [1..2, 3, 5, 6..8, 9]}).to_a.map(&:id).sort
370 371
  end

372
  def test_find_on_multiple_hash_conditions
373 374 375 376
    assert Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
377
  end
378

379
  def test_condition_interpolation
380
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
381 382 383
    assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!"]).first
    assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.all.merge!(:where => ["id = %d", 1]).first.written_on
384 385
  end

386
  def test_condition_array_interpolation
387 388 389 390
    assert_kind_of Firm, Company.all.merge!(:where => ["name = '%s'", "37signals"]).first
    assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!"]).first
    assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.all.merge!(:where => ["id = %d", 1]).first.written_on
D
David Heinemeier Hansson 已提交
391
  end
392

393
  def test_condition_hash_interpolation
394 395 396
    assert_kind_of Firm, Company.all.merge!(:where => { :name => "37signals"}).first
    assert_nil Company.all.merge!(:where => { :name => "37signals!"}).first
    assert_kind_of Time, Topic.all.merge!(:where => {:id => 1}).first.written_on
397
  end
398

399
  def test_hash_condition_find_malformed
400
    assert_raise(ActiveRecord::StatementInvalid) {
401
      Company.all.merge!(:where => { :id => 2, :dhh => true }).first
402 403
    }
  end
404

405 406
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
407
    assert Company.all.merge!(:where => { :name => "Ain't noth'n like' \#stuff" }).first
408 409 410
  end

  def test_hash_condition_find_with_array
411 412 413
    p1, p2 = Post.all.merge!(:limit => 2, :order => 'id asc').to_a
    assert_equal [p1, p2], Post.all.merge!(:where => { :id => [p1, p2] }, :order => 'id asc').to_a
    assert_equal [p1, p2], Post.all.merge!(:where => { :id => [p1, p2.id] }, :order => 'id asc').to_a
414 415 416
  end

  def test_hash_condition_find_with_nil
417
    topic = Topic.all.merge!(:where => { :last_read => nil } ).first
418 419
    assert_not_nil topic
    assert_nil topic.last_read
420
  end
D
David Heinemeier Hansson 已提交
421

422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
  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

464 465 466 467
  def test_condition_utc_time_interpolation_with_default_timezone_local
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :local do
        topic = Topic.first
468
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getutc]).first
469 470 471 472 473 474 475 476
      end
    end
  end

  def test_hash_condition_utc_time_interpolation_with_default_timezone_local
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :local do
        topic = Topic.first
477
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getutc}).first
478 479 480 481 482 483 484 485
      end
    end
  end

  def test_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :utc do
        topic = Topic.first
486
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getlocal]).first
487 488 489 490 491 492 493 494
      end
    end
  end

  def test_hash_condition_local_time_interpolation_with_default_timezone_utc
    with_env_tz 'America/New_York' do
      with_active_record_default_timezone :utc do
        topic = Topic.first
495
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getlocal}).first
496 497 498 499
      end
    end
  end

D
David Heinemeier Hansson 已提交
500
  def test_bind_variables
501 502 503 504
    assert_kind_of Firm, Company.all.merge!(:where => ["name = ?", "37signals"]).first
    assert_nil Company.all.merge!(:where => ["name = ?", "37signals!"]).first
    assert_nil Company.all.merge!(:where => ["name = ?", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.all.merge!(:where => ["id = ?", 1]).first.written_on
505
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
506
      Company.all.merge!(:where => ["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
507
    }
508
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
509
     Company.all.merge!(:where => ["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
510 511
    }
  end
512

D
David Heinemeier Hansson 已提交
513 514
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
515
    assert Company.all.merge!(:where => ["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
516 517 518 519
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
520
    assert Company.all.merge!(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
521 522 523 524
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
525
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
526

527
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
528
    assert_nothing_raised                                 { bind '?', 1 }
529
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
530
  end
531

D
David Heinemeier Hansson 已提交
532 533 534
  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
535

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

538 539 540 541
    assert_kind_of Firm, Company.all.merge!(:where => ["name = :name", { :name => "37signals" }]).first
    assert_nil Company.all.merge!(:where => ["name = :name", { :name => "37signals!" }]).first
    assert_nil Company.all.merge!(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first
    assert_kind_of Time, Topic.all.merge!(:where => ["id = :id", { :id => 1 }]).first.written_on
D
David Heinemeier Hansson 已提交
542 543
  end

544 545 546 547 548 549 550 551 552 553 554 555
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

556
  def test_bind_enumerable
557 558
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
559
    assert_equal '1,2,3', bind('?', [1, 2, 3])
560
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
561 562

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

565 566
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
567

568 569
    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 已提交
570 571
  end

572 573 574 575 576 577 578
  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 已提交
579 580 581
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
582 583
  end

584 585 586 587 588
  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")
589 590
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
591 592
  end

593 594 595 596 597 598 599 600
  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

601 602 603 604 605 606
  def test_named_bind_with_postgresql_type_casts
    l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
    assert_nothing_raised(&l)
    assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call
  end

D
David Heinemeier Hansson 已提交
607
  def test_string_sanitation
608 609
    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 已提交
610 611 612 613 614 615 616 617 618
  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
619
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
620 621
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
622

623 624
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
625
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
626 627
  end

628 629 630 631 632
  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 已提交
633
  def test_find_by_one_attribute_bang_with_blank_defined
634 635
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
636 637
  end

638
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
639
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
640 641
  end

642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
  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

676 677
  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 已提交
678
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
679 680
    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
681 682
  end

683
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
684
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
685
  end
686

D
David Heinemeier Hansson 已提交
687
  def test_find_by_one_missing_attribute
688
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
689
  end
690

691
  def test_find_by_invalid_method_syntax
692 693 694 695
    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") }
696
  end
D
David Heinemeier Hansson 已提交
697 698

  def test_find_by_two_attributes
699
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
700 701 702
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

703 704 705 706
  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 已提交
707 708 709 710 711
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
712

D
David Heinemeier Hansson 已提交
713 714 715 716 717 718
  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
719
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
720 721
  end

722
  def test_find_all_with_join
723
    developers_on_project_one = Developer.all.merge!(
724
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
J
Jon Leighton 已提交
725
      :where => 'project_id=1'
726
    ).to_a
727
    assert_equal 3, developers_on_project_one.length
728 729 730
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
731
  end
732

733
  def test_joins_dont_clobber_id
734
    first = Firm.all.merge!(
735
      :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
J
Jon Leighton 已提交
736 737
      :where => 'companies.id = 1'
    ).first
738 739 740
    assert_equal 1, first.id
  end

741
  def test_joins_with_string_array
742
    person_with_reader_and_post = Post.all.merge!(
743 744 745 746 747 748 749 750
      :joins => [
        "INNER JOIN categorizations ON categorizations.post_id = posts.id",
        "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'"
      ]
    )
    assert_equal 1, person_with_reader_and_post.size
  end

751 752
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
753
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
754 755
    end
  end
756

757 758
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
759
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
760
    assert_equal [], Post.where(id: nil)
761 762
  end

763 764 765 766 767
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
768
    assert_equal [], Post.where('id in (?)', [])
769 770 771
  end

  def test_find_by_records
772 773 774
    p1, p2 = Post.all.merge!(:limit => 2, :order => 'id asc').to_a
    assert_equal [p1, p2], Post.all.merge!(:where => ['id in (?)', [p1, p2]], :order => 'id asc')
    assert_equal [p1, p2], Post.all.merge!(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc')
775 776
  end

777 778 779 780 781 782 783 784 785
  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
786 787
    assert_equal ["1","2","3","4","5","6","7","8","9", "10"], 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"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
788 789
  end

790 791
  def test_select_rows
    assert_equal(
792
      [["1", "1", nil, "37signals"],
793 794
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
795
      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?}})
796
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
797
      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?}}
798 799
  end

800
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
801
    assert_equal 2, Post.all.merge!(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).to_a.size
802

803
    assert_equal 3, Post.all.merge!(:includes => { :author => :author_address, :authors => :author_address},
804
                              :order => 'author_addresses_authors.id DESC ', :limit => 3).to_a.size
805 806
  end

807
  def test_find_with_nil_inside_set_passed_for_one_attribute
808
    client_of = Company.all.merge!(
J
Jon Leighton 已提交
809
      :where => {
810 811 812 813 814
        :client_of => [2, 1, nil],
        :name => ['37signals', 'Summit', 'Microsoft'] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

815 816
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
817 818
  end

819 820 821 822 823 824 825 826 827
  def test_find_with_nil_inside_set_passed_for_attribute
    client_of = Company.all.merge!(
      :where => { :client_of => [nil] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

    assert_equal [], client_of.compact
  end

828
  def test_with_limiting_with_custom_select
829
    posts = Post.references(:authors).merge(
J
Jon Leighton 已提交
830
      :includes => :author, :select => ' posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
831
      :limit => 3, :order => 'posts.id'
832
    ).to_a
833 834
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
835
  end
836

837
  def test_find_one_message_with_custom_primary_key
838
    Toy.primary_key = :name
839 840 841 842 843
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
844 845
  ensure
    Toy.reset_primary_key
846 847
  end

848
  def test_finder_with_offset_string
849
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a }
850 851
  end

D
David Heinemeier Hansson 已提交
852 853 854 855 856 857 858 859
  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
860 861 862 863 864 865 866 867 868 869 870 871 872 873

    def with_env_tz(new_tz = 'US/Eastern')
      old_tz, ENV['TZ'] = ENV['TZ'], new_tz
      yield
    ensure
      old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
    end

    def with_active_record_default_timezone(zone)
      old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
      yield
    ensure
      ActiveRecord::Base.default_timezone = old_zone
    end
D
David Heinemeier Hansson 已提交
874
end