Well, this has been an adventure. Whilst looking into the final
ActiveRecord unit test that was failing for me, I discovered
interesting things and overcame an assumption I was making about SQL
Server behaviour. I'm putting this into a new thread to highlight it.
Here's the test that fails. I've put an asterisk beside the first
failing assertion, but any of the four final assertions are also
liable to fail because they are based on behaviour that isn't
guaranteed by SQL Server. I wonder where the other RDBMS's usable with
Rails stand?
1) Failure:
test_named_scopes_honor_current_scopes_from_when_defined
(NamedScopeTest)
def test_named_scopes_honor_current_scopes_from_when_defined
assert !Post.ranked_by_comments.limit(5).empty?
assert !authors(:david).posts.ranked_by_comments.limit(5).empty?
* assert_not_equal Post.ranked_by_comments.limit(5),
authors(:david).posts.ranked_by_comments.limit(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
assert_equal authors(:david).posts.ranked_by_comments.limit(5),
authors(:david).posts.top(5)
assert_equal Post.ranked_by_comments.limit(5), Post.top(5)
end
Here's what the Post class looks like:
class Post < ActiveRecord::Base
named_scope :ranked_by_comments, :order => "comments_count DESC"
end
Here's what the test data looks like in SQL Server Manager (I've
removed columns irrelevant to the issue):
SELECT * FROM posts
id author_id comments_count
-- --------- --------------
1 1 2
2 1 1
3 0 0
4 1 0
5 1 0
6 1 0
7 2 0
If I replicate the failing assertion in the Rails console, you can see
it's definitely failing for a reason:
>> a = Author.find_by_name("David")
=> #<Author id: 1, name: "David", author_address_id: 1,
author_address_extra_id: 2>
>> pp a.posts.ranked_by_comments.limit(5).map{|p| {:id =>
p.id, :author_id => p.author_id, :comments_count => p.comments_count}}
[{:comments_count=>2, :author_id=>1, :id=>1},
{:comments_count=>1, :author_id=>1, :id=>2},
{:comments_count=>0, :author_id=>1, :id=>6},
{:comments_count=>0, :author_id=>1, :id=>5},
{:comments_count=>0, :author_id=>1, :id=>4}]
=> nil
>> pp Post.ranked_by_comments.limit(5).map{|p| {:id =>
p.id, :author_id => p.author_id, :comments_count => p.comments_count}}
[{:comments_count=>2, :author_id=>1, :id=>1},
{:comments_count=>1, :author_id=>1, :id=>2},
{:comments_count=>0, :author_id=>1, :id=>6},
{:comments_count=>0, :author_id=>1, :id=>5},
{:comments_count=>0, :author_id=>1, :id=>4}]
=> nil
Both queries return the same result set.
The SQL generated is:
SELECT TOP 5 * FROM [posts] WHERE ([posts].author_id = 1) AND
([posts].author_id = 1) ORDER BY comments_count DESC
SELECT TOP 5 * FROM [posts] ORDER BY comments_count DESC
That first SQL looks a bit strange, no? But it gives the desired
effect and it wouldn't be the first time Rails has generated ugly SQL.
Now, if I run that SQL directly in SQL Server Manager:
SELECT TOP 5 * FROM [posts] WHERE ([posts].author_id = 1) AND
([posts].author_id = 1) ORDER BY comments_count DESC
id author_id comments_count
-- ---------- --------------
1 1 2
2 1 1
6 1 0
5 1 0
4 1 0
SELECT TOP 5 * FROM [posts] ORDER BY comments_count DESC
id author_id comments_count
-- --------- --------------
1 1 2
2 1 1
6 1 0
5 1 0
4 1 0
So my assumption, and that failing test's assumption, was that the
rows with a comments_count of 0 would be returned in the same order by
different queries with a an order by clause of 'comments_count DESC'
but SQL Server makes no such guarantee.
Here's a couple of related threads:
http://stackoverflow.com/questions/1424560/sql-server-2008-unexpected-results-using-select-top
http://blogs.msdn.com/conor_cunningham_msft/archive/2008/08/27/no-seatbelt-expecting-order-without-order-by.aspx
--
Nick