views:

192

answers:

1

Imagine something like a model User who has many Friends, each of who has many Comments, where I'm trying to display to the user the latest 100 comments by his friends.

Is it possible to draw out the latest 100 in a single SQL query, or am I going to have to use Ruby application logic to parse a bigger list or make multiple queries?

I see two ways of going about this:

  1. starting at User.find and use some complex combination of :join and :limit. This method seems promising, but unfortunately, would return me users and not comments, and once I get those back, I'd have lots of models taking up memory (for each Friend and the User), lots of unnecessary fields being transferred (everything for the User, and everything about the name row for the Friends), and I'd still have to step through somehow to collect and sort all the comments in application logic.
  2. starting at the Comments and using some sort of find_by_sql, but I just can't seem to figure out what I'd need to put in. I don't know how you could have the necessary information to pass in with this to limit it to only looking at comments made by friends.

Edit: I'm having some difficult getting EmFi's solution to work, and would appreciate any insight anyone can provide.

Friends are a cyclic association through a join table.

has_many :friendships
has_many :friends, 
         :through => :friendships,
         :conditions => "status = #{Friendship::FULL}"

This is the error I'm getting in relevant part:

ERROR: column users.user_id does not exist

: SELECT "comments".* FROM "comments"  INNER JOIN "users" ON "comments".user_id = "users".id    WHERE (("users".user_id = 1) AND ((status = 2)))

When I just enter user.friends, and it works, this is the query it executes:

: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

So it seems like it's mangling the :through to have two :through's in one query.

+1  A: 

Given the following relationships:

class User < ActiveRecord::Base
  has_many :friends           
  has_many :comments

  has_many :friends_comments, :through => :friends, :source => :comments
end

This statement will execute a single SQL statement. Associations essentially create named scopes for you that aren't evaluated until the end of the chain.

@user.friends_comments.find(:limit => 100, :order => 'created_at DESC')

If this is a common query, the find can be simplified into its own named scope.

class Comments < ActiveRecord::Base
  belongs_to :user

  named_scope :recent, :limit => 100, :order => 'created_at DESC'
end

So now you can do:

@user.friends_comments.recent

N.B.: The friends association on user may be a cyclical one through a join table, but that's not important to this solution. As long as friends is a working association on User, the preceding will work.

EmFi
Actually, I'm having some trouble getting your solution to work, but the lack of formatting in comments makes it difficult for me to describe here, so I'll put it in my post.
WIlliam Jones