Don Leatham
Don Leatham

Reputation: 2704

Rails 3 - How can you sort an AR query result by doing math on attributes?

I'm building a controller/view that provides a wide selection of player rankings (e.g. "Top 10 Leader Boards".) Using this model:

class Player < ActiveRecord::Base
  attr_accessible :name, :games_played, :games_lost, :games_won, games_exited, 
                  :total_kills, :total_deaths, :total_points, :total_coins
end

In my controller I have some obvious query results to pass to my view to populate player ranking lists:

@top_winners = Player.order("games_won DESC").limit(10)
@top_assassins = Player.order("total_kills DESC").limit(10)

I now need to add some sorted rankings that are calculations. Examples:

@most_greedy would be sorted on:      :total_coins / :games_played
@most_lethal would be sorted on:      :total_kills / :games_played
@most_vanquished would be sorted on:  :total_deaths / (:games_lost + :games_exited)

My approach is to get all the players in an array and then use Ruby's array.sort {| a,b | block } → new_array option. In the case of @most_greedy I tried this:

rich_players = Player.order("total_coins DESC").limit(30)  # only consider top 30 richest
@most_greedy = rich_players.sort {|total_coins, games_played| x / y }.slice(0, 9)

Which generates the error:

undefined local variable or method `x' for #<PlayersController:0x007fb7dac59d08>

Unfortunately my meager AR understanding and Ruby skills are failing me. How can I make this approach work? Is there a different approach to this type of problem? I didn't see anything in the AR Query Guide like this.

Upvotes: 0

Views: 1319

Answers (1)

DGM
DGM

Reputation: 26979

sort is not active record, it is plain old ruby, and uses a block with two parameters to compare both objects, which are going to be Player objects.

@most_greedy = rich_players.sort {|x, y| 
  (x.total_coins / x.games_played)  <=>  (y.total_coins / y.games_played)
}.slice(0, 9)

Or even better, using sort_by:

@most_greedy = rich_players.sort_by {|x|
  x.total_coins / x.games_played
}.slice(0, 9)

If you want to use the database to calulate (which might give different results, ie, may find a less weathly player that has a better score than by limiting to the 10 top wealthy players) you might try this... (untested)

@most_greedy = Player.select('*, total_coins/games_played as greediness').order('greediness DESC').limit(10)

Upvotes: 2

Related Questions