Сортировка/Разбивка на страницы/Фильтрация сложных таблиц объектов Multi-AR в Rails

У меня есть сложная таблица, извлеченная из массива объектов с несколькими ActiveRecord. Этот список представляет собой комбинированное отображение всех «любимых» элементов конкретного пользователя (песни, сообщения, публикации в блогах и т. д.). Каждый из этих предметов является полноценным AR-объектом.

Моя цель — предоставить пользователю упрощенный интерфейс поиска, сортировки и разбиения на страницы. Пользователю не нужно знать, что у Песни есть исполнитель, а у сообщения есть автор — для конечного пользователя обе записи в таблице будут отображаться как «Пользователь». Таким образом, поле поиска будет просто выпадающим списком, в котором им будет задан вопрос, по какому из них выполнять поиск (имя пользователя, место создания и т. д.). Внутри мне нужно было бы преобразовать это в соответствующий поиск объектов, объединить результаты и отобразить.

Я могу по отдельности выполнять разбивку на страницы (mislav will_paginate), сортировку и фильтрацию, но вместе у меня возникают проблемы с их объединением.

Например, если я разбиваю комбинированный список элементов на страницы, плагин разбивки на страницы прекрасно справляется с этим. Это неэффективно, поскольку разбиение на страницы происходит в приложении, а не в БД, но давайте предположим, что предполагаемый вариант использования будет указывать на то, что подавляющее большинство пользователей будет иметь менее 30 избранных элементов и все остальное поведение, возможности сервера и т. д. указывает, что это не будет узким местом.

Однако, если я хочу отсортировать список, я не могу отсортировать его с помощью плагина разбивки на страницы, потому что он основан на предположении, что набор результатов получен из одного SQL-запроса, а также что имя поля постоянно. Таким образом, я должен отсортировать объединенный массив через ruby, например.

@items.sort_by{ |i| i.whatever }

Но, поскольку элементы не имеют общих имен, я должен сначала опросить объект, а затем вызвать правильную сортировку по. Например, если пользователь хочет выполнить сортировку по имени пользователя, если сортируемый объект является сообщением, я сортирую по автору, а если объектом является песня, я сортирую по исполнителю. Все это очень грубо и совсем не похоже на рубин.

Та же проблема возникает и с фильтром. Если пользователь фильтрует «родительский элемент» (поток сообщения, альбом песни), я должен преобразовать это в соответствующий метод объекта коллекции. Тоже грубо.

Это не точная установка, но достаточно близкая. Обратите внимание, что это устаревшее приложение, поэтому изменить его довольно сложно, хотя и возможно. Кроме того, да, есть некоторые DRY, которые можно сделать, но не сосредотачивайтесь на стиле или элегантности следующего кода. Однако важен стиль/элегантность РЕШЕНИЯ! :D

модели:

class User < ActiveRecord::Base
  ...
  has_and_belongs_to_many :favorite_messages, :class_name => "Message"
  has_and_belongs_to_many :favorite_songs,    :class_name => "Song"
  has_many                :authored_messages, :class_name => "Message"
  has_many                :sung_songs,        :class_name => "Song"
end

class Message < ActiveRecord::Base
  has_and_belongs_to_many :favorite_messages  
  belongs_to              :author, :class_name => "User"
  belongs_to              :thread
end

class Song < ActiveRecord::Base
  has_and_belongs_to_many :favorite_songs  
  belongs_to              :singer, :class_name => "User"
  belongs_to              :album
end

контроллер:

def show
  u = User.find 123

  @items = Array.new
  @items << u.favorite_messages
  @items << u.favorite_songs
  # etc. etc. 

  @items.flatten!

  @items = @items.sort_by{ |i| i.created_at }

  @items = @items.paginate :page => params[:page], :per_page => 20
end

def search

  # Assume user is searching for username like 'Bob'

  u = User.find 123

  @items = Array.new
  @items << u.favorite_messages.find( :all, :conditions => "LOWER( author ) LIKE LOWER('%bob%')" )
  @items << u.favorite_songs.find( :all, :conditions => "LOWER( singer ) LIKE ... " )
  # etc. etc. 

  @items.flatten!

  @items = @items.sort_by{ |i| determine appropriate sorting based on user selection }

  @items = @items.paginate :page => params[:page], :per_page => 20
end

просмотр:

   #index.html.erb
   ...
   <table>
     <tr>
       <th>Title (sort ASC/DESC links)</th>
       <th>Created By (sort ASC/DESC links))</th>
       <th>Collection Title (sort ASC/DESC links)</th>
       <th>Created At (sort ASC/DESC links)</th> 
     </tr>
     <% @items.each |item| do %>
       <%= render { :partial => "message", :locals => item } if item.is_a? Message %>
       <%= render { :partial => "song",    :locals => item } if item.is_a? Song %>
     <%end%>
   ...
   </table>

   #message.html.erb
   # shorthand, not real ruby
   print out message title, author name, thread title, message created at

   #song.html.erb
   # shorthand
   print out song title, singer name, album title, song created at

person Matt Rogish    schedule 30.12.2009    source источник


Ответы (3)


Вы должны попробовать мышление-сфинкс. Просто чтобы получить представление о том, как это работает, вы можете посетить мой сайт www.campzero.com и попробовать ввести в поиск «Dhaka».

Thinking-sphinx — это плагин для rails, который поможет вам в поиске по нескольким моделям/полям, и в него встроена нумерация страниц/сортировка. Подробнее на сайте thinking-sphinx.

person Sohan    schedule 30.12.2009
comment
Мне это нравится, но установка поисковой системы для этой маленькой функции кажется излишним (поскольку нетривиально запустить и запустить sphinx и т. д.) - person Matt Rogish; 30.12.2009

К вашему сведению, мне удалось сделать все это с очень толстой моделью, но, похоже, она работает довольно хорошо.

person Matt Rogish    schedule 07.01.2010

Вы также можете добавить методы #collection_title и #title к каждому типу item и выполнить сортировку с помощью утиного ввода.

person Tim Snowhite    schedule 30.04.2010