通过关联设置 has_many
Posted
技术标签:
【中文标题】通过关联设置 has_many【英文标题】:Setting up has_many through association 【发布时间】:2012-01-31 11:03:37 【问题描述】:我有 3 个模型
class Battle < ActiveRecord::Base
has_many :battle_videos
has_many :videos, :through => :battle_videos
...
end
class BattleVideo < ActiveRecord::Base
belongs_to :battle
belongs_to :video
validates :code, :presence => true
end
class Video < ActiveRecord::Base
belongs_to :user, :foreign_key => :user_id, :class_name => "User"
...
end
BattleVideo 有属性“code”(left_side || right_side),它决定了 BATTLE SIDE(战斗中总是有 2 个边,因为总是在玩 - 1 vs. 1,team vs. team 等等)
我想在模型 video 中指定关联 (left_side_video, right_side_video),它会获取所选一侧的视频。
现在获取 SIDE 1(左)的视频 - 使用此代码
Battle.first.battle_videos.where("battle_videos.code = 'left_side'").first.video
我想要这样的战斗视频
Battle.first.left_side_video
我认为,战斗模型应该是这样的,但它不起作用(只在左侧起作用)
class Battle < ActiveRecord::Base
has_many :battle_videos
has_many :videos, :through => :battle_videos
has_many :left_side_video, :through => :battle_videos, :source => :video, :conditions => ["battle_videos.code = 'left_side'"]
has_many :right_side_video, :through => :battle_videos, :source => :video, :conditions => ["battle_videos.code = 'right_side'"]
end
更新:
工作,但不是以期望的方式。问题出在includes
链中。
模型Battle
具有作用域:all_inclusive
,它会加载所有关联
scope :all_inclusive, includes(:battle_category).includes(:bets).includes(:votes).includes(:left_side_video).includes(:right_side_video)
生成的 SQL:
Battle Load (0.6ms) SELECT `battles`.* FROM `battles` WHERE (battles.status = 1 AND valid_from < '2012-01-31 13:31:50' AND battles.valid_to > '2012-01-31 13:31:50') ORDER BY battles.id DESC
BattleCategory Load (0.1ms) SELECT `battle_categories`.* FROM `battle_categories` WHERE `battle_categories`.`id` IN (1)
Bet Load (0.1ms) SELECT `bets`.* FROM `bets` WHERE `bets`.`parent_type` = 'Battle' AND `bets`.`parent_id` IN (1, 2)
Vote Load (0.2ms) SELECT `votes`.* FROM `votes` WHERE `votes`.`parent_type` = 'Battle' AND `votes`.`parent_id` IN (1, 2)
BattleVideo Load (0.1ms) SELECT `battle_videos`.* FROM `battle_videos` WHERE `battle_videos`.`battle_id` IN (1, 2) AND (battle_videos.code = 'user_1')
Video Load (0.1ms) SELECT `videos`.* FROM `videos` WHERE `videos`.`id` IN (1, 3)
请注意,视频 2 和 4(右侧) - 不要加载。我只能访问 1,3 个视频
视频 id,battle_id(边)
[1 , 1 (lft)]
[2 , 1 (rgt)]
[3 , 2 (lft)]
[4 , 2 (rgt)]
当我从 :all_inclusive
链中删除 .includes(:right_side_video)
时,我得到了这个 sql:
Battle Load (0.6ms) SELECT `battles`.* FROM `battles` WHERE (battles.status = 1 AND valid_from < '2012-01-31 13:39:26' AND battles.valid_to > '2012-01-31 13:39:26') ORDER BY battles.id DESC
BattleCategory Load (0.1ms) SELECT `battle_categories`.* FROM `battle_categories` WHERE `battle_categories`.`id` IN (1)
Bet Load (0.1ms) SELECT `bets`.* FROM `bets` WHERE `bets`.`parent_type` = 'Battle' AND `bets`.`parent_id` IN (1,2)
Vote Load (0.2ms) SELECT `votes`.* FROM `votes` WHERE `votes`.`parent_type` = 'Battle' AND `votes`.`parent_id` IN (1,2)
BattleVideo Load (0.3ms) SELECT `battle_videos`.* FROM `battle_videos` WHERE `battle_videos`.`battle_id` IN (1,2) AND (battle_videos.code = 'left_side')
Video Load (0.1ms) SELECT `videos`.* FROM `videos` WHERE `videos`.`id` IN (1, 3)
Video Load (0.2ms) SELECT `videos`.* FROM `videos` INNER JOIN `battle_videos` ON `videos`.`id` = `battle_videos`.`video_id` WHERE `battle_videos`.`battle_id` = 1 AND (battle_videos.code = 'right_side') LIMIT 1
Video Load (0.1ms) SELECT `videos`.* FROM `videos` INNER JOIN `battle_videos` ON `videos`.`id` = `battle_videos`.`video_id` WHERE `battle_videos`.`battle_id` = 2 AND (battle_videos.code = 'right_side') LIMIT 1
现在这工作正常。但在 SQL 级别 - 它并不像我想要的那样完美。您可以看到,视频 1、3 以正确的方式加载
Video Load (0.1ms) SELECT `videos`.* FROM `videos` WHERE `videos`.`id` IN (1, 3)
但视频 2、4 由单独的 sql 加载:
Video Load (0.2ms) SELECT `videos`.* FROM `videos` INNER JOIN `battle_videos` ON `videos`.`id` = `battle_videos`.`video_id` WHERE `battle_videos`.`battle_id` = 1 AND (battle_videos.code = 'right_side') LIMIT 1
Video Load (0.1ms) SELECT `videos`.* FROM `videos` INNER JOIN `battle_videos` ON `videos`.`id` = `battle_videos`.`video_id` WHERE `battle_videos`.`battle_id` = 2 AND (battle_videos.code = 'right_side') LIMIT 1
我想要什么? 由 RUBY 生成的最终 SQL
Video Load (0.1ms) SELECT `videos`.* FROM `videos` WHERE `videos`.`id` IN (1, 2, 3, 4)
【问题讨论】:
所以你想在一个请求中获取 Rails 加载的所有视频,使用什么代码?这个:battle.videos.left_side
不可接受?
是的。你很紧张,1 个请求 - 所有视频
left_side|right_side code
存储在 assoc 模型 BattleVideo
中而不是模型 Video
所以最后你只想得到 1 个Video
的请求并且还有 3 个不同的关联? IE。没有更多的查询?看起来你应该从现有的缓存集合中按类型在 ruby 中进行所有过滤。
【参考方案1】:
为什么不尝试使用“joins”而不是“includes”?
你可以像这样创建named_scope
named_scope : left_side_video,
:select => "videos.*",
:joins => "INNER JOIN battle_videos ON battle_videos.video_id = videos.id INNER JOIN battles on battles.id = battle_videos.battle_id",
:conditions=>"battle_videos.code = 'left_side'"
尚未对此进行测试。但是完美的查询应该或多或少是相似的。
【讨论】:
【参考方案2】:我认为,您应该使用preload
而不是includes
来加载关联。
范围 :all_inclusive, preload(:battle_category, :bets, :votes, :left_side_video, :right_side_video)
preload
不影响原始查询,它对您指定的每个关联进行单独查询。
它在 rails 3.0.x 中运行良好。不知道你用的是哪个版本,可能会有所不同...
【讨论】:
以上是关于通过关联设置 has_many的主要内容,如果未能解决你的问题,请参考以下文章