finder_test.rb 27.3 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
  def test_exists_does_not_instantiate_records
    Developer.expects(:instantiate).never
    Developer.exists?
  end

D
David Heinemeier Hansson 已提交
91 92 93 94
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
95

D
David Heinemeier Hansson 已提交
96
  def test_find_by_ids
97 98 99 100 101
    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
J
Jon Leighton 已提交
102 103
    assert_equal 2, Entrant.scoped(:limit => 2).find([1,3,2]).size
    assert_equal 1, Entrant.scoped(:limit => 3, :offset => 2).find([1,3,2]).size
104 105 106 107

    # 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.
108
    devs = Developer.to_a
J
Jon Leighton 已提交
109
    last_devs = Developer.scoped(:limit => 3, :offset => 9).find devs.map(&:id)
110
    assert_equal 2, last_devs.size
D
David Heinemeier Hansson 已提交
111 112
  end

113 114 115 116
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

117 118 119 120
  def test_find_doesnt_have_implicit_ordering
    assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
  end

D
David Heinemeier Hansson 已提交
121
  def test_find_by_ids_missing_one
122
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
123
  end
124

125
  def test_find_with_group_and_sanitized_having_method
126
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a
127 128 129 130 131
    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 已提交
132 133
  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
134

D
David Heinemeier Hansson 已提交
135
    assert_equal(1, topics.size)
136
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
137
  end
138

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

D
David Heinemeier Hansson 已提交
142
    assert_equal(1, topics.size)
143
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
144
  end
145

146 147 148 149
  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
150

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
  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

171
  def test_first
172
    assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
173
  end
174

175
  def test_first_failing
176
    assert_nil Topic.where("title = 'The Second Topic of the day!'").first
177
  end
D
David Heinemeier Hansson 已提交
178

179 180 181 182 183 184 185 186 187 188 189 190
  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

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

197
  def test_model_class_responds_to_first_bang
198 199
    assert Topic.first!
    Topic.delete_all
200 201 202 203 204
    assert_raises ActiveRecord::RecordNotFound do
      Topic.first!
    end
  end

205 206 207 208 209 210 211 212 213 214 215 216
  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

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

225 226
  def test_take_and_first_and_last_with_integer_should_use_sql_limit
    assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
227 228
    assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
    assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
  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)
245 246
  end

247 248
  def test_take_and_first_and_last_with_integer_should_return_an_array
    assert_kind_of Array, Topic.take(5)
249 250 251 252
    assert_kind_of Array, Topic.first(5)
    assert_kind_of Array, Topic.last(5)
  end

D
David Heinemeier Hansson 已提交
253
  def test_unexisting_record_exception_handling
254
    assert_raise(ActiveRecord::RecordNotFound) {
D
David Heinemeier Hansson 已提交
255 256
      Topic.find(1).parent
    }
257

258
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
259
  end
260

261
  def test_find_only_some_columns
J
Jon Leighton 已提交
262
    topic = Topic.scoped(:select => "author_name").find(1)
263
    assert_raise(ActiveModel::MissingAttributeError) {topic.title}
264
    assert_nil topic.read_attribute("title")
265 266
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
267
    assert !topic.attribute_present?(:title)
268
    assert topic.attribute_present?("author_name")
269
    assert_respond_to topic, "author_name"
270
  end
J
Jeremy Kemper 已提交
271

272
  def test_find_on_array_conditions
J
Jon Leighton 已提交
273 274
    assert Topic.scoped(:where => ["approved = ?", false]).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => ["approved = ?", true]).find(1) }
D
David Heinemeier Hansson 已提交
275
  end
276

277
  def test_find_on_hash_conditions
J
Jon Leighton 已提交
278 279
    assert Topic.scoped(:where => { :approved => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :approved => true }).find(1) }
280
  end
281

282
  def test_find_on_hash_conditions_with_explicit_table_name
J
Jon Leighton 已提交
283 284
    assert Topic.scoped(:where => { 'topics.approved' => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { 'topics.approved' => true }).find(1) }
285 286
  end

287
  def test_find_on_hash_conditions_with_hashed_table_name
J
Jon Leighton 已提交
288 289
    assert Topic.scoped(:where => {:topics => { :approved => false }}).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => {:topics => { :approved => true }}).find(1) }
290 291 292
  end

  def test_find_with_hash_conditions_on_joined_table
293
    firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
294 295 296 297 298
    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
J
Jon Leighton 已提交
299
    firms = DependentFirm.scoped :joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
300 301 302 303
    assert_equal 1, firms.size
    assert_equal companies(:rails_core), firms.first
  end

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

308
  def test_find_on_hash_conditions_with_range
309
    assert_equal [1,2], Topic.scoped(:where => { :id => 1..2 }).to_a.map(&:id).sort
J
Jon Leighton 已提交
310
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2..3 }).find(1) }
311
  end
312

313
  def test_find_on_hash_conditions_with_end_exclusive_range
314 315
    assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).to_a.map(&:id).sort
    assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).to_a.map(&:id).sort
J
Jon Leighton 已提交
316
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) }
317 318
  end

319
  def test_find_on_hash_conditions_with_multiple_ranges
320 321
    assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).to_a.map(&:id).sort
    assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).to_a.map(&:id).sort
322
  end
323

324
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
325
    assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).to_a.map(&:id).sort
326 327
  end

328
  def test_find_on_multiple_hash_conditions
J
Jon Leighton 已提交
329 330 331 332
    assert Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
333
  end
334

335
  def test_condition_interpolation
336
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
J
Jon Leighton 已提交
337 338 339
    assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
    assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
340 341
  end

342
  def test_condition_array_interpolation
J
Jon Leighton 已提交
343 344 345 346
    assert_kind_of Firm, Company.scoped(:where => ["name = '%s'", "37signals"]).first
    assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
    assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
D
David Heinemeier Hansson 已提交
347
  end
348

349
  def test_condition_hash_interpolation
J
Jon Leighton 已提交
350 351 352
    assert_kind_of Firm, Company.scoped(:where => { :name => "37signals"}).first
    assert_nil Company.scoped(:where => { :name => "37signals!"}).first
    assert_kind_of Time, Topic.scoped(:where => {:id => 1}).first.written_on
353
  end
354

355
  def test_hash_condition_find_malformed
356
    assert_raise(ActiveRecord::StatementInvalid) {
J
Jon Leighton 已提交
357
      Company.scoped(:where => { :id => 2, :dhh => true }).first
358 359
    }
  end
360

361 362
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
J
Jon Leighton 已提交
363
    assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first
364 365 366
  end

  def test_hash_condition_find_with_array
367 368 369
    p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').to_a
    assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').to_a
    assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').to_a
370 371 372
  end

  def test_hash_condition_find_with_nil
J
Jon Leighton 已提交
373
    topic = Topic.scoped(:where => { :last_read => nil } ).first
374 375
    assert_not_nil topic
    assert_nil topic.last_read
376
  end
D
David Heinemeier Hansson 已提交
377

378 379 380 381
  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
J
Jon Leighton 已提交
382
        assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first
383 384 385 386 387 388 389 390
      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
J
Jon Leighton 已提交
391
        assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first
392 393 394 395 396 397 398 399
      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
J
Jon Leighton 已提交
400
        assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first
401 402 403 404 405 406 407 408
      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
J
Jon Leighton 已提交
409
        assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first
410 411 412 413
      end
    end
  end

D
David Heinemeier Hansson 已提交
414
  def test_bind_variables
J
Jon Leighton 已提交
415 416 417 418
    assert_kind_of Firm, Company.scoped(:where => ["name = ?", "37signals"]).first
    assert_nil Company.scoped(:where => ["name = ?", "37signals!"]).first
    assert_nil Company.scoped(:where => ["name = ?", "37signals!' OR 1=1"]).first
    assert_kind_of Time, Topic.scoped(:where => ["id = ?", 1]).first.written_on
419
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
J
Jon Leighton 已提交
420
      Company.scoped(:where => ["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
421
    }
422
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
J
Jon Leighton 已提交
423
     Company.scoped(:where => ["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
424 425
    }
  end
426

D
David Heinemeier Hansson 已提交
427 428
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
J
Jon Leighton 已提交
429
    assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
430 431 432 433
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
J
Jon Leighton 已提交
434
    assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
435 436 437 438
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
439
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
440

441
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
442
    assert_nothing_raised                                 { bind '?', 1 }
443
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
444
  end
445

D
David Heinemeier Hansson 已提交
446 447 448
  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
449

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

J
Jon Leighton 已提交
452 453 454 455
    assert_kind_of Firm, Company.scoped(:where => ["name = :name", { :name => "37signals" }]).first
    assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!" }]).first
    assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first
    assert_kind_of Time, Topic.scoped(:where => ["id = :id", { :id => 1 }]).first.written_on
D
David Heinemeier Hansson 已提交
456 457
  end

458 459 460 461 462 463 464 465 466 467 468 469
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

470
  def test_bind_enumerable
471 472
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
473
    assert_equal '1,2,3', bind('?', [1, 2, 3])
474
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
475 476

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

479 480
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
481

482 483
    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 已提交
484 485
  end

486 487 488 489 490 491 492
  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 已提交
493 494 495
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
496 497
  end

498 499 500 501 502
  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")
503 504
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
505 506
  end

507 508 509 510 511 512 513 514
  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

515 516 517 518 519 520
  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 已提交
521
  def test_string_sanitation
522 523
    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 已提交
524 525 526 527 528 529 530 531 532
  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
533
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
534 535
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
536

537 538
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
539
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
540 541
  end

542 543 544 545 546
  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

547
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
548
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
549 550
  end

551 552
  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 已提交
553
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
J
Jon Leighton 已提交
554 555
    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
556 557
  end

558
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
559
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
560
  end
561

D
David Heinemeier Hansson 已提交
562
  def test_find_by_one_missing_attribute
563
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
564
  end
565

566
  def test_find_by_invalid_method_syntax
567 568 569 570
    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") }
571
  end
D
David Heinemeier Hansson 已提交
572 573

  def test_find_by_two_attributes
574
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
575 576 577
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

578 579 580 581
  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 已提交
582 583 584 585 586
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
587

D
David Heinemeier Hansson 已提交
588 589 590 591 592 593
  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
594
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
595 596
  end

597
  def test_find_all_with_join
J
Jon Leighton 已提交
598
    developers_on_project_one = Developer.scoped(
599
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
J
Jon Leighton 已提交
600
      :where => 'project_id=1'
601
    ).to_a
602
    assert_equal 3, developers_on_project_one.length
603 604 605
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
606
  end
607

608
  def test_joins_dont_clobber_id
J
Jon Leighton 已提交
609
    first = Firm.scoped(
610
      :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
J
Jon Leighton 已提交
611 612
      :where => 'companies.id = 1'
    ).first
613 614 615
    assert_equal 1, first.id
  end

616
  def test_joins_with_string_array
J
Jon Leighton 已提交
617
    person_with_reader_and_post = Post.scoped(
618 619 620 621 622 623 624 625
      :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

626 627
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
628
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
629 630
    end
  end
631

632 633
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
634
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
635
    assert_equal [], Post.where(id: nil)
636 637
  end

638 639 640 641 642
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
643
    assert_equal [], Post.where('id in (?)', [])
644 645 646
  end

  def test_find_by_records
647
    p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').to_a
J
Jon Leighton 已提交
648 649
    assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2]], :order => 'id asc')
    assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc')
650 651
  end

652 653 654 655 656 657 658 659 660
  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
661 662
    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")
663 664
  end

665 666
  def test_select_rows
    assert_equal(
667
      [["1", "1", nil, "37signals"],
668 669
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
670
      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?}})
671
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
672
      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?}}
673 674
  end

675
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
676
    assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).to_a.size
677

J
Jon Leighton 已提交
678
    assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address},
679
                              :order => 'author_addresses_authors.id DESC ', :limit => 3).to_a.size
680 681
  end

682
  def test_find_with_nil_inside_set_passed_for_one_attribute
J
Jon Leighton 已提交
683 684
    client_of = Company.scoped(
      :where => {
685 686 687 688 689
        :client_of => [2, 1, nil],
        :name => ['37signals', 'Summit', 'Microsoft'] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

690 691
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
692 693 694
  end

  def test_find_with_nil_inside_set_passed_for_attribute
J
Jon Leighton 已提交
695 696
    client_of = Company.scoped(
      :where => { :client_of => [nil] },
697 698 699
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

700
    assert_equal [], client_of.compact
701 702
  end

703
  def test_with_limiting_with_custom_select
J
Jon Leighton 已提交
704 705
    posts = Post.references(:authors).scoped(
      :includes => :author, :select => ' posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
706
      :limit => 3, :order => 'posts.id'
707
    ).to_a
708 709
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
710
  end
711

712
  def test_find_one_message_with_custom_primary_key
713
    Toy.primary_key = :name
714 715 716 717 718 719 720
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
  end

721
  def test_finder_with_offset_string
722
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").to_a }
723 724
  end

D
David Heinemeier Hansson 已提交
725 726 727 728 729 730 731 732
  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
733 734 735 736 737 738 739 740 741 742 743 744 745 746

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