我有一个带有Movie模型的Rails应用程序。 Movie模型将'name'和'release_date'作为常规属性,以及用于使用elasticsearch搜索电影名称的范围。
class Movie < ActiveRecord::Base
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
Movie.where(id: movie_ids).reorder('').order_by_ids(movie_ids) unless movie_ids.nil?
}
end
然后我设置了我的活动管理员来显示此数据
ActiveAdmin.register Promotion do
filter :movie_name_search, as: :string, label: "Movie Name"
index do
actions
column :name
column :release date, sortable: :release_date
end
end
将一个电影名称放入搜索栏可以很好地工作,并且对release_date进行排序可以很好地完成,但我不能同时进行这两项操作。一旦我使用过滤器的电影名称,按日期排序不起作用。它仅在我删除重新排序和新订单时有效。
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
Movie.where(id: movie_ids) unless movie_ids.nil?
}
看起来我在范围内强制执行的排序优先于列的排序,但我不知道为什么。有什么想法吗?
答案 0 :(得分:1)
当您在Movie.where
中呼叫movie_search_name
时,您正在重置范围链。您希望将where
发送至self
(即只删除Movie.
部分),以便保留先前条件。
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
where(id: movie_ids) unless movie_ids.nil?
}
编辑:我现在明白了这个问题
就像你说的那样,Elastic Search会按排序顺序返回id
个数组,但where
不遵守该顺序。它只是在找到数据库时从数据库中提取记录,只要它们的id
位于数组中即可。我们之后将记录排序为数组对我们没有好处,因为ActiveRecord不能将其他子句应用于查询。它必须作为查询的一部分完成。
SQL确实提供了一种强制执行任意顺序的方法:ORDER BY CASE
,但它没有内置在Rails中。幸运的是,添加起来并不难。
假设您的搜索结果为[2, 1, 3]
。在SQL中,我们可以按照以下顺序检索它们:
SELECT * FROM movies
WHERE id IN (2, 1, 3)
ORDER BY CASE id
WHEN 2 THEN 0
WHEN 1 THEN 1
WHEN 3 THEN 2
ELSE 3 END;
为了使它与ActiveRecord兼容,我们可以为Movie添加一个类方法:
应用/模型/ movie.rb 强>
class Movie < ActiveRecord::Base
# ...
def self.order_by_ids_array(ids)
order_clause = "CASE id "
ids.each_with_index do |id, index|
order_clause << sanitize_sql_array(["WHEN ? THEN ? ", id, index])
end
order_clause << sanitize_sql_array(["ELSE ? END", ids.length])
order(order_clause)
end
end
现在,您的ActiveAdmin范围同时使用where
和order_by_ids_array
:
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
where(id: movie_ids).order_by_ids_array(movie_ids) unless movie_ids.nil?
}
参考:
{{3}}
编辑II:真正的黑客
注意:这需要使用Ransack的最新版本的ActiveAdmin。
我们遇到的问题是过滤器在排序方面不能很好地发挥作用。所以这里 新计划:让我们在索引表中添加另一列来显示搜索 每部电影的排名。此列仅在我们按电影过滤后才会显示 名称,它将是可排序的。这样就没有&#34;默认&#34;排序,但是 你可以按你想要的任何东西排序,包括搜索排名。
诀窍是使用CASE
之类的方法将计算列插入到查询中
上面,但在SELECT
条款中。我们称之为search_rank
,它可以
在任何返回的电影上访问movie.attributes['search_rank']
。
应用/管理/ movies.rb 强>
ActiveAdmin.register Movie do
filter :movie_search, as: string, label: 'Movie Name'
index do
# only show this column when a search term is present
if params[:q] && params[:q][:movie_search]
# we'll alias this column to `search_rank` in our scope so it can be sorted by
column :search_rank, sortable: 'search_rank' do |movie|
movie.attributes['search_rank']
end
end
end
end
现在在我们的模型中,我们需要定义movie_search
范围(作为类方法)
并将其标记为可以回收。
应用/模型/ movie.rb 强>
class Movie < ActiveRecord::Base
def self.ransackable_scopes(opts)
[:movie_search]
end
def self.movie_search(term)
# do search here
ids = elasticSearch(term, :name).map(&id)
# build extra column
rank_col = "(CASE movies.id "
ids.each_with_index do |id, index|
rank_col << "WHEN #{ id } THEN #{ index } "
end
rank_col << 'ELSE NULL END) AS search_rank'
select("movies.*, #{ rank_col }").where(id: ids)
end
end