finder_test.rb 33.4 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 50 51 52 53 54 55 56
    assert_equal true, Topic.exists?(1)
    assert_equal true, Topic.exists?("1")
    assert_equal true, Topic.exists?(title: "The First Topic")
    assert_equal true, Topic.exists?(heading: "The First Topic")
    assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true)
    assert_equal true, Topic.exists?(["parent_id = ?", 1])

    assert_equal false, Topic.exists?(45)
    assert_equal false, Topic.exists?(Topic.new)
57 58

    begin
59
      assert_equal false, Topic.exists?("foo")
60 61 62 63 64 65
    rescue ActiveRecord::StatementInvalid
      # PostgreSQL complains about string comparison with integer field
    rescue Exception
      flunk
    end

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

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

75
  def test_exists_returns_true_with_one_record_and_no_args
76
    assert_equal true, Topic.exists?
77
  end
78

E
Egor Lynko 已提交
79
  def test_exists_returns_false_with_false_arg
80
    assert_equal false, Topic.exists?(false)
E
Egor Lynko 已提交
81 82
  end

83 84 85
  # 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
86 87 88 89 90
    assert_equal false, Topic.exists?(nil)
    assert_equal true, Topic.exists?

    assert_equal false, Topic.first.replies.exists?(nil)
    assert_equal true, Topic.first.replies.exists?
91 92
  end

93 94
  # ensures +exists?+ runs valid SQL by excluding order value
  def test_exists_with_order
95
    assert_equal true, Topic.order(:id).distinct.exists?
96 97
  end

98
  def test_exists_with_includes_limit_and_empty_result
99 100
    assert_equal false, Topic.includes(:replies).limit(0).exists?
    assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists?
101 102
  end

103 104
  def test_exists_with_distinct_association_includes_and_limit
    author = Author.first
105 106
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
107 108 109 110
  end

  def test_exists_with_distinct_association_includes_limit_and_order
    author = Author.first
111 112
    assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
    assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
113 114
  end

115
  def test_exists_with_empty_table_and_no_args_given
116
    Topic.delete_all
117
    assert_equal false, Topic.exists?
118
  end
119

120 121
  def test_exists_with_aggregate_having_three_mappings
    existing_address = customers(:david).address
122
    assert_equal true, Customer.exists?(:address => existing_address)
123 124 125 126
  end

  def test_exists_with_aggregate_having_three_mappings_with_one_difference
    existing_address = customers(:david).address
127
    assert_equal false, Customer.exists?(:address =>
128
      Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
129
    assert_equal false, Customer.exists?(:address =>
130
      Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
131
    assert_equal false, Customer.exists?(:address =>
132 133 134
      Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
  end

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

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

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

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

162 163 164 165
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

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

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

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

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

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

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

195 196 197 198
  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
199

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

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

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

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

240 241 242 243 244 245
  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

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

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

266 267 268 269 270 271 272 273
  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

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

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

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

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

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

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

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

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

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

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

354 355 356 357 358 359 360 361
  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

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

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

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

377
  def test_find_on_hash_conditions_with_multiple_ranges
378 379
    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
380
  end
381

382
  def test_find_on_hash_conditions_with_array_of_integers_and_ranges
383
    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
384 385
  end

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

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

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

407
  def test_condition_hash_interpolation
408 409 410
    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
411
  end
412

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

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

  def test_hash_condition_find_with_array
425 426 427
    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
428 429 430
  end

  def test_hash_condition_find_with_nil
431
    topic = Topic.all.merge!(:where => { :last_read => nil } ).first
432 433
    assert_not_nil topic
    assert_nil topic.last_read
434
  end
D
David Heinemeier Hansson 已提交
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 476 477
  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

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

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

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

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

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
539
    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
540

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

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

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

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

558 559 560 561 562 563 564 565 566 567 568 569
  class SimpleEnumerable
    include Enumerable

    def initialize(ary)
      @ary = ary
    end

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

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

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

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

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

582 583
    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 已提交
584 585
  end

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

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

607 608 609 610 611 612 613 614
  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

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

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

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

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

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

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 688 689
  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

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

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

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

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

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

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

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

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

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

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

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

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

777 778 779 780 781
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

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

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

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

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

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

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

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

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

833 834 835 836 837 838 839 840 841
  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

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

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

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

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

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