基于多关联表构建带作用域的has_many关联以实现Ransack标签搜索
解决方案:为Low模型添加包含继承标签的关联以支持Ransack搜索
要实现无需冗余存储、能被Ransack识别的完整标签集合,你可以在Low模型中通过自定义SQL的has_many关联来合并自身、关联Middle及Top的标签,具体实现如下:
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 # 定义包含所有继承标签的关联 has_many :all_tags, ->(low) { from( <<-SQL.squish, 'tags' SELECT tags.* FROM tags JOIN low_tags ON low_tags.tag_id = tags.id WHERE low_tags.low_id = #{low.id} UNION SELECT tags.* FROM tags JOIN middle_tags ON middle_tags.tag_id = tags.id JOIN middle_lows ON middle_lows.middle_id = middle_tags.middle_id WHERE middle_lows.low_id = #{low.id} UNION SELECT tags.* FROM tags JOIN top_tags ON top_tags.tag_id = tags.id JOIN middles ON middles.top_id = top_tags.top_id JOIN middle_lows ON middle_lows.middle_id = middles.id WHERE middle_lows.low_id = #{low.id} SQL ) }, class_name: 'Tag', primary_key: :id, foreign_key: :id # 允许Ransack搜索该关联 def self.ransackable_associations(auth_object = nil) super + ['all_tags'] end end
代码说明
- 关联逻辑:
- 通过
UNION合并三个标签来源:Low自身标签、关联Middle的标签、关联Top的标签(通过Middle间接关联) UNION会自动去重重复标签,若需保留重复可替换为UNION ALL
- 通过
- Rails关联配置:
- 使用
class_name: 'Tag'指定关联模型为Tag primary_key和foreign_key用于匹配自定义SQL结果集与Tag模型的主键
- 使用
- Ransack支持:
- 重写
ransackable_associations方法,将all_tags加入可搜索关联列表,确保Ransack能识别该关联
- 重写
使用示例
你可以直接通过Ransack基于all_tags进行搜索:
# 搜索包含名称为"ruby"标签的Low实例 Low.ransack(all_tags_name_eq: 'ruby').result # 搜索包含任意指定标签的Low实例 Low.ransack(all_tags_name_in: %w[rails backend]).result
注意事项
原模型中Low关联Middle的join table命名为middle_foos,但Middle中使用的是middle_lows,若数据库实际join table为middle_lows(对应MiddleLow模型),需将Low中的middle_foos关联修正为middle_lows,否则会导致关联查询失败。
内容的提问来源于stack exchange,提问作者Slth
相关产品推荐
相关产品推荐

