Rails 3: сортировка ActiveRelation по рейтингу, подсчитанному из другой таблицы - PullRequest
1 голос
/ 30 августа 2011

Моя система оценки довольно проста, нравится ли пользователю статья или ему не нравится статья.

В принципе, у меня есть что-то вроде этого, и она отлично работает:

class Article
  has_many :ratings
  def self.by_ratings
    all.sort_by{|article| (article.ratings.where(like: true).size).to_f / (article.ratings.size) }
  end
end

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

Я пытаюсь переписать его в чистом SQL для повышения производительности.Я также пытаюсь сделать это как ActiveRelation, чтобы связать с другими условиями.Попытка также написать sql, но безуспешно.Есть идеи?

Спасибо!

Использование:

  • Rails 3.0.10
  • ActiveRecord
  • Sqlite 3 (Мы можемпереключиться на Postgresql)

Ответы [ 4 ]

2 голосов
/ 30 августа 2011

Вам нужен столбец кеша.

1) Создайте миграцию, чтобы заполнить столбец кэша:

class AddRatingCacheToArticles < ActiveRecord::Migration

  def self.up
    add_column :articles, :rating_cache, :decimal, :default => 0.0
  end

  def self.down
    remove_column :articles, :rating_cache
  end

end

2) Определите метод обновления в Article, который будет считать:

class Article < ActiveRecord::Base
  has_many :ratings

  def update_rating_cache
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f
    update_attribute(:rating_cache, current_rating)
  end
end

3) Установите обратный вызов в Rating для запуска метода update_rating_cache при их сохранении:

class Rating < ActiveRecord::Base
  belongs_to :article

  after_save :update_article_rating_cache
  after_destroy :update_article_rating_cache

  def update_article_rating_cache
    article.update_rating_cache if article
  end
end

4) Теперь сортировать статьи по рейтингу теперь очень просто:

class Article < ActiveRecord::Base
  has_many :ratings

  def self.by_ratings
    order('rating_cache DESC')
  end

  def update_rating_cache
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f
    update_attribute(:rating_cache, current_rating)
  end
end

И это можно использовать как ActiveRelation!

Удачи:)

1 голос
/ 30 августа 2011

Из вашего метода by_ratings я понимаю, что вы хотите, чтобы статьи сортировались по наиболее понравившимся отзывам / оценкам.

Можем ли мы переписать метод в такую ​​область:

scope :by_ratings, select('articles.*, ((select count(id) from ratings where article_id = articles.id) - count(article_id) ) as article_diff_count')
 .joins(:ratings).group('article_id').where('like = ?',true).order('article_diff_count asc')

Я решил сравнить difference instead of ratio между общими рейтингами и понравившимися оценками, поскольку это должно быть легче для механизма SQL. Надеюсь, это поможет.

1 голос
/ 30 августа 2011

Я делаю некоторые предположения относительно вашей реализации, например, я предполагаю, что у вас есть поле value в вашей модели рейтинга, которое может быть 1 для «нравится» и -1 для «не нравится» и т. Д.

Начните с:

class Article
   has_one :rating_value, :class_name => 'RatingValue'
end

class RatingValue < ActiveRecord::Base
   belongs_to :article
   set_table_name "rating_values"
end

Итак, при миграции вы генерируете представление (пример postgres):

   execute %q{CREATE OR REPLACE VIEW rating_values AS
 SELECT ratings.article_id, sum(ratings.value) AS value
   FROM ratings
  GROUP BY ratings.article_id;}

если у вас есть такой вид базы данных, вы можете создать область, необходимую для сортировки:

scope :ascend_by_rating, {:joins => %Q{
LEFT JOIN "rating_values"
ON rating_values.article_id = article.id },
                                          :order => "rating_values.value ASC"}

Должно быть намного эффективнее , чем ваша попытка сортировки в Ruby.

1 голос
/ 30 августа 2011

Я еще не смог протестировать этот SQL в вашей модели, но вы можете попробовать:

select articles_ratings.*, likes/total rating from (select articles.*, SUM(if(articles.like, 1, 0)) likes, count(*) total from articles JOIN ratings on article_id = articles.id GROUP BY articles.id) articles_ratings ORDER BY rating desc

Надеемся, это даст вам список статей, отсортированных по их рейтингу (по убыванию). Я могу попробовать с некоторыми рельсами, если это сработает.

РЕДАКТИРОВАТЬ Как подсказывает @socjopa, если вы не пытаетесь немедленно запустить его в производство, моей следующей рекомендацией будет перенести этот запрос в представление. Относитесь к нему, как к любому другому ActiveRecord, и соответственно связывайте его с вашими статьями.

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

Тем не менее, это исполнение должно быть днем ​​и ночью в течение вашей текущей итерации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...