我的项目中有3个模型:用户,游戏,GameVisits
class User < ActiveRecord::Base
has_many :game_visits
has_many :games, through: :game_visits
end
class Game < ActiveRecord::Base
has_many :game_visits
has_many :users, through: :game_visits
end
class GameVisit < ActiveRecord::Base
self.table_name = :users_games
enum status: [:visited, :not_visited, :unknown]
belongs_to :user
belongs_to :game
end
我的前端是用angularjs编写的,所以我想要一个函数,对于游戏列表返回哈希数组,每个哈希包含user和status或nil的名称。数据显示为表格,cols - 玩家,行 - 游戏,单元格 - 玩家在游戏中的存在。 这是这种功能的例子:
def team_json(game_ids = nil)
game_ids ||= Game.pluck(:id)
games = Game.find(game_ids)
users = self.users
result = []
games.each do |game|
record = {name: game.name, date: game.date }
users_array = []
users.each do |user|
users_array << {
name: user.name,
status: user.game_visits.find_by_game_id(game.id)
}
end
record[:users] = users_array
result << record
end
result
end
输出:
[{:name =&gt;&#34;游戏0&#34;,:date =&gt;星期五,2014年12月19日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;&#34; Bob&#34;,:status =&gt; nil}]}, {:name =&gt;&#34;游戏1&#34;,:date =&gt;星期六,2014年12月20日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;& #34; Bob&#34;,:status =&gt; nil}]}, {:name =&gt;&#34;游戏2&#34;,:date =&gt;星期五,2014年12月19日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;& #34; Bob&#34;,:status =&gt; nil}]}]
但是这个函数为GameVisits产生了大量的SQL查询:
(1.0ms) SELECT "games"."id" FROM "games"
Game Load (1.3ms) SELECT "games".* FROM "games" WHERE "games"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101)
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."team_id" = $1 [["team_id", 1]]
GameVisit Load (0.6ms) SELECT "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1 [["user_id", 7], ["game_id", 1]]
GameVisit Load (0.6ms) SELECT "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1 [["user_id", 7], ["game_id", 2]]
GameVisit Load (0.5ms) SELECT "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1 [["user_id", 7], ["game_id", 3]]
..............
我该如何优化它?
答案 0 :(得分:1)
您的user.game_visits.find_by_game_id(game.id)
正在触发所有这些查询。您可以使用预先加载来解决此问题。
将users
的作业更改为以下内容:
users = self.users.includes(:game_visits)
然后将status
中的users_array
的值分配更改为:
status: user.game_visits.find{ |gv| gv.game_id == game.id }
以上一行使用的是可枚举的find
,而不是ActiveRelation&#39}。如果您坚持使用find_by_game_id
或其他find
AR,它仍会触发查询。
另一个优化可能是为game_visits
创建哈希,这样你就可以完全绕过find
做这样的事情:
user_game_visits_hash = \
user.game_visits.each_with_object({}) do |gv, hash|
hash[gv.game_id] = gv
end
# more code here
status: user_game_visits_hash[game.id]
还有一个小问题:如果方法参数中的game_ids
为nil,则方法的前两行将不必要地触发两个查询,因为pluck
所有ID都来自游戏只是为了获取所有游戏。您可以将其更改为:
games = game_ids ? Game.find(game_ids) : Game.all