【问题标题】:Multiple Associations To Same Model With Has Many Through同一模型的多个关联有很多通过
【发布时间】:2014-04-20 17:42:39
【问题描述】:

我目前有以下型号:

class Base < ActiveRecord::Base
  has_many :base_tags
  has_many :tags, through: :base_tags
  has_many :primary_tags, through: :base_tags, class_name: 'Tag', source: :tag, conditions: ['base_tags.primary = ?', true]
  has_and_belongs_to_many :channels
end

class Tag < ActiveRecord::Base
  has_many :base_tags
  has_many :bases, through: :base_tags
end

class BaseTag < ActiveRecord::Base
  belongs_to :base
  belongs_to :tag
end

我正在努力在基本模型上实现主要标签。在基础实例上调用 #primary_tags 会重新调整正确的记录,但同时尝试创建/更新记录:

Base.create({tag_ids: [1,2], primary_tag_ids: [1]})

我遇到了以下错误:

ActiveRecord::RecordNotUnique

Mysql2::Error: Duplicate entry '1-2' for key 'index_bases_tags_on_base_id_and_tag_id': INSERT INTO `base_tags` (`base_id`, `primary`, `tag_id`) VALUES (1, 0, 2)

ActiveRecord 正在尝试为 primary_tag_ids 创建与 tag_ids 相同的关联,而实际上它应该更新关系并且主要属性应该是 1。

有什么方法可以让 ActiveRecord 玩得更好?我想我的 has_many :primary_tags 关系不正确。

【问题讨论】:

  • 你想用 :primary_tags 属性做什么?
  • 我希望 primary_tags 关联成为具有所有适当方法的标签的子集。所以我可以调用 base.primary_tags 来获取主要标签的数组,或者调用 base.primary_tags= 来将它们设置为其他东西。

标签: ruby-on-rails activerecord


【解决方案1】:

我认为有两个问题。

首先,如果你使用字符串形式的条件,那么activerecord就无法解析那个sql片段并理解它应该设置什么属性。

其次,因为这是一个有很多通过的,我认为那些条件需要在join模型关联上

像这样的模型

class Base < ActiveRecord::Base
  has_many :base_tags
  has_many :primary_base_tags,  conditions: {primary:  true}, class_name: 'BaseTag'
  has_many :tags, through: :base_tags
  has_many :primary_tags, through: :primary_base_tags, class_name: 'Tag', source: :tag
end

然后 activerecord 为我正确设置了主要标志。

【讨论】:

  • 谢谢,这就是我要找的。我在使用此解决方案时遇到的唯一问题是,由于 base_id 和 tag_id 已经存在,推送标签列表中已经存在的 primary_tag 会给我一个 RecordNotUnique 错误。
  • 如果某物既是主标签又是标签是合法的,那么您必须更改索引以包含主列。
  • 这个实现有一个小问题。数据库将毫不犹豫地保存相同的 base_tag 两次,其中主要分别设置为 true 和 false,因为它们都是唯一的。
  • 在这种情况下,不要让索引包含主列,并且正如您之前的评论所指出的那样,如果您这样做,您将收到 RecordNotUnique 错误
【解决方案2】:

此解决方案可能适合您。我还添加了访问primary_tags 的替代版本,但由于您的版本有效,您可能不需要它。

class Base < ActiveRecord::Base
  has_many :base_tags
  has_many :tags, through: :base_tags
  has_and_belongs_to_many :channels

  def primary_tags
    self.tags.includes(:base_tags).where(base_tags: { primary: true } )
  end

  def primary_tags_ids=(ids)
    current_ids = self.base_tags.map(&:tag_id)
    ids.each do |id|
      if current_ids.include?(id)
        self.base_tags.select { |bt| bt.tag_id == id }.first.primary = true
      else
        self.base_tags.build( { tag_id: id, primary: true } )
      end
    end
  end
end

【讨论】:

    猜你喜欢
    • 2013-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多