Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/branches/2-1-caching/activerecord/test/finder_test.rb

Revision 8375, 25.8 kB (checked in by bitsweat, 1 year ago)

Base.exists? doesn't rescue exceptions to avoid hiding SQL errors. Closes #10458.

Line 
1 require 'abstract_unit'
2 require 'fixtures/author'
3 require 'fixtures/comment'
4 require 'fixtures/company'
5 require 'fixtures/topic'
6 require 'fixtures/reply'
7 require 'fixtures/entrant'
8 require 'fixtures/developer'
9 require 'fixtures/post'
10
11 class FinderTest < Test::Unit::TestCase
12   fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors
13
14   def test_find
15     assert_equal(topics(:first).title, Topic.find(1).title)
16   end
17
18   # find should handle strings that come from URLs
19   # (example: Category.find(params[:id]))
20   def test_find_with_string
21     assert_equal(Topic.find(1).title,Topic.find("1").title)
22   end
23
24   def test_exists
25     assert Topic.exists?(1)
26     assert Topic.exists?("1")
27     assert Topic.exists?(:author_name => "David")
28     assert Topic.exists?(:author_name => "Mary", :approved => true)
29     assert Topic.exists?(["parent_id = ?", 1])
30     assert !Topic.exists?(45)
31
32     begin
33       assert !Topic.exists?("foo")
34     rescue ActiveRecord::StatementInvalid
35       # PostgreSQL complains about string comparison with integer field
36     rescue Exception
37       flunk
38     end
39
40     assert_raise(NoMethodError) { Topic.exists?([1,2]) }
41   end
42
43   def test_find_by_array_of_one_id
44     assert_kind_of(Array, Topic.find([ 1 ]))
45     assert_equal(1, Topic.find([ 1 ]).length)
46   end
47
48   def test_find_by_ids
49     assert_equal 2, Topic.find(1, 2).size
50     assert_equal topics(:second).title, Topic.find([2]).first.title
51   end
52
53   def test_find_by_ids_with_limit_and_offset
54     assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
55     assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
56
57     # Also test an edge case: If you have 11 results, and you set a
58     #   limit of 3 and offset of 9, then you should find that there
59     #   will be only 2 results, regardless of the limit.
60     devs = Developer.find :all
61     last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
62     assert_equal 2, last_devs.size
63   end
64
65   def test_find_an_empty_array
66     assert_equal [], Topic.find([])
67   end
68
69   def test_find_by_ids_missing_one
70     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
71   end
72
73   def test_find_all_with_limit
74     entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
75
76     assert_equal(2, entrants.size)
77     assert_equal(entrants(:first).name, entrants.first.name)
78   end
79
80   def test_find_all_with_prepared_limit_and_offset
81     entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
82
83     assert_equal(2, entrants.size)
84     assert_equal(entrants(:second).name, entrants.first.name)
85
86     entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
87     assert_equal(1, entrants.size)
88     assert_equal(entrants(:third).name, entrants.first.name)
89   end
90
91   def test_find_all_with_limit_and_offset_and_multiple_orderings
92     developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
93     assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
94   end
95
96   def test_find_with_limit_and_condition
97     developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
98     assert_equal(1, developers.size)
99     assert_equal("fixture_3", developers.first.name)
100   end
101
102   def test_find_with_entire_select_statement
103     topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
104
105     assert_equal(1, topics.size)
106     assert_equal(topics(:second).title, topics.first.title)
107   end
108
109   def test_find_with_prepared_select_statement
110     topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
111
112     assert_equal(1, topics.size)
113     assert_equal(topics(:second).title, topics.first.title)
114   end
115
116   def test_find_by_sql_with_sti_on_joined_table
117     accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id")
118     assert_equal [Account], accounts.collect(&:class).uniq
119   end
120
121   def test_find_first
122     first = Topic.find(:first, :conditions => "title = 'The First Topic'")
123     assert_equal(topics(:first).title, first.title)
124   end
125
126   def test_find_first_failing
127     first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
128     assert_nil(first)
129   end
130
131   def test_unexisting_record_exception_handling
132     assert_raises(ActiveRecord::RecordNotFound) {
133       Topic.find(1).parent
134     }
135
136     Topic.find(2).topic
137   end
138
139   def test_find_only_some_columns
140     topic = Topic.find(1, :select => "author_name")
141     assert_raises(ActiveRecord::MissingAttributeError) {topic.title}
142     assert_equal "David", topic.author_name
143     assert !topic.attribute_present?("title")
144     #assert !topic.respond_to?("title")
145     assert topic.attribute_present?("author_name")
146     assert topic.respond_to?("author_name")
147   end
148  
149   def test_find_on_blank_conditions
150     [nil, " ", [], {}].each do |blank|
151       assert_nothing_raised { Topic.find(:first, :conditions => blank) }
152     end
153   end
154  
155   def test_find_on_blank_bind_conditions
156     [ [""], ["",{}] ].each do |blank|
157       assert_nothing_raised { Topic.find(:first, :conditions => blank) }
158     end
159   end
160
161   def test_find_on_array_conditions
162     assert Topic.find(1, :conditions => ["approved = ?", false])
163     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
164   end
165
166   def test_find_on_hash_conditions
167     assert Topic.find(1, :conditions => { :approved => false })
168     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
169   end
170
171   def test_find_on_hash_conditions_with_explicit_table_name
172     assert Topic.find(1, :conditions => { 'topics.approved' => false })
173     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
174   end
175
176   def test_find_on_association_proxy_conditions
177     assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
178   end
179
180   def test_find_on_hash_conditions_with_range
181     assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
182     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
183   end
184
185   def test_find_on_hash_conditions_with_multiple_ranges
186     assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
187     assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
188   end
189
190   def test_find_on_multiple_hash_conditions
191     assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
192     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
193     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
194     assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
195   end
196
197
198   def test_condition_interpolation
199     assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
200     assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
201     assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
202     assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
203   end
204
205   def test_condition_array_interpolation
206     assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
207     assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
208     assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
209     assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
210   end
211
212   def test_condition_hash_interpolation
213     assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
214     assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
215     assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
216   end
217
218   def test_hash_condition_find_malformed
219     assert_raises(ActiveRecord::StatementInvalid) {
220       Company.find(:first, :conditions => { :id => 2, :dhh => true })
221     }
222   end
223
224   def test_hash_condition_find_with_escaped_characters
225     Company.create("name" => "Ain't noth'n like' \#stuff")
226     assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
227   end
228
229   def test_hash_condition_find_with_array
230     p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
231     assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
232     assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
233   end
234
235   def test_hash_condition_find_with_nil
236     topic = Topic.find(:first, :conditions => { :last_read => nil } )
237     assert_not_nil topic
238     assert_nil topic.last_read
239   end
240
241   def test_bind_variables
242     assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
243     assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
244     assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
245     assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
246     assert_raises(ActiveRecord::PreparedStatementInvalid) {
247       Company.find(:first, :conditions => ["id=? AND name = ?", 2])
248     }
249     assert_raises(ActiveRecord::PreparedStatementInvalid) {
250            Company.find(:first, :conditions => ["id=?", 2, 3, 4])
251     }
252   end
253
254   def test_bind_variables_with_quotes
255     Company.create("name" => "37signals' go'es agains")
256     assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
257   end
258
259   def test_named_bind_variables_with_quotes
260     Company.create("name" => "37signals' go'es agains")
261     assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
262   end
263
264   def test_bind_arity
265     assert_nothing_raised                                 { bind '' }
266     assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
267
268     assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
269     assert_nothing_raised                                 { bind '?', 1 }
270     assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
271   end
272
273   def test_named_bind_variables
274     assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
275     assert_equal '1 1', bind(':a :a', :a => 1)  # ' ruby-mode
276
277     assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
278     assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
279     assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
280     assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
281   end
282
283   def test_bind_enumerable
284     quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
285
286     assert_equal '1,2,3', bind('?', [1, 2, 3])
287     assert_equal quoted_abc, bind('?', %w(a b c))
288
289     assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
290     assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
291
292     require 'set'
293     assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
294     assert_equal quoted_abc, bind('?', Set.new(%w(a b c)))
295
296     assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
297     assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # '
298   end
299
300   def test_bind_empty_enumerable
301     quoted_nil = ActiveRecord::Base.connection.quote(nil)
302     assert_equal quoted_nil, bind('?', [])
303     assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
304     assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
305   end
306
307   def test_bind_string
308     assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
309   end
310
311   def test_bind_record
312     o = Struct.new(:quoted_id).new(1)
313     assert_equal '1', bind('?', o)
314
315     os = [o] * 3
316     assert_equal '1,1,1', bind('?', os)
317   end
318
319   def test_string_sanitation
320     assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
321     assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
322   end
323
324   def test_count
325     assert_equal(0, Entrant.count(:conditions => "id > 3"))
326     assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
327     assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
328   end
329
330   def test_count_by_sql
331     assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
332     assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
333     assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
334   end
335
336   def test_find_by_one_attribute
337     assert_equal topics(:first), Topic.find_by_title("The First Topic")
338     assert_nil Topic.find_by_title("The First Topic!")
339   end
340  
341   def test_find_by_one_attribute_caches_dynamic_finder
342     # ensure this test can run independently of order
343     class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
344     assert !Topic.respond_to?(:find_by_title)
345     t = Topic.find_by_title("The First Topic")
346     assert Topic.respond_to?(:find_by_title)
347   end
348
349   def test_dynamic_finder_returns_same_results_after_caching
350     # ensure this test can run independently of order
351     class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title)
352     t = Topic.find_by_title("The First Topic")
353     assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached
354   end
355
356   def test_find_by_one_attribute_with_order_option
357     assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
358     assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
359   end
360
361   def test_find_by_one_attribute_with_conditions
362     assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
363   end
364
365   def test_dynamic_finder_on_one_attribute_with_conditions_caches_method
366     # ensure this test can run independently of order
367     class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
368     assert !Account.respond_to?(:find_by_credit_limit)
369     a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
370     assert Account.respond_to?(:find_by_credit_limit)
371   end
372
373   def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
374     # ensure this test can run independently of order
375     class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit)
376     a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
377     assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
378   end
379
380   def test_find_by_one_attribute_with_several_options
381