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

101 102 103 104 105 106 107 108 109 110 111 112
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
    assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
    assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
    assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
    assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
  end

113
  def test_exists_with_empty_table_and_no_args_given
114 115 116
    Topic.delete_all
    assert !Topic.exists?
  end
117

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
  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

133 134 135 136 137
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
138 139 140 141
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
142

D
David Heinemeier Hansson 已提交
143
  def test_find_by_ids
144 145 146 147 148
    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
149 150
    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
151 152 153 154

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

160 161 162 163
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

164 165 166 167
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
168
  def test_find_by_ids_missing_one
169
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
170
  end
171

172
  def test_find_with_group_and_sanitized_having_method
173
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
174 175 176 177 178
    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 已提交
179 180
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
181

D
David Heinemeier Hansson 已提交
182
    assert_equal(1, topics.size)
183
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
184
  end
185

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

D
David Heinemeier Hansson 已提交
189
    assert_equal(1, topics.size)
190
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
191
  end
192

193 194 195 196
  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
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
  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

218
  def test_first
219
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
220
  end
221

222
  def test_first_failing
223
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
224
  end
D
David Heinemeier Hansson 已提交
225

226 227 228 229 230 231 232 233 234 235 236 237
  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

238 239 240 241 242 243
  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

244
  def test_model_class_responds_to_first_bang
245 246
    assert Topic.first!
    Topic.delete_all
247 248 249 250 251
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

252 253 254 255 256 257 258 259 260 261 262 263
  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

264 265 266 267 268 269 270 271
  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

272 273
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
274 275
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
  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)
292 293
  end

294 295
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
296 297 298 299
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
300
  def test_unexisting_record_exception_handling
301
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
302 303
      Topic.find(1).parent
    }
304

305
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
306
  end
307

308
  def test_find_only_some_columns
309
    topic = Topic.all.merge!(:select => "author_name").find(1)
310
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
311
    assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
312
    assert_nil topic.read_attribute("title")
313 314
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
315
    assert !topic.attribute_present?(:title)
316
    assert topic.attribute_present?("author_name")
317
    assert_respond_to topic, "author_name"
318
  end
J
Jeremy Kemper 已提交
319

320
  def test_find_on_array_conditions
321 322
    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 已提交
323
  end
324

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

330
  def test_find_on_hash_conditions_with_explicit_table_name
331 332
    assert Topic.all.merge!(:where => { 'topics.approved' => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { 'topics.approved' => true }).find(1) }
333 334
  end

335
  def test_find_on_hash_conditions_with_hashed_table_name
336 337
    assert Topic.all.merge!(:where => {:topics => { :approved => false }}).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => {:topics => { :approved => true }}).find(1) }
338 339 340
  end

  def test_find_with_hash_conditions_on_joined_table
341
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
342 343 344 345 346
    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
347
    firms = DependentFirm.all.merge!(:joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }})
348 349 350 351
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

352 353 354 355 356 357 358 359
  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

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

364
  def test_find_on_hash_conditions_with_range
365 366
    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) }
367
  end
368

369
  def test_find_on_hash_conditions_with_end_exclusive_range
370 371 372
    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) }
373 374
  end

375
  def test_find_on_hash_conditions_with_multiple_ranges
376 377
    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
378
  end
379

380
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
381
    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
382 383
  end

384
  def test_find_on_multiple_hash_conditions
385 386 387 388
    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) }
389
  end
390

391
  def test_condition_interpolation
392
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
393 394 395
    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
396 397
  end

398
  def test_condition_array_interpolation
399 400 401 402
    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 已提交
403
  end
404

405
  def test_condition_hash_interpolation
406 407 408
    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
409
  end
410

411
  def test_hash_condition_find_malformed
412
    assert_raise(ActiveRecord::StatementInvalid) {
413
      Company.all.merge!(:where => { :id => 2, :dhh => true }).first
414 415
    }
  end
416

417 418
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
419
    assert Company.all.merge!(:where => { :name => "Ain't noth'n like' \#stuff" }).first
420 421 422
  end

  def test_hash_condition_find_with_array
423 424 425
    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
426 427 428
  end

  def test_hash_condition_find_with_nil
429
    topic = Topic.all.merge!(:where => { :last_read => nil } ).first
430 431
    assert_not_nil topic
    assert_nil topic.last_read
432
  end
D
David Heinemeier Hansson 已提交
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 464 465 466 467 468 469 470 471 472 473 474 475
  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

476 477 478 479
  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
480
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getutc]).first
481 482 483 484 485 486 487 488
      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
489
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getutc}).first
490 491 492 493 494 495 496 497
      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
498
        assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getlocal]).first
499 500 501 502 503 504 505 506
      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
507
        assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getlocal}).first
508 509 510 511
      end
    end
  end

D
David Heinemeier Hansson 已提交
512
  def test_bind_variables
513 514 515 516
    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
517
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
518
      Company.all.merge!(:where => ["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
519
    }
520
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
521
     Company.all.merge!(:where => ["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
522 523
    }
  end
524

D
David Heinemeier Hansson 已提交
525 526
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
527
    assert Company.all.merge!(:where => ["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
528 529 530 531
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
532
    assert Company.all.merge!(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
533 534 535 536
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
537
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
538

539
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
540
    assert_nothing_raised                                 { bind '?', 1 }
541
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
542
  end
543

D
David Heinemeier Hansson 已提交
544 545 546
  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
547

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

550 551 552 553
    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 已提交
554 555
  end

556 557 558 559 560 561 562 563 564 565 566 567
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

568
  def test_bind_enumerable
569 570
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
571
    assert_equal '1,2,3', bind('?', [1, 2, 3])
572
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
573 574

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

577 578
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
579

580 581
    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 已提交
582 583
  end

584 585 586 587 588 589 590
  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 已提交
591 592 593
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
594 595
  end

596 597 598 599 600
  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")
601 602
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
603 604
  end

605 606 607 608 609 610 611 612
  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

613 614 615
  def test_named_bind_with_postgresql_type_casts
    l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
    assert_nothing_raised(&l)
616
    assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
617 618
  end

D
David Heinemeier Hansson 已提交
619
  def test_string_sanitation
620 621
    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 已提交
622 623 624 625 626 627 628 629 630
  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
631
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
632 633
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
634

635 636
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
637
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
638 639
  end

640 641 642 643 644
  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 已提交
645
  def test_find_by_one_attribute_bang_with_blank_defined
646 647
    blank_topic = BlankTopic.create(title: "The Blank One")
    assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
N
Nikita Afanasenko 已提交
648 649
  end

650
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
651
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
652 653
  end

654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
  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

688 689
  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 已提交
690
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
691 692
    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
693 694
  end

695
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
696
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
697
  end
698

D
David Heinemeier Hansson 已提交
699
  def test_find_by_one_missing_attribute
700
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
701
  end
702

703
  def test_find_by_invalid_method_syntax
704 705 706 707
    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") }
708
  end
D
David Heinemeier Hansson 已提交
709 710

  def test_find_by_two_attributes
711
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
712 713 714
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

715 716 717 718
  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 已提交
719 720 721 722 723
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
724

D
David Heinemeier Hansson 已提交
725 726 727 728 729 730
  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
731
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
732 733
  end

734
  def test_find_all_with_join
735
    developers_on_project_one = Developer.all.merge!(
736
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
J
Jon Leighton 已提交
737
      :where => 'project_id=1'
738
    ).to_a
739
    assert_equal 3, developers_on_project_one.length
740 741 742
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
743
  end
744

745
  def test_joins_dont_clobber_id
746
    first = Firm.all.merge!(
747
      :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
J
Jon Leighton 已提交
748 749
      :where => 'companies.id = 1'
    ).first
750 751 752
    assert_equal 1, first.id
  end

753
  def test_joins_with_string_array
754
    person_with_reader_and_post = Post.all.merge!(
755 756 757 758 759 760 761 762
      :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

763 764
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
765
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
766 767
    end
  end
768

769 770
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
771
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
772
    assert_equal [], Post.where(id: nil)
773 774
  end

775 776 777 778 779
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
780
    assert_equal [], Post.where('id in (?)', [])
781 782 783
  end

  def test_find_by_records
784 785 786
    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')
787 788
  end

789 790 791 792 793 794 795 796 797
  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
798 799
    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")
800 801
  end

802 803
  def test_select_rows
    assert_equal(
804
      [["1", "1", nil, "37signals"],
805 806
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
807
      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?}})
808
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
809
      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?}}
810 811
  end

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

815
    assert_equal 3, Post.all.merge!(:includes => { :author => :author_address, :authors => :author_address},
816
                              :order => 'author_addresses_authors.id DESC ', :limit => 3).to_a.size
817 818
  end

819
  def test_find_with_nil_inside_set_passed_for_one_attribute
820
    client_of = Company.all.merge!(
J
Jon Leighton 已提交
821
      :where => {
822 823 824 825 826
        :client_of => [2, 1, nil],
        :name => ['37signals', 'Summit', 'Microsoft'] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

827 828
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
829 830
  end

831 832 833 834 835 836 837 838 839
  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

840
  def test_with_limiting_with_custom_select
841
    posts = Post.references(:authors).merge(
J
Jon Leighton 已提交
842
      :includes => :author, :select => ' posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
843
      :limit => 3, :order => 'posts.id'
844
    ).to_a
845 846
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
847
  end
848

849
  def test_find_one_message_with_custom_primary_key
850
    Toy.primary_key = :name
851 852 853 854 855
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
856 857
  ensure
    Toy.reset_primary_key
858 859
  end

860
  def test_finder_with_offset_string
861
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a }
862 863
  end

D
David Heinemeier Hansson 已提交
864 865 866 867 868 869 870 871
  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
872 873 874 875 876 877 878

    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
D
David Heinemeier Hansson 已提交
879
end