You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails中如何筛选标题/描述含指定字符串或标签匹配的文章

解决方案:Rails多条件文章搜索(含关联标签)

环境与需求

  • Ruby: 3.2.2
  • Rails: 7.1.0
  • PostgreSQL: 16

需求:实现文章搜索接口GET /search/articles?keywords=programming,筛选满足以下任一条件的文章:

  • 标题(name)包含指定关键词
  • 描述包含指定关键词
  • 关联标签的名称包含指定关键词
    同时兼容Kaminari分页。

现有模型代码

class Tag < ApplicationRecord
  has_many :tagable_tags
  has_many :articles, through: :tagable_tags, source: :tagable, source_type: "Article"
end

class TagableTag < ApplicationRecord
  belongs_to :tag
  belongs_to :tagable, polymorphic: true
end

class Article < ApplicationRecord
  has_many :tagable_tags, as: :tagable, dependent: :destroy
  has_many :tags, through: :tagable_tags

  def self.find_by_keywords(keywords, page = 1, per = 50)
    sanitized_keywords = sanitize_sql_like(keywords.downcase)
    # 待完善逻辑
  end
end

原方案问题分析

之前尝试的查询使用includes(:tags).references(:tags)会触发内连接,导致无关联标签的文章被直接排除,无法匹配标题含关键词的场景。

完善后的find_by_keywords方法

class Article < ApplicationRecord
  has_many :tagable_tags, as: :tagable, dependent: :destroy
  has_many :tags, through: :tagable_tags

  def self.find_by_keywords(keywords, page = 1, per = 50)
    sanitized_keywords = sanitize_sql_like(keywords.downcase)
    keyword_pattern = "%#{sanitized_keywords}%"

    left_outer_joins(:tags)
      .where(
        "lower(articles.name) LIKE ? OR lower(articles.description) LIKE ? OR lower(tags.name) LIKE ?",
        keyword_pattern, keyword_pattern, keyword_pattern
      )
      .distinct
      .page(page)
      .per(per)
  end
end

关键说明

  1. 左外连接:left_outer_joins(:tags)确保所有文章都被保留,即使没有关联标签,解决了无标签文章被过滤的问题。
  2. OR条件组合:同时匹配标题、描述、标签三个维度的关键词,满足任一条件即返回文章。
  3. 去重处理:distinct避免同一文章因多个标签匹配关键词而被重复返回。
  4. 分页兼容:直接链式调用Kaminari的page和per方法,天然支持分页功能。

测试用例验证

  • 标题含关键词的无标签文章:匹配第一个OR条件,正常返回。
  • 标题不含但标签含关键词的文章:匹配第三个OR条件,正常返回。
  • 标题和标签均不含关键词的文章:不匹配任何条件,不会返回。

内容的提问来源于stack exchange,提问作者Stainz42

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 03:34:50