[英]How to sort the ActiveAdmin index based on a filter and a column
我有一個帶有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?
}
看來我在范圍內強制執行的排序優先於列的排序,但我不知道為什么。 有任何想法嗎?
當您在movie_search_name
調用Movie.where
時,您正在重置范圍鏈。 你想送where
來self
,而不是(即只刪除Movie.
部分),使先決條件得以保留。
scope :movie_name_search, -> (term) {
movie_ids = elasticSearch(term, :name).map(&id)
where(id: movie_ids) unless movie_ids.nil?
}
編輯:我現在明白這個問題
像你說的,彈性的搜索將返回數組id
在有序S,但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?
}
參考:
http://www.justinweiss.com/articles/how-to-select-database-records-in-an-arbitrary-order/
編輯II:一個真正的黑客
注意:這需要使用Ransack的最新版本的ActiveAdmin。
我們遇到的問題是過濾器在排序方面效果不佳。 所以這是新計划:讓我們在索引表中添加另一列,顯示每部電影的搜索排名。 此列僅在我們按電影名稱過濾時顯示,並且可以排序。 這種方式不會有“默認”排序,但您可以按任何方式排序,包括搜索排名。
訣竅是使用上面的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
范圍(作為類方法)並將其標記為ransackable。
應用程序/模型/ 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.