views:

42

answers:

2

I have a user model and a bid model. I want the user to know what their rank is based upon a score stored as a method, i.e. "3/7" based upon user.score method. Currently, I'm trying to tuck this geek_rank method into the Bid model as:

def user_rank(my_id)
  #Finds all bids associated with parent ticket object
  bids = Bid.find_by_ticket_id(self.ticket.id)
  bids = bids.sort_by { |b| b.user.score}
  i = 0
  for b in bids
    i += 1
    if b.user_id.to_i == my_id.to_i
      myrank = i
    end
  end
  user_rank = myrank.to_s + "/" + i.to_s
end

For some reason the sort_by method works in the controller but not when I try to sort in the model. Can anyone tell me what the problem is along with how my code sucks? :)

TO CLARIFY:

The actual error I'm getting is a method missing error.

A: 

so, are the scores being returned from the method as rational numbers, eg 3/7 or are they Fixnums ?

if they are rationals then they should be able to be compared by the enumarable sort_by method:

ruby-1.8.7-p299 > require 'mathn'
 => true 
ruby-1.8.7-p299 > Rational
 => Rational 
ruby-1.8.7-p299 > Rational(3/7)
 => 3/7 
ruby-1.8.7-p299 > Rational(3/7) <=> Rational(5/7)
 => -1 

but if they are being evaluated as fixnums then 3/7 returns zero, and ruby cant compare 0 to 0 (integer division)

ruby-1.8.7-p299 > 3/7
 => 0 
ruby-1.8.7-p299 > 5/7
 => 0 
ruby-1.8.7-p299 > 3/7 <=> 5/7
 => 0 
ruby-1.8.7-p299 > 5/7 <=> 5/7
 => 0 
ruby-1.8.7-p299 > 8/7 <=> 5/7
 => 1 
ruby-1.8.7-p299 > 7/7 <=> 7/7
 => 0 
Jed Schneider
I'm getting a method missing error saying no such thing as sort_by
Kevin
if you are getting a no method error then what is coming back from your bids assignment is not a data structure that implements enumerable. can you put a trace statement before your bids sort call, (STDERR.puts bids.inpsect)
Jed Schneider
+4  A: 

The method find_by_ticket_id DOES not return an array; it returns a Bid. Use find_all_by_ticket_id instead.

bids = Bid.find_all_by_ticket_id(self.ticket.id)
bids = bids.sort_by { |b| b.user.score}

I would rewrite your method as follows:

def user_rank(my_id)
  # find the bid by the given id
  score = Bid.find_by_id(my_id).user.score

  # find the total number of bids for the ticket
  count  = Bid.count(:conditions => {:ticket_id => self.ticket.id})

  # find the rank
  rank   = Bid.count(:conditions => ["ticket_id =? AND users.score > ? ",
              self.ticket.id, score], :joins => :user) + 1
  "#{rank}/#{count}"
end

In this approach most of the calculation is done by the DB.

Caveat 1

This method will return the same rank for people with the same score.

E.g:

#name #score   #rank
foo        5        4
bar        6        2
kate       6        2
kevin      8        1

Caveat 2

This solution performs better than your solution. But it still requires n * 3 round trips to the server to calculate the rank. Solution can be further optimized to calculate the rank of all the users in one SQL.

Articles about rank calculation optimization:

Article 1

KandadaBoggu
Find_all_by_ticket_id should be the ticket. +1 to this answer
Jed Schneider
THanks so much Kandada!
Kevin