使用多个联接表构造具有作用域的has_many关系时出现问题



我在多个链接在一起的模型上有一个标记系统。

该系统的工作方式如下:

  • 一个顶部有许多中间
  • 有许多
  • 顶部中间底部有许多标签
  • Top级别关联的标签应符合与其关联的每个MiddleLow
  • Middle关联的标记也是如此,与之关联的每个Low都将从标记中"继承">

这个机制不是在数据库级别上,最终与数据库有关的是,TopsMiddlesows都有自己的标记集合,我最初在每个模型上实现了实例方法,因此当您调用例如low_instance.all_tags时,它会连接其父Middles的标记集合,以及其顶部中的一个。

以下是模型的样子:

#          ______________________________
#         /                              
#      (1)                                (*)
#    [Top] (1) __ (*) [Middle] (*) __ (*) [Low]
#      (*)               (*)              (*)
#        _______________ | ______________/
#                         |
#                         *
#                       [Tags]
class Low < ApplicationRecord
has_many :low_tags, dependent: :destroy
has_many :tags, through: :low_tags
has_many :middle_foos, dependent: :destroy
has_many :middles, through: :middle_foos
end
class Middle < ApplicationRecord
belongs_to :top
has_many :middle_tags, dependent: :destroy
has_many :tags, through: :middle_tags
has_many :middle_lows, dependent: :destroy
has_many :lows, through: :middle_lows
end
class Top < ApplicationRecord
has_many :middles, dependent: :destroy
has_many :lows, dependent: :destroy
has_many :top_tags, dependent: :destroy
has_many :tags, through: :top_tags
end

### Join tables
class MiddleLow < ApplicationRecord
belongs_to :middle
belongs_to :low
end
class LowTag < ApplicationRecord
belongs_to :low
belongs_to :tag
end
class MiddleTag < ApplicationRecord
belongs_to :middle
belongs_to :tag
end
class TopTag < ApplicationRecord
belongs_to :top
belongs_to :tag
end

这真的很有魅力。问题是,我希望能够使用令人敬畏的Ransack宝石搜索我的Lows,并使用Low的完整标签集合(其自身标签,加上从父Middlesop继承的标签(

问题:Ransack仅适用于ActiveRecord::Relations。因此,从Ransack的角度来看,我只能使用它们的self标签搜索我的Lows,而不能搜索完整的继承集合,因为这在数据库级别上不存在。

我想实现的这个问题的最初解决方案是添加一个";复制";数据库级别的完整标签集合,与其他标签一起更新,我可以使用Ransack进行搜索。

但是我确信我不必向数据库添加任何内容,因为所有信息都已经在联接表中了,我有点不想复制这些信息,我认为这不是很酷,会让代码库变得不那么容易理解。

我已经看到了使用的潜在解决方案。有很多这样的范围:

has_many :all_tags, ->(low) {
unscope(.........).
left_joins(..........).
where(.........)
# Returs self tags (Low) + tags from associated Middles + tags from the Top
}

我很确定这将是最好的解决方案,但在数据库查询方面我真的不擅长,尤其是在有这么多模型和联接表的情况下。我很困惑,似乎找不到该范围内应该放什么,所以我得到了这个完整的标签集合。

因此,如果有人对此有任何线索,我们将不胜感激!

顺便说一下,使用Rails 6.1和Ruby 2.7

所以最终找到了我想要构建的查询的解决方案。

范围如下:

has_many :full_tags, lambda { |low|
where_clause = 'top_tags.top_id = ? or low_tags.low_id = ?'
where_args = [low.top_id, low.id]
if low.middles.any?
where_clause += ' or middle_tags.zone_id IN ?'
where_args << low.middle_ids
end
unscope(where: :low_id)
.left_joins(:middle_tags, :top_tags, :low_tags)
.where(where_clause, *where_args).distinct
}, class_name: 'Tag'

low的任何实例上调用xxx.full_tags都会返回它所属的每个middletags的整个集合,再加上它所属top的集合,以及它自己的tags,而distinct使它成为唯一的集合。

话虽如此,这并没有完全解决我的问题,因为整个目的是将这个作用域的has_many关系作为Ransack gem使用的属性,从它们完全继承的标签集合中筛选出我的Low模型。

当我发现Ransack在搜索关联时表现出急切的加载时,我感到非常失望:Rails不支持作用域关联上的Eager Loading

因此,我最终为我的标签系统实现了另一个完整的解决方案。但是嘿,我学到了很多。

相关内容

最新更新