2015-09-22 1 views
3

У меня есть приложение Rails с моделью Movie. Модель фильма имеет «имя» и «release_дат» в качестве обычных атрибутов, а также область, используемую для поиска названий фильмов с помощью elasticsearch.Как отсортировать индекс ActiveAdmin на основе фильтра и столбца

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?  
} 

Казалось бы, что порядок я в жизнь в рамках имеет приоритет над своего рода колонны, но я понятия не имею, почему. Есть идеи?

+1

Там не кажется, что ничего плохого с кодом выше, за исключением места, где подчеркивание должно быть в 'столбец: релиз date'. Здесь также должна быть лишняя часть 'sortable'. – ahmacleod

+1

Вы правы, после некоторой отладки я сузил проблему и обновил свой вопрос. – Nate

ответ

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?  
} 

Edit: Я понимаю, этот вопрос сейчас

Как вы говорите, Elastic Search возвращает массив id с в отсортированном порядке, но where не уважает этот порядок. Он просто извлекает записи из базы данных по мере их нахождения, пока их id s находятся в массиве. Нам нехорошо сортировать записи впоследствии как массив, потому что тогда 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.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/

Edit II: Реальный хак

Примечание: Для этого требуется последняя версия ActiveAdmin, которая использует Ransack.

Проблема, с которой мы сталкиваемся, заключается в том, что фильтры не очень хорошо подходят для сортировки. Итак, вот новый план: давайте добавим еще один столбец в нашу индексную таблицу, которая показывает поиск ранга каждого фильма. Этот столбец появится только в том случае, если мы отфильтровали по имени фильма , и оно будет сортироваться. Таким образом, не будет сортировки по умолчанию, но вы можете сортировать все, что угодно, включая ранжирование поиска.

Трюк состоит в том, чтобы вставить вычисленный столбец в запрос, используя 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 
+0

Это позволяет мне сортировать по дате, когда меня фильтруют по названию фильма, однако он не сохраняет исходный порядок фильтра, если сортировка не применяется. Например, мой оригинальный фильтр дал бы мне A1, A2, A3, A4, и теперь он дает мне A3, A1, A2, A4, когда сортировка не применяется. Я хотел бы сохранить первоначальный порядок сортировки. – Nate

+0

Моя догадка заключается в том, что эластичный поиск возвращает 'id' в суровом порядке. Вы можете попробовать добавить '.sort' после' map (&: id) '. – ahmacleod

+0

Действительно? Я предположил, что ElasticSearch вернет их в порядке с лучшим совпадением в первую очередь. В примере в моем комментарии A1 не самый маленький идентификатор, а лучший идентификатор. – Nate

Смежные вопросы