BACKGROUND: I have a set of Posts that can be voted on. I\'d like to sort Posts according to their \"vote score\" which is determined by the following equation:
You shouldn't use sort!
if you are going to assign to the same variable (it is wrong in this case), you should change the sort to:
@posts.sort!{|a, b| vote_score(b) <=> vote_score(a) }
It looks like you are counting the votes for Post each time you call another Post which is hitting the database quite a bit and probably the source of the toll on your load times, you can use a counter_cache
to count each time a vote is made and store that in the posts table. This will make it so you only do one db query to load from the posts table.
http://guides.rubyonrails.org/association_basics.html
If you are using MySQL you can do the entire thing using a query:
SELECT posts.id,
(COUNT(votes.id)/(TIME_TO_SEC(NOW()) - TIME_TO_SEC(posts.created_at))) as score
FROM posts INNER JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
ORDER BY score DESC
Or:
class Post
scope :with_score, select('posts.*')
.select('(COUNT(votes.id)/(TIME_TO_SEC(NOW()) - TIME_TO_SEC(posts.created_at))) as score')
.joins(:votes)
.group('posts.id')
.order('score DESC')
end
Which would make your entire query:
@posts = Post.with_score.all
P.S: You can then modify your Post class to use the SQL version of score if it is present. You can also make the score function cached in an instance so you don't have to re-calculate it every time you ask for a post's score:
class Post
def score
@score ||= self[:score] || (votes.count/(Time.now.utc - x.created_at.utc)
end
end
P.S: The SQLLite3 equivalent is:
strftime('%s','now') - strftime('%s',posts.created_at)