finder_test.rb 32.1 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

  def test_find
19
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
20
  end
21

22 23 24 25 26
  # 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
27

D
David Heinemeier Hansson 已提交
28
  def test_exists
29 30 31 32 33 34
    assert Topic.exists?(1)
    assert Topic.exists?("1")
    assert Topic.exists?(:author_name => "David")
    assert Topic.exists?(:author_name => "Mary", :approved => true)
    assert Topic.exists?(["parent_id = ?", 1])
    assert !Topic.exists?(45)
35
    assert !Topic.exists?(Topic.new)
36 37 38 39 40 41 42 43 44

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

45
    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
D
David Heinemeier Hansson 已提交
46
  end
47

48
  def test_exists_does_not_select_columns_without_alias
49
    assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
50 51 52 53
      Topic.exists?
    end
  end

54 55 56
  def test_exists_returns_true_with_one_record_and_no_args
    assert Topic.exists?
  end
57

E
Egor Lynko 已提交
58 59 60 61
  def test_exists_returns_false_with_false_arg
    assert !Topic.exists?(false)
  end

62 63 64 65 66 67 68 69 70
  # 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

71 72 73 74 75
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
    assert Topic.order(:id).uniq.exists?
  end

76 77 78 79 80 81
  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
82 83 84
    Topic.delete_all
    assert !Topic.exists?
  end
85

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  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

101 102 103 104 105
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
106 107 108 109
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
110

D
David Heinemeier Hansson 已提交
111
  def test_find_by_ids
112 113 114 115 116
    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
117 118
    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
119 120 121 122

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

128 129 130 131
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

132 133 134 135
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
136
  def test_find_by_ids_missing_one
137
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
138
  end
139

140
  def test_find_with_group_and_sanitized_having_method
141
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
142 143 144 145 146
    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 已提交
147 148
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
149

D
David Heinemeier Hansson 已提交
150
    assert_equal(1, topics.size)
151
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
152
  end
153

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

D
David Heinemeier Hansson 已提交
157
    assert_equal(1, topics.size)
158
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
159
  end
160

161 162 163 164
  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
165

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
  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

186
  def test_first
187
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
188
  end
189

190
  def test_first_failing
191
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
192
  end
D
David Heinemeier Hansson 已提交
193

194 195 196 197 198 199 200 201 202 203 204 205
  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

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

212
  def test_model_class_responds_to_first_bang
213 214
    assert Topic.first!
    Topic.delete_all
215 216 217 218 219
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

220 221 222 223 224 225 226 227 228 229 230 231
  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

232 233 234 235 236 237 238 239
  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

240 241
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
242 243
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
  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)
260 261
  end

262 263
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
264 265 266 267
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
268
  def test_unexisting_record_exception_handling
269
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
270 271
      Topic.find(1).parent
    }
272

273
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
274
  end
275

276
  def test_find_only_some_columns
277
    topic = Topic.all.merge!(:select => "author_name").find(1)
278
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
279
    assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
280
    assert_nil topic.read_attribute("title")
281 282
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
283
    assert !topic.attribute_present?(:title)
284
    assert topic.attribute_present?("author_name")
285
    assert_respond_to topic, "author_name"
286
  end
J
Jeremy Kemper 已提交
287

288
  def test_find_on_array_conditions
289 290
    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 已提交
291
  end
292

293
  def test_find_on_hash_conditions
294 295
    assert Topic.all.merge!(:where => { :approved => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :approved => true }).find(1) }
296
  end
297

298
  def test_find_on_hash_conditions_with_explicit_table_name
299 300
    assert Topic.all.merge!(:where => { 'topics.approved' => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { 'topics.approved' => true }).find(1) }
301 302
  end

303
  def test_find_on_hash_conditions_with_hashed_table_name
304 305
    assert Topic.all.merge!(:where => {:topics => { :approved => false }}).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => {:topics => { :approved => true }}).find(1) }
306 307 308
  end

  def test_find_with_hash_conditions_on_joined_table
309
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
310 311 312 313 314
    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
315
    firms = DependentFirm.all.merge!(:joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }})
316 317 318 319
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

320 321 322 323 324 325 326 327
  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

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

332
  def test_find_on_hash_conditions_with_range
333 334
    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) }
335
  end
336

337
  def test_find_on_hash_conditions_with_end_exclusive_range
338 339 340
    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) }
341 342
  end

343
  def test_find_on_hash_conditions_with_multiple_ranges
344 345
    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
346
  end
347

348
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
349
    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
350 351
  end

352
  def test_find_on_multiple_hash_conditions
353 354 355 356
    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) }
357
  end
358

359
  def test_condition_interpolation
360
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
361 362 363
    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
364 365
  end

366
  def test_condition_array_interpolation
367 368 369 370
    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 已提交
371
  end
372

373
  def test_condition_hash_interpolation
374 375 376
    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
377
  end
378

379
  def test_hash_condition_find_malformed
380
    assert_raise(ActiveRecord::StatementInvalid) {
381
      Company.all.merge!(:where => { :id => 2, :dhh => true }).first
382 383
    }
  end
384

385 386
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
387
    assert Company.all.merge!(:where => { :name => "Ain't noth'n like' \#stuff" }).first
388 389 390
  end

  def test_hash_condition_find_with_array
391 392 393
    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
394 395 396
  end

  def test_hash_condition_find_with_nil
397
    topic = Topic.all.merge!(:where => { :last_read => nil } ).first
398 399
    assert_not_nil topic
    assert_nil topic.last_read
400
  end
D
David Heinemeier Hansson 已提交
401

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
  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

444 445 446 447
  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
448
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getutc]).first
449 450 451 452 453 454 455 456
      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
457
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getutc}).first
458 459 460 461 462 463 464 465
      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
466
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getlocal]).first
467 468 469 470 471 472 473 474
      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
475
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getlocal}).first
476 477 478 479
      end
    end
  end

D
David Heinemeier Hansson 已提交
480
  def test_bind_variables
481 482 483 484
    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
485
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
486
      Company.all.merge!(:where => ["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
487
    }
488
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
489
     Company.all.merge!(:where => ["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
490 491
    }
  end
492

D
David Heinemeier Hansson 已提交
493 494
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
495
    assert Company.all.merge!(:where => ["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
496 497 498 499
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
500
    assert Company.all.merge!(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
501 502 503 504
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
505
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
506

507
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
508
    assert_nothing_raised                                 { bind '?', 1 }
509
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
510
  end
511

D
David Heinemeier Hansson 已提交
512 513 514
  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
515

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

518 519 520 521
    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 已提交
522 523
  end

524 525 526 527 528 529 530 531 532 533 534 535
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

536
  def test_bind_enumerable
537 538
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
539
    assert_equal '1,2,3', bind('?', [1, 2, 3])
540
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
541 542

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

545 546
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
547

548 549
    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 已提交
550 551
  end

552 553 554 555 556 557 558
  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 已提交
559 560 561
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
562 563
  end

564 565 566 567 568
  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")
569 570
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
571 572
  end

573 574 575 576 577 578 579 580
  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

581 582 583 584 585 586
  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 已提交
587
  def test_string_sanitation
588 589
    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 已提交
590 591 592 593 594 595 596 597 598
  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
599
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
600 601
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
602

603 604
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
605
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
606 607
  end

608 609 610 611 612
  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 已提交
613
  def test_find_by_one_attribute_bang_with_blank_defined
614 615
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
616 617
  end

618
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
619
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
620 621
  end

622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
  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

656 657
  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 已提交
658
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
659 660
    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
661 662
  end

663
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
664
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
665
  end
666

D
David Heinemeier Hansson 已提交
667
  def test_find_by_one_missing_attribute
668
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
669
  end
670

671
  def test_find_by_invalid_method_syntax
672 673 674 675
    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") }
676
  end
D
David Heinemeier Hansson 已提交
677 678

  def test_find_by_two_attributes
679
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
680 681 682
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

683 684 685 686
  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 已提交
687 688 689 690 691
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
692

D
David Heinemeier Hansson 已提交
693 694 695 696 697 698
  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
699
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
700 701
  end

702
  def test_find_all_with_join
703
    developers_on_project_one = Developer.all.merge!(
704
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
J
Jon Leighton 已提交
705
      :where => 'project_id=1'
706
    ).to_a
707
    assert_equal 3, developers_on_project_one.length
708 709 710
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
711
  end
712

713
  def test_joins_dont_clobber_id
714
    first = Firm.all.merge!(
715
      :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
J
Jon Leighton 已提交
716 717
      :where => 'companies.id = 1'
    ).first
718 719 720
    assert_equal 1, first.id
  end

721
  def test_joins_with_string_array
722
    person_with_reader_and_post = Post.all.merge!(
723 724 725 726 727 728 729 730
      :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

731 732
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
733
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
734 735
    end
  end
736

737 738
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
739
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
740
    assert_equal [], Post.where(id: nil)
741 742
  end

743 744 745 746 747
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
748
    assert_equal [], Post.where('id in (?)', [])
749 750 751
  end

  def test_find_by_records
752 753 754
    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')
755 756
  end

757 758 759 760 761 762 763 764 765
  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
766 767
    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")
768 769
  end

770 771
  def test_select_rows
    assert_equal(
772
      [["1", "1", nil, "37signals"],
773 774
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
775
      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?}})
776
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
777
      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?}}
778 779
  end

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

783
    assert_equal 3, Post.all.merge!(:includes => { :author => :author_address, :authors => :author_address},
784
                              :order => 'author_addresses_authors.id DESC ', :limit => 3).to_a.size
785 786
  end

787
  def test_find_with_nil_inside_set_passed_for_one_attribute
788
    client_of = Company.all.merge!(
J
Jon Leighton 已提交
789
      :where => {
790 791 792 793 794
        :client_of => [2, 1, nil],
        :name => ['37signals', 'Summit', 'Microsoft'] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

795 796
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
797 798 799
  end

  def test_find_with_nil_inside_set_passed_for_attribute
800
    client_of = Company.all.merge!(
J
Jon Leighton 已提交
801
      :where => { :client_of => [nil] },
802 803 804
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

805
    assert_equal [], client_of.compact
806 807
  end

808
  def test_with_limiting_with_custom_select
809
    posts = Post.references(:authors).merge(
J
Jon Leighton 已提交
810
      :includes => :author, :select => ' posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
811
      :limit => 3, :order => 'posts.id'
812
    ).to_a
813 814
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
815
  end
816

817
  def test_find_one_message_with_custom_primary_key
818
    Toy.primary_key = :name
819 820 821 822 823 824 825
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
  end

826
  def test_finder_with_offset_string
827
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a }
828 829
  end

D
David Heinemeier Hansson 已提交
830 831 832 833 834 835 836 837
  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
838 839 840 841 842 843 844 845 846 847 848 849 850 851

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