通过关联设置 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的主要内容,如果未能解决你的问题,请参考以下文章

LoadRunner 如何设置关联

关联--简单暴力的给对象设置属性

Sequelize belongsToMany 关联不通过表工作

Rails 通过关联加入

关联参数化思考时间检查点事务的设置方式

SQL数据库里面怎样设置表与表之间的关联