finder_test.rb 34.9 KB
Newer Older
1
require "cases/helper"
J
Jeremy Kemper 已提交
2 3 4 5 6 7 8 9
require 'models/author'
require 'models/comment'
require 'models/company'
require 'models/topic'
require 'models/reply'
require 'models/entrant'
require 'models/developer'
require 'models/post'
10
require 'models/customer'
D
David Heinemeier Hansson 已提交
11

12
class FinderTest < ActiveRecord::TestCase
13
  fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
D
David Heinemeier Hansson 已提交
14 15

  def test_find
16
    assert_equal(topics(:first).title, Topic.find(1).title)
D
David Heinemeier Hansson 已提交
17
  end
18

19 20 21 22 23
  # 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
24

D
David Heinemeier Hansson 已提交
25
  def test_exists
26 27 28 29 30 31
    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)
32 33 34 35 36 37 38 39 40

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

41
    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
D
David Heinemeier Hansson 已提交
42
  end
43

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
  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

D
David Heinemeier Hansson 已提交
59 60 61 62
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
63

D
David Heinemeier Hansson 已提交
64
  def test_find_by_ids
65 66 67 68 69
    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
70
    assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
71
    assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
72 73 74 75 76 77 78

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

81 82 83 84
  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

D
David Heinemeier Hansson 已提交
85
  def test_find_by_ids_missing_one
86
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
D
David Heinemeier Hansson 已提交
87
  end
88

D
David Heinemeier Hansson 已提交
89 90
  def test_find_all_with_limit
    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
91

D
David Heinemeier Hansson 已提交
92
    assert_equal(2, entrants.size)
93
    assert_equal(entrants(:first).name, entrants.first.name)
D
David Heinemeier Hansson 已提交
94 95 96
  end

  def test_find_all_with_prepared_limit_and_offset
97
    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
D
David Heinemeier Hansson 已提交
98

99 100
    assert_equal(2, entrants.size)
    assert_equal(entrants(:second).name, entrants.first.name)
101

102 103 104
    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
    assert_equal(1, entrants.size)
    assert_equal(entrants(:third).name, entrants.first.name)
D
David Heinemeier Hansson 已提交
105
  end
106

107 108 109 110
  def test_find_all_with_limit_and_offset_and_multiple_orderings
    developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
    assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
  end
111

112 113 114 115 116
  def test_find_with_limit_and_condition
    developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
    assert_equal(1, developers.size)
    assert_equal("fixture_3", developers.first.name)
  end
D
David Heinemeier Hansson 已提交
117 118 119

  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
120

D
David Heinemeier Hansson 已提交
121
    assert_equal(1, topics.size)
122
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
123
  end
124

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

D
David Heinemeier Hansson 已提交
128
    assert_equal(1, topics.size)
129
    assert_equal(topics(:second).title, topics.first.title)
D
David Heinemeier Hansson 已提交
130
  end
131

132 133 134 135
  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
136

D
David Heinemeier Hansson 已提交
137 138
  def test_find_first
    first = Topic.find(:first, :conditions => "title = 'The First Topic'")
139
    assert_equal(topics(:first).title, first.title)
D
David Heinemeier Hansson 已提交
140
  end
141

D
David Heinemeier Hansson 已提交
142 143 144 145
  def test_find_first_failing
    first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
    assert_nil(first)
  end
146 147 148 149 150 151 152 153
  
  def test_first
    assert_equal topics(:second).title, Topic.first(:conditions => "title = 'The Second Topic of the day'").title
  end
  
  def test_first_failing
    assert_nil Topic.first(:conditions => "title = 'The Second Topic of the day!'")
  end
D
David Heinemeier Hansson 已提交
154 155 156 157 158

  def test_unexisting_record_exception_handling
    assert_raises(ActiveRecord::RecordNotFound) {
      Topic.find(1).parent
    }
159

160
    Topic.find(2).topic
D
David Heinemeier Hansson 已提交
161
  end
162

163
  def test_find_only_some_columns
164
    topic = Topic.find(1, :select => "author_name")
165
    assert_raises(ActiveRecord::MissingAttributeError) {topic.title}
166 167
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
168
    #assert !topic.respond_to?("title")
169 170
    assert topic.attribute_present?("author_name")
    assert topic.respond_to?("author_name")
171
  end
J
Jeremy Kemper 已提交
172

173 174 175 176 177
  def test_find_on_blank_conditions
    [nil, " ", [], {}].each do |blank|
      assert_nothing_raised { Topic.find(:first, :conditions => blank) }
    end
  end
J
Jeremy Kemper 已提交
178

179 180 181 182 183
  def test_find_on_blank_bind_conditions
    [ [""], ["",{}] ].each do |blank|
      assert_nothing_raised { Topic.find(:first, :conditions => blank) }
    end
  end
D
David Heinemeier Hansson 已提交
184

185
  def test_find_on_array_conditions
186 187
    assert Topic.find(1, :conditions => ["approved = ?", false])
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
D
David Heinemeier Hansson 已提交
188
  end
189

190 191 192 193
  def test_find_on_hash_conditions
    assert Topic.find(1, :conditions => { :approved => false })
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
  end
194

195 196 197 198 199
  def test_find_on_hash_conditions_with_explicit_table_name
    assert Topic.find(1, :conditions => { 'topics.approved' => false })
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
  end

200 201 202 203 204 205 206 207
  def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
    david = customers(:david)
    assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
    assert_raises(ActiveRecord::RecordNotFound) {
      Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address })
    }
  end

208 209 210 211
  def test_find_on_association_proxy_conditions
    assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
  end

212 213 214 215
  def test_find_on_hash_conditions_with_range
    assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
  end
216

217 218 219 220
  def test_find_on_hash_conditions_with_multiple_ranges
    assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
    assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
  end
221

222 223 224 225 226 227
  def test_find_on_multiple_hash_conditions
    assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
  end
228

229 230 231 232 233 234 235 236

  def test_condition_interpolation
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
  end

237
  def test_condition_array_interpolation
D
David Heinemeier Hansson 已提交
238 239 240 241 242
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
  end
243

244 245 246 247 248
  def test_condition_hash_interpolation
    assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
    assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
    assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
  end
249

250 251 252 253 254
  def test_hash_condition_find_malformed
    assert_raises(ActiveRecord::StatementInvalid) {
      Company.find(:first, :conditions => { :id => 2, :dhh => true })
    }
  end
255

256 257
  def test_hash_condition_find_with_escaped_characters
    Company.create("name" => "Ain't noth'n like' \#stuff")
258 259 260 261 262 263 264 265 266 267 268 269 270
    assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
  end

  def test_hash_condition_find_with_array
    p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
    assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
    assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
  end

  def test_hash_condition_find_with_nil
    topic = Topic.find(:first, :conditions => { :last_read => nil } )
    assert_not_nil topic
    assert_nil topic.last_read
271
  end
D
David Heinemeier Hansson 已提交
272

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  def test_hash_condition_find_with_aggregate_having_one_mapping
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customer = Customer.find(:first, :conditions => {:balance => balance})
    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.find(:first, :conditions => {:gps_location => gps_location})
    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.find(:first, :conditions => {:balance => balance.amount})
    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.find(:first, :conditions => {:gps_location => gps_location.gps_location})
    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.find(:first, :conditions => {:address => address})
    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.find(:first, :conditions => {:address => address, :name => customers(:david).name})
    assert_equal customers(:david), found_customer
  end

D
David Heinemeier Hansson 已提交
315 316 317 318 319 320 321 322 323 324 325 326
  def test_bind_variables
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
    assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
    assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
      Company.find(:first, :conditions => ["id=? AND name = ?", 2])
    }
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
	   Company.find(:first, :conditions => ["id=?", 2, 3, 4])
    }
  end
327

D
David Heinemeier Hansson 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
341

D
David Heinemeier Hansson 已提交
342 343 344 345
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
    assert_nothing_raised                                 { bind '?', 1 }
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
  end
346

D
David Heinemeier Hansson 已提交
347 348 349
  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
350 351
    
    assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
352

D
David Heinemeier Hansson 已提交
353 354 355 356 357 358
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
    assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
    assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
  end

359
  def test_bind_enumerable
360 361
    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})

D
David Heinemeier Hansson 已提交
362
    assert_equal '1,2,3', bind('?', [1, 2, 3])
363
    assert_equal quoted_abc, bind('?', %w(a b c))
D
David Heinemeier Hansson 已提交
364 365

    assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
366
    assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
367 368 369

    require 'set'
    assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
370
    assert_equal quoted_abc, bind('?', Set.new(%w(a b c)))
371 372

    assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
373
    assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # '
D
David Heinemeier Hansson 已提交
374 375
  end

376 377 378 379 380 381 382
  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

383
  def test_bind_string
384
    assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
385 386
  end

387 388 389 390 391 392 393 394
  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

D
David Heinemeier Hansson 已提交
395
  def test_string_sanitation
396 397
    assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
    assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
D
David Heinemeier Hansson 已提交
398 399 400
  end

  def test_count
401 402 403
    assert_equal(0, Entrant.count(:conditions => "id > 3"))
    assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
    assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
D
David Heinemeier Hansson 已提交
404 405 406 407 408 409 410 411 412
  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
413
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
D
David Heinemeier Hansson 已提交
414 415
    assert_nil Topic.find_by_title("The First Topic!")
  end
J
Jeremy Kemper 已提交
416

417 418 419 420 421 422 423 424 425 426 427 428 429 430
  def test_find_by_one_attribute_caches_dynamic_finder
    # ensure this test can run independently of order
    class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
    assert !Topic.respond_to?(:find_by_title)
    t = Topic.find_by_title("The First Topic")
    assert Topic.respond_to?(:find_by_title)
  end

  def test_dynamic_finder_returns_same_results_after_caching
    # ensure this test can run independently of order
    class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
    t = Topic.find_by_title("The First Topic")
    assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached
  end
D
David Heinemeier Hansson 已提交
431

432
  def test_find_by_one_attribute_with_order_option
433
    assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
434 435 436 437 438 439 440
    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
  end

  def test_find_by_one_attribute_with_conditions
    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
  end

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

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
  def test_dynamic_finder_on_one_attribute_with_conditions_caches_method
    # ensure this test can run independently of order
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
    assert !Account.respond_to?(:find_by_credit_limit)
    a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
    assert Account.respond_to?(:find_by_credit_limit)
  end

  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
    # ensure this test can run independently of order
    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
    a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
    assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
  end

490 491 492
  def test_find_by_one_attribute_with_several_options
    assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
  end
493

D
David Heinemeier Hansson 已提交
494 495 496
  def test_find_by_one_missing_attribute
    assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
  end
497

498 499 500 501 502 503
  def test_find_by_invalid_method_syntax
    assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
    assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") }
    assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") }
    assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") }
  end
D
David Heinemeier Hansson 已提交
504 505

  def test_find_by_two_attributes
506
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
D
David Heinemeier Hansson 已提交
507 508 509 510 511 512
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

  def test_find_all_by_one_attribute
    topics = Topic.find_all_by_content("Have a nice day")
    assert_equal 2, topics.size
513
    assert topics.include?(topics(:first))
D
David Heinemeier Hansson 已提交
514 515 516

    assert_equal [], Topic.find_all_by_title("The First Topic!!")
  end
517

518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
  def test_find_all_by_one_attribute_that_is_an_aggregate
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customers = Customer.find_all_by_balance(balance)
    assert_equal 1, found_customers.size
    assert_equal customers(:david), found_customers.first
  end

  def test_find_all_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_customers = Customer.find_all_by_balance_and_address(balance, address)
    assert_equal 1, found_customers.size
    assert_equal customers(:david), found_customers.first
  end

  def test_find_all_by_two_attributes_with_one_being_an_aggregate
    balance = customers(:david).balance
    assert_kind_of Money, balance
    found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
    assert_equal 1, found_customers.size
    assert_equal customers(:david), found_customers.first
  end

544 545
  def test_find_all_by_one_attribute_with_options
    topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
546
    assert topics(:first), topics.last
547 548

    topics = Topic.find_all_by_content("Have a nice day", :order => "id")
549
    assert topics(:first), topics.first
550
  end
D
David Heinemeier Hansson 已提交
551

552
  def test_find_all_by_array_attribute
553
    assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
554 555
  end

D
David Heinemeier Hansson 已提交
556 557 558
  def test_find_all_by_boolean_attribute
    topics = Topic.find_all_by_approved(false)
    assert_equal 1, topics.size
559
    assert topics.include?(topics(:first))
D
David Heinemeier Hansson 已提交
560 561

    topics = Topic.find_all_by_approved(true)
562
    assert_equal 3, topics.size
563
    assert topics.include?(topics(:second))
D
David Heinemeier Hansson 已提交
564
  end
565

D
David Heinemeier Hansson 已提交
566 567 568 569 570
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
571

D
David Heinemeier Hansson 已提交
572 573
  def test_find_all_by_nil_attribute
    topics = Topic.find_all_by_last_read nil
574 575
    assert_equal 3, topics.size
    assert topics.collect(&:last_read).all?(&:nil?)
D
David Heinemeier Hansson 已提交
576
  end
577

D
David Heinemeier Hansson 已提交
578 579 580 581 582 583 584 585 586 587 588
  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_all_by_nil_and_not_nil_attributes
    topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
    assert_equal 1, topics.size
    assert_equal "Mary", topics[0].author_name
  end

589 590 591 592 593
  def test_find_or_create_from_one_attribute
    number_of_companies = Company.count
    sig38 = Company.find_or_create_by_name("38signals")
    assert_equal number_of_companies + 1, Company.count
    assert_equal sig38, Company.find_or_create_by_name("38signals")
594
    assert !sig38.new_record?
595 596 597
  end

  def test_find_or_create_from_two_attributes
598 599 600 601
    number_of_topics = Topic.count
    another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
    assert_equal number_of_topics + 1, Topic.count
    assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
602
    assert !another.new_record?
603
  end
604

605 606 607 608 609 610 611 612
  def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
    number_of_customers = Customer.count
    created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
    assert_equal number_of_customers + 1, Customer.count
    assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
    assert !created_customer.new_record?
  end

613 614 615 616 617 618 619 620 621 622 623
  def test_find_or_create_from_one_attribute_and_hash
    number_of_companies = Company.count
    sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
    assert_equal number_of_companies + 1, Company.count
    assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
    assert !sig38.new_record?
    assert_equal "38signals", sig38.name
    assert_equal 17, sig38.firm_id
    assert_equal 23, sig38.client_of
  end

624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
  def test_find_or_create_from_one_aggregate_attribute
    number_of_customers = Customer.count
    created_customer = Customer.find_or_create_by_balance(Money.new(123))
    assert_equal number_of_customers + 1, Customer.count
    assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
    assert !created_customer.new_record?
  end

  def test_find_or_create_from_one_aggregate_attribute_and_hash
    number_of_customers = Customer.count
    balance = Money.new(123)
    name = "Elizabeth"
    created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
    assert_equal number_of_customers + 1, Customer.count
    assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
    assert !created_customer.new_record?
    assert_equal balance, created_customer.balance
    assert_equal name, created_customer.name
  end

644 645 646
  def test_find_or_initialize_from_one_attribute
    sig38 = Company.find_or_initialize_by_name("38signals")
    assert_equal "38signals", sig38.name
647
    assert sig38.new_record?
648
  end
J
Jeremy Kemper 已提交
649

650 651 652 653 654 655
  def test_find_or_initialize_from_one_aggregate_attribute
    new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
    assert_equal 123, new_customer.balance.amount
    assert new_customer.new_record?
  end

656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
  def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
    c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
    assert_equal "Fortune 1000", c.name
    assert_not_equal 1000, c.rating
    assert c.valid?
    assert c.new_record?
  end

  def test_find_or_create_from_one_attribute_should_set_not_attribute_even_when_protected
    c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
    assert_equal "Fortune 1000", c.name
    assert_not_equal 1000, c.rating
    assert c.valid?
    assert !c.new_record?
  end

672 673 674 675 676
  def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
    c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
    assert_equal "Fortune 1000", c.name
    assert_equal 1000, c.rating
    assert c.valid?
J
Jeremy Kemper 已提交
677
    assert c.new_record?
678 679 680 681 682 683 684
  end

  def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
    c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
    assert_equal "Fortune 1000", c.name
    assert_equal 1000, c.rating
    assert c.valid?
J
Jeremy Kemper 已提交
685
    assert !c.new_record?
686
  end
687

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
  def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
    c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
    assert_equal "Fortune 1000", c.name
    assert_equal 1000.to_f, c.rating.to_f
    assert c.valid?
    assert c.new_record?
  end

  def test_find_or_create_should_set_protected_attributes_if_given_as_block
    c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
    assert_equal "Fortune 1000", c.name
    assert_equal 1000.to_f, c.rating.to_f
    assert c.valid?
    assert !c.new_record?
  end
  
704
  def test_dynamic_find_or_initialize_from_one_attribute_caches_method
J
Jeremy Kemper 已提交
705
    class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.respond_to?(:find_or_initialize_by_name)
706 707 708 709 710
    assert !Company.respond_to?(:find_or_initialize_by_name)
    sig38 = Company.find_or_initialize_by_name("38signals")
    assert Company.respond_to?(:find_or_initialize_by_name)
  end

711 712 713 714
  def test_find_or_initialize_from_two_attributes
    another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
    assert_equal "Another topic", another.title
    assert_equal "John", another.author_name
715
    assert another.new_record?
716
  end
717

718 719 720 721 722 723 724
  def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
    new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
    assert_equal 123, new_customer.balance.amount
    assert_equal "Elizabeth", new_customer.name
    assert new_customer.new_record?
  end

725 726 727 728 729 730 731
  def test_find_or_initialize_from_one_attribute_and_hash
    sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
    assert_equal "38signals", sig38.name
    assert_equal 17, sig38.firm_id
    assert_equal 23, sig38.client_of
    assert sig38.new_record?
  end
732

733 734 735 736 737 738 739 740 741
  def test_find_or_initialize_from_one_aggregate_attribute_and_hash
    balance = Money.new(123)
    name = "Elizabeth"
    new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
    assert_equal balance, new_customer.balance
    assert_equal name, new_customer.name
    assert new_customer.new_record?
  end

D
David Heinemeier Hansson 已提交
742 743 744 745
  def test_find_with_bad_sql
    assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
  end

746 747 748 749 750
  def test_find_with_invalid_params
    assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
    assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
  end

751 752 753 754
  def test_dynamic_finder_with_invalid_params
    assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
  end

D
David Heinemeier Hansson 已提交
755
  def test_find_all_with_limit
756
    first_five_developers = Developer.find :all, :order => 'id ASC', :limit =>  5
D
David Heinemeier Hansson 已提交
757 758 759
    assert_equal 5, first_five_developers.length
    assert_equal 'David', first_five_developers.first.name
    assert_equal 'fixture_5', first_five_developers.last.name
760

761
    no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
D
David Heinemeier Hansson 已提交
762 763
    assert_equal 0, no_developers.length
  end
764

D
David Heinemeier Hansson 已提交
765 766 767 768
  def test_find_all_with_limit_and_offset
    first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
    second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
    last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
769

D
David Heinemeier Hansson 已提交
770 771 772
    assert_equal 3, first_three_developers.length
    assert_equal 3, second_three_developers.length
    assert_equal 2, last_two_developers.length
773

D
David Heinemeier Hansson 已提交
774 775 776 777 778
    assert_equal 'David', first_three_developers.first.name
    assert_equal 'fixture_4', second_three_developers.first.name
    assert_equal 'fixture_9', last_two_developers.first.name
  end

779 780 781 782 783 784 785 786 787 788
  def test_find_all_with_limit_and_offset_and_multiple_order_clauses
    first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
    second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
    last_posts = Post.find :all, :order => ' author_id, id  ', :limit => 3, :offset => 6

    assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
    assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
    assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
  end

789
  def test_find_all_with_join
790
    developers_on_project_one = Developer.find(
791 792
      :all,
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
793 794
      :conditions => 'project_id=1'
    )
795
    assert_equal 3, developers_on_project_one.length
796 797 798
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
799
  end
800

801 802 803 804 805 806 807 808 809
  def test_joins_dont_clobber_id
    first = Firm.find(
      :first,
      :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id',
      :conditions => 'companies.id = 1'
    )
    assert_equal 1, first.id
  end

810 811 812
  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
      Post.find([1,2,3],
813
        :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
814 815
    end
  end
816

817 818
  # http://dev.rubyonrails.org/ticket/6778
  def test_find_ignores_previously_inserted_record
819
    post = Post.create!(:title => 'test', :body => 'it out')
820 821 822
    assert_equal [], Post.find_all_by_id(nil)
  end

823 824 825 826 827 828 829 830 831
  def test_find_by_empty_ids
    assert_equal [], Post.find([])
  end

  def test_find_by_empty_in_condition
    assert_equal [], Post.find(:all, :conditions => ['id in (?)', []])
  end

  def test_find_by_records
J
Jeremy Kemper 已提交
832 833 834
    p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
    assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc')
    assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc')
835 836
  end

837 838 839 840 841 842 843 844 845
  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
846 847
    assert_equal ["1","2","3","4","5","6","7","8","9"], 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"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
848 849
  end

850 851 852 853 854
  def test_select_rows
    assert_equal(
      [["1", nil, nil, "37signals"],
       ["2", "1", "2", "Summit"],
       ["3", "1", "1", "Microsoft"]],
855
      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?}})
856
    assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
857
      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?}}
858 859
  end

D
David Heinemeier Hansson 已提交
860 861 862 863 864 865 866 867 868
  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
end