Rails sum on AssociationRelation attribute is incorrect if association has limit clause

余生颓废 提交于 2019-12-11 06:17:42

问题


I have a method that computes stats (mainly sums) on a number of float attributes in a model.

The models

class GroupPlayer < ActiveRecord::Base
  belongs_to :group
  has_many :scored_rounds
  has_many :rounds, dependent: :destroy
end

class Round < ActiveRecord::Base
  belongs_to :group_player
end

class ScoredRound < Round
  # STI
end

The method that provides stats on up to 4 float attributes that is called from a other methods, depending if I'm getting stats for one player or a group of players. An initial filter on ScoredRound is passed to the method (sr)

def method_stats(method,sr,grp)
  rounds = sr.where.not(method => nil)
  number_rounds = rounds.count
  won = rounds.sum(method).round(2)
  if method == :quality
    dues = grp.options[:dues] * number_rounds
  else
    dues = grp.options["#{method.to_s}_dues"] * number_rounds
  end
  balance = (won - dues).round(2)
  perc = dues > 0 ? (won / dues).round(3) : 0.0
  [self.full_name,number_rounds,won,dues,balance,perc]
end

3 of the 4 attributes I am summing in ScoredRounds may not be set (nil) if the player did not win that game so the rounds are filtered.

Everything worked fine until I decided to add a limit on how many rounds to use. For instance if I only wanted status for the last 25 rounds in the query passed to method_stats I'd call:

def money_stats(grp,method,limit=100)
  sr = self.scored_rounds.where.not(method => nil).order(:date).reverse_order.limit(limit)
  method_stats(method,sr,grp)
end

Again, I just added the limit and order clause to the query. Worked fine for all records.

If I simulate the procedure in the console with out using the above methods (or using them!) I'll get an erroneous sum

gp = GroupPlayer.find(123)
  GroupPlayer Load (2.1ms)  SELECT  "group_players".* FROM "group_players" WHERE "group_players"."id" = $1 LIMIT $2  [["id", 123], ["LIMIT", 1]]
  => valid group player

sr = gp.scored_rounds.where.not(:quality => nil)
  ScoredRound Load (1.7ms)  SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL)  [["group_player_id", 123]]

  => #<ActiveRecord::AssociationRelation [#<ScoredRound id: 5706, player_id: 123, group_player_id: 123, event_id: 12, type: "ScoredRound", date: "2016-11-04", team: 3, tee: "White", quota: 32, front: 15, back: 15, total: 30, created_at: "2016-11-04 14:18:27", updated_at: "2016-11-04 19:12:47", quality: 0.0, skins: nil, par3: nil, other: nil>,...]

sr.count
   (1.5ms)  SELECT COUNT(*) FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL)  [["group_player_id", 123]]
  => 44

sr.sum(:quality)
   (1.0ms)  SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL)  [["group_player_id", 123]]
  => 354.166666666667

# Now if I add the order and limit clause

sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
  ScoredRound Load (1.6ms)  SELECT  "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) ORDER BY "rounds"."date" DESC LIMIT $2  [["group_player_id", 123], ["LIMIT", 25]]
  => => #<ActiveRecord::AssociationRelation [...]

sr.count
   (1.1ms)  SELECT COUNT(count_column) FROM (SELECT  1 AS count_column FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2) subquery_for_count  [["group_player_id", 123], ["LIMIT", 25]]
=> 25

sr.sum(:quality)
   (1.8ms)  SELECT  SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2  [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667

###  This is the error, it return the sum off all records, 
# not the limited???? if I use pluck and sum

sr.pluck(:quality)
=> [10.0, 11.3333333333333, 10.0, 34.0, 0.0, 7.33333333333333, 0.0, 0.0, 31.5, 0.0, 21.3333333333333, 0.0, 19.0, 0.0, 0.0, 7.5, 0.0, 20.0, 10.0, 28.0, 8.0, 9.5, 0.0, 3.0, 24.0]

sr.pluck(:quality).sum
=> 254.49999999999994

Don't know if I found a bug in AREL or I'm doing something wrong. I tried it with just Round instead of the STI ScoredRound with the same results.

Any ideas?


回答1:


If you notice, the SUM results for both, with and without LIMIT, are the same:

sr = gp.scored_rounds.where.not(:quality => nil)
sr.sum(:quality)
   (1.0ms)  SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL)  [["group_player_id", 123]]
  => 354.166666666667

sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
sr.sum(:quality)
   (1.8ms)  SELECT  SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2  [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667

That's because LIMIT affects the number of rows returned by the query and SUM returns just one, so the function is applied for all the 44 records, not the 25 given to LIMIT. That's not what happens with sr.pluck(:quality).sum which applies only to the 25 records returned by the query.

Don't know if I found a bug in AREL or I'm doing something wrong

Sadly, 99.9% of times is not a bug but our fault :(




回答2:


# File activerecord/lib/active_record/relation/calculations.rb, line 75
def sum(column_name = nil)
  return super() if block_given?
  calculate(:sum, column_name)
end

if you call sr.sum(:quality) then sum take quality as a column name and Calculates the sum of values on a given column.



来源:https://stackoverflow.com/questions/43955310/rails-sum-on-associationrelation-attribute-is-incorrect-if-association-has-limit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!