Question

> Player.joins(:game).order('games.scheduled_start ASC').last(5).sum(:ppg)

=> NoMethodError: undefined method `+' for #<GamePlayer:0x007ff543cd05d8> 


> Player.joins(:game).order('games.scheduled_start ASC').sum(:ppg)

=> #<BigDecimal:7ff543cebc20,'0.30115E3',18(18)>

So, I don't understand why .last(n) is breaking my ability to call .average and .sum on this collection.

EDIT

[19] pry(main)> GamePlayer.order('id desc').limit(10).map{|x| x.ppg.to_f}
  GamePlayer Load (0.6ms)  SELECT "game_players".* FROM "game_players" ORDER BY id desc LIMIT 10
=> [14.0, 0.75, 1.2, 0.0, 2.55, 1.19, 2.04, 2.0, 0.0, 24.68]

[20] pry(main)> GamePlayer.order('id desc').limit(10).average(:ppg).to_f
  (74.4ms)  SELECT AVG("game_players"."ppg") AS avg_id FROM "game_players" LIMIT 10
=> 8.943831900603671

[21] pry(main)> GamePlayer.order('id desc').limit(50).average(:ppg).to_f
  (73.4ms)  SELECT AVG("game_players"."ppg") AS avg_id FROM "game_players" LIMIT 50
=> 8.943831900603671

[22] pry(main)> GamePlayer.order('id desc').limit(50).map{|x| x.ppg.to_f}.sum/50
  GamePlayer Load (0.9ms)  SELECT "game_players".* FROM "game_players" ORDER BY id desc LIMIT 50
=> 3.649800000000001

Anyone have an idea what this discrepancy is about?

Was it helpful?

Solution

Well last(5) (or more generally last(n)) is a method defined on Array, which means the ActiveRecord::Relation you've build by calling Player.joins(:game).order('games.scheduled_start ASC') will be evaluated to an array and then last(5) will be called on that resulting array.

Rails adds the ability to call sum on an array, but it will work differently than on an ActiveRecord::Relation (by calling + on each object and the current sum, starting with 0).

Instead you will probably want to use this:

players = Player.joins(:game).order('games.scheduled_start DESC').limit(5)
sum = Player.joins(:game).where(id: players).sum(:ppg)

Note that i've switched from ASC to DESC, as limit will always take elements from the beginning. Now sum will actually be translated to the corresponding SQL aggregate function, which will work on the :ppg-column. The where-clause is needed in order to trigger a subquery, to ensure that sum will only work on those players selected by limit.

If you however actually want to work with an Array (which will be slower), you can use this method:

players = Player.joins(:game).order('games.scheduled_start ASC').last(5)
# builds the sum of all ppg-values of all players selected
sum = players.sum(&:ppg)

But average will only work on an ActiveRecord::Relation, as it is not defined on Array (not even in Rails). You can, of course, build it yourself, but again, the ActiveRecord::Relation method will be faster.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top