finder_test.rb 31.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 49 50
  def test_exists_returns_true_with_one_record_and_no_args
    assert Topic.exists?
  end
51

52 53 54 55 56 57 58 59 60
  # 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

61 62 63 64 65
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
    assert Topic.order(:id).uniq.exists?
  end

66 67 68 69
  def test_does_not_exist_with_empty_table_and_no_args_given
    Topic.delete_all
    assert !Topic.exists?
  end
70

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
  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

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.all
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 126 127 128 129 130 131
  def test_find_with_group_and_sanitized_having_method
    developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all
    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 305
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
J
Jon Leighton 已提交
306
    assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id)
307
    assert_raise(ActiveRecord::RecordNotFound) {
J
Jon Leighton 已提交
308
      Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id)
309 310 311
    }
  end

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

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

321
  def test_find_on_hash_conditions_with_end_exclusive_range
J
Jon Leighton 已提交
322 323
    assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).all.map(&:id).sort
    assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).all.map(&:id).sort
J
Jon Leighton 已提交
324
    assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) }
325 326
  end

327
  def test_find_on_hash_conditions_with_multiple_ranges
J
Jon Leighton 已提交
328 329
    assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).all.map(&:id).sort
    assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).all.map(&:id).sort
330
  end
331

332
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
J
Jon Leighton 已提交
333
    assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).all.map(&:id).sort
334 335
  end

336
  def test_find_on_multiple_hash_conditions
J
Jon Leighton 已提交
337 338 339 340
    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) }
341
  end
342

343
  def test_condition_interpolation
344
    assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
J
Jon Leighton 已提交
345 346 347
    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
348 349
  end

350
  def test_condition_array_interpolation
J
Jon Leighton 已提交
351 352 353 354
    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 已提交
355
  end
356

357
  def test_condition_hash_interpolation
J
Jon Leighton 已提交
358 359 360
    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
361
  end
362

363
  def test_hash_condition_find_malformed
364
    assert_raise(ActiveRecord::StatementInvalid) {
J
Jon Leighton 已提交
365
      Company.scoped(:where => { :id => 2, :dhh => true }).first
366 367
    }
  end
368

369 370
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
J
Jon Leighton 已提交
371
    assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first
372 373 374
  end

  def test_hash_condition_find_with_array
375
    p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
J
Jon Leighton 已提交
376 377
    assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').all
    assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').all
378 379 380
  end

  def test_hash_condition_find_with_nil
J
Jon Leighton 已提交
381
    topic = Topic.scoped(:where => { :last_read => nil } ).first
382 383
    assert_not_nil topic
    assert_nil topic.last_read
384
  end
D
David Heinemeier Hansson 已提交
385

386 387 388
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
J
Jon Leighton 已提交
389
    found_customer = Customer.scoped(:where => {:balance => balance}).first
390 391 392 393 394 395
    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
J
Jon Leighton 已提交
396
    found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first
397 398 399 400 401 402
    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
J
Jon Leighton 已提交
403
    found_customer = Customer.scoped(:where => {:balance => balance.amount}).first
404 405 406 407 408 409
    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
J
Jon Leighton 已提交
410
    found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first
411 412 413 414 415 416
    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
J
Jon Leighton 已提交
417
    found_customer = Customer.scoped(:where => {:address => address}).first
418 419 420 421 422 423
    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
J
Jon Leighton 已提交
424
    found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first
425 426 427
    assert_equal customers(:david), found_customer
  end

428 429 430 431
  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 已提交
432
        assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first
433 434 435 436 437 438 439 440
      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 已提交
441
        assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first
442 443 444 445 446 447 448 449
      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 已提交
450
        assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first
451 452 453 454 455 456 457 458
      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 已提交
459
        assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first
460 461 462 463
      end
    end
  end

D
David Heinemeier Hansson 已提交
464
  def test_bind_variables
J
Jon Leighton 已提交
465 466 467 468
    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
469
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
J
Jon Leighton 已提交
470
      Company.scoped(:where => ["id=? AND name = ?", 2]).first
D
David Heinemeier Hansson 已提交
471
    }
472
    assert_raise(ActiveRecord::PreparedStatementInvalid) {
J
Jon Leighton 已提交
473
     Company.scoped(:where => ["id=?", 2, 3, 4]).first
D
David Heinemeier Hansson 已提交
474 475
    }
  end
476

D
David Heinemeier Hansson 已提交
477 478
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
J
Jon Leighton 已提交
479
    assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first
D
David Heinemeier Hansson 已提交
480 481 482 483
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
J
Jon Leighton 已提交
484
    assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
D
David Heinemeier Hansson 已提交
485 486 487 488
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
489
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
490

491
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
D
David Heinemeier Hansson 已提交
492
    assert_nothing_raised                                 { bind '?', 1 }
493
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
D
David Heinemeier Hansson 已提交
494
  end
495

D
David Heinemeier Hansson 已提交
496 497 498
  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
499

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

J
Jon Leighton 已提交
502 503 504 505
    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 已提交
506 507
  end

508 509 510 511 512 513 514 515 516 517 518 519
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

520
  def test_bind_enumerable
521 522
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
523
    assert_equal '1,2,3', bind('?', [1, 2, 3])
524
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
525 526

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

529 530
    assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3]))
    assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c)))
531

532 533
    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 已提交
534 535
  end

536 537 538 539 540 541 542
  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 已提交
543 544 545
  def test_bind_empty_string
    quoted_empty = ActiveRecord::Base.connection.quote('')
    assert_equal quoted_empty, bind('?', '')
546 547
  end

548 549 550 551 552
  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")
553 554
    assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars)
    assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars)
555 556
  end

557 558 559 560 561 562 563 564
  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

565 566 567 568 569 570
  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 已提交
571
  def test_string_sanitation
572 573
    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 已提交
574 575 576 577 578 579 580 581 582
  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
583
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
584 585
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
586

587 588
  def test_find_by_one_attribute_bang
    assert_equal topics(:first), Topic.find_by_title!("The First Topic")
589
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
590 591
  end

592
  def test_find_by_one_attribute_with_conditions
J
Jon Leighton 已提交
593
    assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
594 595
  end

596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
  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

630 631
  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
632
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
J
Jon Leighton 已提交
633 634
    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
635 636
  end

637
  def test_find_by_one_attribute_with_several_options
J
Jon Leighton 已提交
638
    assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
639
  end
640

D
David Heinemeier Hansson 已提交
641
  def test_find_by_one_missing_attribute
642
    assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
D
David Heinemeier Hansson 已提交
643
  end
644

645
  def test_find_by_invalid_method_syntax
646 647 648 649
    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") }
650
  end
D
David Heinemeier Hansson 已提交
651 652

  def test_find_by_two_attributes
653
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
654 655 656
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

657 658 659 660
  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 已提交
661 662 663 664 665
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
666

D
David Heinemeier Hansson 已提交
667 668 669 670 671 672
  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
673
    assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
D
David Heinemeier Hansson 已提交
674 675
  end

676
  def test_find_all_with_join
J
Jon Leighton 已提交
677
    developers_on_project_one = Developer.scoped(
678
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
J
Jon Leighton 已提交
679 680
      :where => 'project_id=1'
    ).all
681
    assert_equal 3, developers_on_project_one.length
682 683 684
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
685
  end
686

687
  def test_joins_dont_clobber_id
J
Jon Leighton 已提交
688
    first = Firm.scoped(
689
      :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
J
Jon Leighton 已提交
690 691
      :where => 'companies.id = 1'
    ).first
692 693 694
    assert_equal 1, first.id
  end

695
  def test_joins_with_string_array
J
Jon Leighton 已提交
696
    person_with_reader_and_post = Post.scoped(
697 698 699 700 701 702 703 704
      :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

705 706
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
J
Jon Leighton 已提交
707
      Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
708 709
    end
  end
710

711 712
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
A
Aaron Patterson 已提交
713
    Post.create!(:title => 'test', :body => 'it out')
J
Jon Leighton 已提交
714
    assert_equal [], Post.where(id: nil)
715 716
  end

717 718 719 720 721
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
722
    assert_equal [], Post.where('id in (?)', [])
723 724 725
  end

  def test_find_by_records
726
    p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
J
Jon Leighton 已提交
727 728
    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')
729 730
  end

731 732 733 734 735 736 737 738 739
  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
740 741
    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")
742 743
  end

744 745
  def test_select_rows
    assert_equal(
746
      [["1", "1", nil, "37signals"],
747 748
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
749
      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?}})
750
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
751
      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?}}
752 753
  end

754
  def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
J
Jon Leighton 已提交
755
    assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).all.size
756

J
Jon Leighton 已提交
757
    assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address},
758
                              :order => 'author_addresses_authors.id DESC ', :limit => 3).all.size
759 760
  end

761
  def test_find_with_nil_inside_set_passed_for_one_attribute
J
Jon Leighton 已提交
762 763
    client_of = Company.scoped(
      :where => {
764 765 766 767 768
        :client_of => [2, 1, nil],
        :name => ['37signals', 'Summit', 'Microsoft'] },
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

769 770
    assert client_of.include?(nil)
    assert_equal [2, 1].sort, client_of.compact.sort
771 772 773
  end

  def test_find_with_nil_inside_set_passed_for_attribute
J
Jon Leighton 已提交
774 775
    client_of = Company.scoped(
      :where => { :client_of => [nil] },
776 777 778
      :order => 'client_of DESC'
    ).map { |x| x.client_of }

779
    assert_equal [], client_of.compact
780 781
  end

782
  def test_with_limiting_with_custom_select
J
Jon Leighton 已提交
783 784
    posts = Post.references(:authors).scoped(
      :includes => :author, :select => ' posts.*, authors.id as "author_id"',
J
Jon Leighton 已提交
785
      :limit => 3, :order => 'posts.id'
J
Jon Leighton 已提交
786
    ).all
787 788
    assert_equal 3, posts.size
    assert_equal [0, 1, 1], posts.map(&:author_id).sort
789
  end
790

791
  def test_find_one_message_with_custom_primary_key
792
    Toy.primary_key = :name
793 794 795 796 797 798 799
    begin
      Toy.find 'Hello World!'
    rescue ActiveRecord::RecordNotFound => e
      assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
    end
  end

800
  def test_finder_with_offset_string
801
    assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").all }
802 803
  end

D
David Heinemeier Hansson 已提交
804 805 806 807 808 809 810 811
  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
812 813 814 815 816 817 818 819 820 821 822 823 824 825

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