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

kgsdhlau  于 2022-11-19  发布在  Ruby
关注(0)|答案(1)|浏览(139)

我有一个标签系统上的多个模型是链接在一起。
系统的工作原理如下:

  • 一个顶部有很多中部
  • 一个中间有很多
    *顶部中部底部有许多标记
  • Top级别关联的标记应该限定与之关联的每个MiddleLow
  • 同样的道理也适用于与Middle关联的标记,与之关联的每个Low都将从标记“继承”。

这个机制不是在数据库级别上,最后在涉及数据库的方面,TopsMiddlesLows都有自己的标记集合,并且我最初在每个模型上实现了示例方法,这样当你调用例如low_instance.all_tags时,它会将其父Middles的标记集合和它的一个Top的标记集合连接起来。
以下是模型的外观:

#          ______________________________
#         /                              \
#      (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

问题是我希望能够使用Ransackgem搜索我的Low,并使用Low的完整标签集合(它的self标签,加上从父标签MiddlesTop继承的标签)

  • 问题 *:Ransack只适用于ActiveRecord::Relations。所以从Ransack的Angular 来看,我只能使用它们的self-tag搜索我的Lows,而不是完整的继承集合,因为这在数据库级别上不存在。

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

但是我确信我不必向数据库中添加任何内容,因为所有信息都已经在连接表中了,我不想复制这些信息,我认为这不是很酷,而且会使代码库更难理解。

我已经看到potential solutions使用有许多这样的作用域:

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

lymnna71

lymnna711#

最后找到了我想构造的查询的解决方案。
作用域如下所示:

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,会从它所属的每个middle传回tags的整个集合,加上它所属的top的集合,再加上它自己的tags,而且相异会使它成为唯一的集合。
话虽如此,这并没有完全解决我的问题,因为整个目的是将这个作用域has_many关系作为Ransack gem使用的一个属性传递,以便从我的Low模型的完整继承的标记集合中过滤掉它们。
当我发现Ransack在搜索关联时会执行急切的加载时,我感到非常失望:Rails不支持对指定范围的关联进行即时加载
所以我最终为我的标签系统实现了一个完全不同的解决方案。但是嘿,我学到了很多。

相关问题