Ruby Ransack 组合条件搜索实现

lanzhiheng · 2022年07月01日 · 最后由 oyaxira 回复于 2022年07月07日 · 451 次阅读

这篇文章简单总结一下如何用Ransack编写较为复杂的组合搜索方法。原文链接: https://step-by-step.tech/posts/ransack-merging-searches

预备

在日常业务中经常会需要借助组合条件进行搜索。假设我有一个用于记录博客文章的数据表,表名为posts,表结构大概如下

                                          Table "public.posts"
   Column    |              Type              | Collation | Nullable |              Default
-------------+--------------------------------+-----------+----------+-----------------------------------
 id          | bigint                         |           | not null | nextval('posts_id_seq'::regclass)
 title       | character varying              |           |          |
 body        | text                           |           |          |
 created_at  | timestamp(6) without time zone |           | not null |
 updated_at  | timestamp(6) without time zone |           | not null |
 category_id | bigint                         |           |          |

另外还有分类表categories,表结构大概如下

                                          Table "public.categories"
   Column   |              Type              | Collation | Nullable |                Default
------------+--------------------------------+-----------+----------+----------------------------------------
 id         | bigint                         |           | not null | nextval('categories_id_seq'::regclass)
 name       | character varying              |           |          |
 created_at | timestamp(6) without time zone |           | not null |
 updated_at | timestamp(6) without time zone |           | not null |

好,现在需求来了,业务那边需要我们编写一个搜索功能,可以根据文章标题 (posts.title),文章内容 (posts.body), 分类名称 (categories.name),文章 id(posts.id) 等字段进行搜索,并且除了posts.id之外其他都是模糊查找。那么在 Rails 里面编写出来的查询方法大概如这个样子

class Post < ApplicationRecord
  def self.search_by(content)
    fuzzy_content = "%#{content}%"
    left_outer_joins(:category).where('posts.title LIKE ? OR posts.body LIKE ? OR categories.name LIKE ? OR posts.id = ?', fuzzy_content, fuzzy_content, fuzzy_content, content.to_i)
  end
end

运行结果如下

Screen Shot 2022-07-01 at 08.50.11.png

Ransack 解决方案

上面的做法确实可行,然而呢,可维护性方面就稍微差了一些,并且还要写不少诸如posts.xxxcategories.xxx的语句,有点烦人。这里考虑用Ransack来生成对应的 SQL 语句。我预想对应的查询方案大概可以写成这个样子

class Post < ApplicationRecord
  ransack_alias :query_for_admin, :id_or_title_or_body_or_category_name

  scope :search_by, ->(keyword) { ransack(query_for_admin_cont: keyword).result }
end

但结果看来不太对呢

Screen Shot 2022-07-01 at 08.51.27.png

按照报错的说法是,posts.id这个字段并不能兼容ILIKE这个模糊匹配操作符,想来也是,毕竟 id 都是整型。那么接下来需要借助 Ransack 的合并查询结果 (Merging searches) 技术了,文档在这里

如此一来,则需要把posts_id = xxx这个过滤条件先分离出来,各自查询再把结果合并

class Post < ApplicationRecord
  ransack_alias :query_for_admin, :title_or_body_or_category_name

  def self.search_by(content)
    shared_context = Ransack::Context.for(self)

    search_parents = ransack(
      { query_for_admin_cont: content }, context: shared_context
    )

    search_children = ransack(
      { id_eq: content }, context: shared_context
    )

    shared_conditions = [search_parents, search_children].map { |search|
      Ransack::Visitor.new.accept(search.base)
    }

    joins(shared_context.join_sources).where(shared_conditions.reduce(&:or))
  end
end

看看结果

Screen Shot 2022-07-01 at 08.53.06.png

这就能生成跟开篇类似的 SQL 语句了,免掉自己一段段去写 SQL 语句的麻烦。

对比两种做法

使用数据库的组合搜索,最直观的方式便是直接编写 SQL 语句。然而这种做法会编写许多重复性的句子,更省事儿的做法是采用 Rails 的Ransack,它能够较为方便地去生成 SQL 语句,并且可维护性更高。然而,当搜索条件判断类型不一致的时候 Ransack 最开始显得有点力不从心,这个时候采用Merging Searches的方式可以编写出更为复杂的组合搜索。

1 楼 已删除

请教一下, 我知道 Ransack 本身是利用 SQL 的 ILIKE 来搜索的, 这种搜索只能从头连续匹配吧? 比如说Ransack组合条件搜索实现这个标题, 我用ransack (空格) 搜索, 是搜不出来的吧?

感觉真的有大需求的话还是丢 es 比较省心..单独优化索引就好

需要 登录 后方可回复, 如果你还没有账号请 注册新账号