如果关联具有limit子句,则AssociationRelation属性上的Rails sum不正确

我有一个方法可以计算模型中许多浮点属性的统计数据(主要是总和)。

模特

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 

提供最多4个浮点属性的统计信息的方法,这些属性是从其他方法调用的,具体取决于我是否获得了一个玩家或一组玩家的统计数据。 ScoredRound上的初始filter传递给方法(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 

我在ScoredRounds中总结的4个属性中的3个可能没有设置(nil)如果玩家没有赢得该游戏,那么轮次被过滤。

一切正常,直到我决定增加使用轮数的限制。 例如,如果我只想要传递给method_stats的查询中的最后25轮的状态,我会调用:

 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 

同样,我刚刚在查询中添加了limit和order子句。 所有记录都很好。

如果我在控制台中模拟程序而没有使用上述方法(或使用它们!)我会得到一个错误的总和

 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 [#,...] 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]] => => # 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 

不知道我是否在AREL中发现了一个错误或者我做错了什么。 我用Round而不是STI ScoredRound尝试了它,结果相同。

有任何想法吗?

如果您注意到,使用和不使用LIMITSUM结果都是相同的:

 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 

这是因为LIMIT影响查询返回的行数, SUM只返回一个,所以该函数适用于所有44条记录,而不是给予LIMIT的25条记录。 这不是sr.pluck(:quality).sum所发生的情况,它仅适用于查询返回的25条记录。

不知道我是否在AREL中发现了一个错误或者我做错了什么

可悲的是,99.9%的时间不是一个错误,但我们的错:(

 # 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 

如果调用sr.sum(:quality)则将质量作为列名称并计算给定列的值的总和。