2016-02-05 2 views
2

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

Конечно у меня есть книга модель:

class Book < ActiveRecord::Base 
    has_many :ratings 
end 

и рейтинг модели:

class Rating < ActiveRecord::Base 
    belongs_to :book 
end 

«общая оценка стоимости» рейтингового объекта рассчитывается по разным рейтинговым категориям (например, читаемость , ...). Кроме того, общий рейтинг одной книги должен быть рассчитан по всем данным рейтингам.

Теперь вопрос, который я задаю себе: должен ли я рассчитать/запросить общий рейтинг для каждой книги? КАЖДЫЙ, кто-то посещает мою страницу, или я должен добавить поле в свою модель книги, где общий рейтинг (периодически) рассчитывается и сохраняется?

EDIT: «Расчет», который я использовал бы в этом случае, является простым средним определением.

Пример: Книга имеет около 200 оценок. Каждый рейтинг - это рейтинг из 10 категорий. Поэтому я хочу определить среднее значение одного рейтинга и в конце всех 200 рейтингов.

+1

Вы можете подскажите нам немного больше о природе этого расчета? Если бы это было так просто, как числовое среднее, я бы предпочел рассчитывать его «на лету» в запросе, когда он запрашивается. –

+0

@TimBiegeleisen Да, это было бы простое среднее определение. Обновлен мой вопрос – zarathustra

ответ

2

Если усреднение этих оценок не является дорогостоящим вычислительным (т. Е. Не занимает много времени), то просто рассчитайте его на лету. Это согласуется с идеей не преждевременного оптимизирования (см. http://c2.com/cgi/wiki?PrematureOptimization).

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

class Book < ActiveRecord::Base 
    has_many :ratings, after_add :update_average_rating 

    def update_average_rating 
    update_attribute(:average_rating, average_rating) 
    end 

    def average_rating 
    rating_sum/ratings.count 
    end 

    def rating_sum 
    ratings.reduce(0) {|sum, rating| 
     sum + rating.value # assuming rating model has a value attribute 
    } 
    end 
end 

class Rating < ActiveRecord::Base 
    belongs_to :book 
end 

Примечание: Приведенный выше код предполагает наличие average_rating колонки на вашей книге таблицы в базе данных. Не забудьте добавить этот столбец с переносом.

+0

Хороший ответ, но получить БД для работы вместо «ActiveRecord :: Calculations» очень просто, и вы избегаете потенциальных проблем с памятью, вытаскивая большое количество записей в память серверов. Большинство СУРБД: es также очень хороши при выполнении расчетов очень эффективно. – max

+0

Возможно, вы захотите использовать обратную связь в ассоциации рейтингов в книге вместо того, чтобы сделать рейтинг ответственным за сохранение состояния книги (из-за [SRP] (https://en.wikipedia.org/wiki/Single_responsibility_principle)). 'has_many: рейтинги, after_add:: update_average_rating' – max

+0

@max. Хороший вопрос о SRP. Я отредактировал свой ответ. Что касается расчетов на уровне БД: да, вы правы. Я вижу, вы добавили этот ответ самостоятельно. –

1

Нет необходимости вообще пересчитывать средний общий рейтинг для посещения каждой страницы, так как он изменится только тогда, когда кто-то фактически оценит книгу. Поэтому просто используйте поле AVG_RATING или что-то вроде этого и обновите значение для каждого заданного рейтинга.

1

Считаете ли вы использование кешированной версии рейтинга.

rating = Rails.cache.fetch("book_#{id}_rating", expires_in: 5.minutes) do 
    do the actual rating calculation here 
end 
1

В большинстве случаев вы можете получить средние просто запросов к базе данных:

average = book.reviews.average(:rating) 

И в большинстве случаев ее не будет достаточно дорого, что запрос в запрос собирается быть реальной проблемой - и предварительная зрелая оптимизация может быть пустой тратой времени и ресурсов, как указывает Нейл Аткинсон.

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

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

В противном случае, если рассчитанные данные имеют высокую «скорость оттока» (многие обзоры создаются ежедневно), вы использовали бы кеширование, чтобы избежать дорогого запроса, если это возможно, но набивка данных в вашу базу данных может привести к чрезмерному количеству медленных UPDATE запросы и связать свои веб-процессы или рабочие процессы.

Есть много подходов кэширования, которые дополняют друг друга:

  • etags использовать кэширование на стороне клиента - не повторно вынести, если ответ не изменился в любом случае.
  • Кэширование фрагментов позволяет избежать запросов db и фрагментов представления повторного воспроизведения для данных, которые не изменились.
  • модель кэширования в Memcached или Redis может использоваться для предотвращения медленных запросов.
  • низкоуровневое кэширование может использоваться для хранения таких вещей, как среднее.

Для получения более подробной информации см. Caching with Rails: An overview.

2

DB

Наиболее эффективным (хотя и не обычный) способ заключается в использовании децибел уровня ALIAS столбцов, что позволяет рассчитать AVG или SUM рейтинга с каждым book вызова:

#app/models/book.rb 
class Book < ActiveRecord::Base 
    def reviews_avg category 
     cat = category ? "AND `category` = \"#{category}\"" : "" 
     sql = "SELECT AVG(`rating`) FROM `reviews` WHERE `book_id` = #{self.id} #{cat}) 
     results = ActiveRecord::Base.connection.execute(sql) 
     results.first.first.to_f 
    end 
end 

Это позволит:

@book = Book.find x 
@book.reviews_avg    # -> 3.5 
@book.reviews_avg "readability" # -> 5 

Это наиболее эффективным, так как она обрабатывается полностью БД:

enter image description here


Rails

Вы должны использовать average функциональность Rails:

#app/models/book.rb 
class Book < ActiveRecord::Base 
    has_many :ratings do 
     def average category 
     if category 
      where(category: category).average(:rating) 
     else 
      average(:rating) 
     end 
     end 
    end 
end 

Вышеприведенные w плохо дать вам возможность созывать экземпляр из @book и оценить average или total для своих оценок:

@book = Book.find x 
@book.reviews.average    #-> 3.5 
@book.reviews.average "readability" #-> 5 

-

Вы можете также использовать class method/scope на Review:

#app/models.review.rb 
class Review < ActiveRecord::Base 
    scope :avg, (category) -> { where(category: category).average(:rating) } 
end 

Это позволит вам позвонить:

@book = Book.find x 
@book.reviews.avg    #-> 3.5 
@book.reviews.avg "readability" #-> 5 

Association Расширения

Другой способ (не проверено) было бы использовать proxy_association.target объект в ActiveRecord Association Extension.

Хотя не так эффективно, как запрос DB-уровня, это даст вам возможность выполнять свою деятельность в памяти:

#app/models/book.rb 
class Book < ActiveRecord::Base 
    has_many :reviews do 
    def avg category 
     associative_array = proxy_association.target 
     associative_array = associative_array.select{|key, hash| hash["category"] == category } if category 
     ratings = associative_array.map { |a| a["rating"] } 
     ratings.inject(:+)/associative_array.size #-> 35/5 = 7 
    end 
    end 
end 

Это позволит вам позвонить:

@book = Book.find x 
@book.reviews.avg    # -> 3.5 
@book.reviews.avg "readability" # -> 5 
Смежные вопросы