这篇文章简单总结一下如何用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
运行结果如下
上面的做法确实可行,然而呢,可维护性方面就稍微差了一些,并且还要写不少诸如posts.xxx
,categories.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
但结果看来不太对呢
按照报错的说法是,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
看看结果
这就能生成跟开篇类似的 SQL 语句了,免掉自己一段段去写 SQL 语句的麻烦。
使用数据库的组合搜索,最直观的方式便是直接编写 SQL 语句。然而这种做法会编写许多重复性的句子,更省事儿的做法是采用 Rails 的Ransack,它能够较为方便地去生成 SQL 语句,并且可维护性更高。然而,当搜索条件判断类型不一致的时候 Ransack 最开始显得有点力不从心,这个时候采用Merging Searches的方式可以编写出更为复杂的组合搜索。