2015-09-21 20:28:20 +02:00
require_dependency " distributed_memoizer "
2013-12-24 00:50:36 +01:00
class TopTopic < ActiveRecord :: Base
belongs_to :topic
2015-09-21 20:28:20 +02:00
def self . topics_per_period ( period )
DistributedMemoizer . memoize ( " #{ Discourse . current_hostname } _topics_per_period_ #{ period } " , 1 . day ) do
TopTopic . where ( " #{ period } _score > 0 " ) . count
end . to_i
end
2014-08-01 16:24:48 -04:00
# The top topics we want to refresh often
def self . refresh_daily!
2013-12-24 00:50:36 +01:00
transaction do
2014-02-28 20:33:52 +01:00
remove_invisible_topics
add_new_visible_topics
2014-08-01 16:24:48 -04:00
2014-08-12 19:42:46 +02:00
update_counts_and_compute_scores_for ( :daily )
2014-08-01 16:24:48 -04:00
end
end
# We don't have to refresh these as often
def self . refresh_older!
2015-03-06 18:00:29 +11:00
older_periods = periods - [ :daily , :all ]
2014-08-01 16:24:48 -04:00
transaction do
2014-08-12 19:42:46 +02:00
older_periods . each do | period |
update_counts_and_compute_scores_for ( period )
2013-12-24 00:50:36 +01:00
end
end
2015-03-06 18:00:29 +11:00
compute_top_score_for ( :all )
2013-12-24 00:50:36 +01:00
end
2014-08-01 16:24:48 -04:00
def self . refresh!
2014-10-04 09:53:46 +05:30
refresh_daily!
refresh_older!
2014-08-01 16:24:48 -04:00
end
2014-08-12 19:42:46 +02:00
2014-08-19 07:28:21 +05:30
def self . periods
2015-07-28 12:31:31 -04:00
@@periods || = [ :all , :yearly , :quarterly , :monthly , :weekly , :daily ] . freeze
2014-08-19 07:28:21 +05:30
end
2014-08-12 19:42:46 +02:00
2015-09-21 20:28:20 +02:00
def self . sorted_periods
2016-01-08 16:23:52 +05:30
ascending_periods || = Enum . new ( daily : 1 ,
weekly : 2 ,
monthly : 3 ,
quarterly : 4 ,
yearly : 5 ,
all : 6 )
2015-09-21 20:28:20 +02:00
end
2014-08-19 07:28:21 +05:30
def self . sort_orders
2015-03-06 18:00:29 +11:00
@@sort_orders || = [ :posts , :views , :likes , :op_likes ] . freeze
2014-08-19 07:28:21 +05:30
end
2014-08-12 19:42:46 +02:00
2014-08-19 07:28:21 +05:30
def self . update_counts_and_compute_scores_for ( period )
2014-10-04 09:53:46 +05:30
sort_orders . each do | sort |
2014-08-19 07:28:21 +05:30
TopTopic . send ( " update_ #{ sort } _count_for " , period )
2014-08-12 19:42:46 +02:00
end
2014-10-04 09:53:46 +05:30
compute_top_score_for ( period )
2014-08-19 07:28:21 +05:30
end
2014-08-12 19:42:46 +02:00
2014-08-19 07:28:21 +05:30
def self . remove_invisible_topics
exec_sql ( " WITH category_definition_topic_ids AS (
2014-08-12 19:42:46 +02:00
SELECT COALESCE ( topic_id , 0 ) AS id FROM categories
) , invisible_topic_ids AS (
SELECT id
FROM topics
WHERE deleted_at IS NOT NULL
OR NOT visible
OR archetype = :private_message
OR archived
OR id IN ( SELECT id FROM category_definition_topic_ids )
)
DELETE FROM top_topics
WHERE topic_id IN ( SELECT id FROM invisible_topic_ids ) " ,
2014-08-19 07:28:21 +05:30
private_message : Archetype :: private_message )
end
2014-08-12 19:42:46 +02:00
2014-08-19 07:28:21 +05:30
def self . add_new_visible_topics
exec_sql ( " WITH category_definition_topic_ids AS (
2014-08-12 19:42:46 +02:00
SELECT COALESCE ( topic_id , 0 ) AS id FROM categories
) , visible_topics AS (
SELECT t . id
FROM topics t
LEFT JOIN top_topics tt ON t . id = tt . topic_id
WHERE t . deleted_at IS NULL
AND t . visible
AND t . archetype < > :private_message
AND NOT t . archived
AND t . id NOT IN ( SELECT id FROM category_definition_topic_ids )
AND tt . topic_id IS NULL
2014-02-28 20:33:52 +01:00
)
2014-08-12 19:42:46 +02:00
INSERT INTO top_topics ( topic_id )
SELECT id FROM visible_topics " ,
2014-08-19 07:28:21 +05:30
private_message : Archetype :: private_message )
end
2014-02-28 20:33:52 +01:00
2014-08-19 07:28:21 +05:30
def self . update_posts_count_for ( period )
sql = " SELECT topic_id, GREATEST(COUNT(*), 1) AS count
2014-08-12 19:42:46 +02:00
FROM posts
WHERE created_at > = :from
AND deleted_at IS NULL
AND NOT hidden
AND post_type = #{Post.types[:regular]}
AND user_id < > #{Discourse.system_user.id}
GROUP BY topic_id "
2014-10-04 09:53:46 +05:30
update_top_topics ( period , " posts " , sql )
2014-08-19 07:28:21 +05:30
end
2013-12-24 00:50:36 +01:00
2014-08-19 07:28:21 +05:30
def self . update_views_count_for ( period )
sql = " SELECT topic_id, COUNT(*) AS count
2014-08-12 19:42:46 +02:00
FROM topic_views
WHERE viewed_at > = :from
GROUP BY topic_id "
2013-12-24 00:50:36 +01:00
2014-10-04 09:53:46 +05:30
update_top_topics ( period , " views " , sql )
2014-08-19 07:28:21 +05:30
end
2013-12-24 00:50:36 +01:00
2014-08-19 07:28:21 +05:30
def self . update_likes_count_for ( period )
2015-03-06 18:00:29 +11:00
sql = " SELECT topic_id, SUM(like_count) AS count
2014-08-12 19:42:46 +02:00
FROM posts
WHERE created_at > = :from
AND deleted_at IS NULL
AND NOT hidden
AND post_type = #{Post.types[:regular]}
GROUP BY topic_id "
2013-12-24 00:50:36 +01:00
2014-10-04 09:53:46 +05:30
update_top_topics ( period , " likes " , sql )
2014-08-19 07:28:21 +05:30
end
2013-12-24 00:50:36 +01:00
2015-03-06 18:00:29 +11:00
def self . update_op_likes_count_for ( period )
sql = " SELECT topic_id, like_count AS count
FROM posts
WHERE created_at > = :from
AND post_number = 1
AND deleted_at IS NULL
AND NOT hidden
AND post_type = #{Post.types[:regular]}"
update_top_topics ( period , " op_likes " , sql )
end
2014-08-19 07:28:21 +05:30
def self . compute_top_score_for ( period )
2015-03-06 18:00:29 +11:00
2016-01-20 22:06:18 +05:30
log_views_multiplier = SiteSetting . top_topics_formula_log_views_multiplier . to_f
log_views_multiplier = 2 if log_views_multiplier == 0
first_post_likes_multiplier = SiteSetting . top_topics_formula_first_post_likes_multiplier . to_f
first_post_likes_multiplier = 0 . 5 if first_post_likes_multiplier == 0
least_likes_per_post_multiplier = SiteSetting . top_topics_formula_least_likes_per_post_multiplier . to_f
least_likes_per_post_multiplier = 3 if least_likes_per_post_multiplier == 0
2015-03-06 18:00:29 +11:00
if period == :all
top_topics = " (
SELECT t . like_count all_likes_count ,
t . id topic_id ,
t . posts_count all_posts_count ,
p . like_count all_op_likes_count ,
t . views all_views_count
FROM topics t
JOIN posts p ON p . topic_id = t . id AND p . post_number = 1
) as top_topics "
time_filter = " false "
else
top_topics = " top_topics "
time_filter = " topics.created_at < :from "
end
2014-08-19 07:28:21 +05:30
sql = <<-SQL
2014-08-12 19:42:46 +02:00
WITH top AS (
SELECT CASE
2015-03-06 18:00:29 +11:00
WHEN #{time_filter} THEN 0
2016-01-20 22:06:18 +05:30
ELSE log ( GREATEST ( #{period}_views_count, 1)) * #{log_views_multiplier} +
#{period}_op_likes_count * #{first_post_likes_multiplier} +
2015-03-06 18:00:29 +11:00
CASE WHEN #{period}_likes_count > 0 AND #{period}_posts_count > 0
THEN
2016-01-20 22:06:18 +05:30
LEAST ( #{period}_likes_count / #{period}_posts_count, #{least_likes_per_post_multiplier})
2015-03-06 18:00:29 +11:00
ELSE 0
END +
CASE WHEN topics . posts_count < 10 THEN
0 - ( ( 10 - topics . posts_count ) / 20 ) * #{period}_op_likes_count
ELSE
10
END +
log ( GREATEST ( #{period}_posts_count, 1))
2014-08-12 19:42:46 +02:00
END AS score ,
topic_id
2015-03-06 18:00:29 +11:00
FROM #{top_topics}
LEFT JOIN topics ON topics . id = top_topics . topic_id AND
topics . deleted_at IS NULL
2014-08-12 19:42:46 +02:00
)
UPDATE top_topics
SET #{period}_score = top.score
FROM top
WHERE top_topics . topic_id = top . topic_id
AND #{period}_score <> top.score
2014-08-19 07:28:21 +05:30
SQL
2014-08-12 19:42:46 +02:00
2014-08-19 07:28:21 +05:30
exec_sql ( sql , from : start_of ( period ) )
end
2013-12-27 18:10:35 +01:00
2014-08-19 07:28:21 +05:30
def self . start_of ( period )
case period
2015-07-28 12:31:31 -04:00
when :yearly then 1 . year . ago
when :monthly then 1 . month . ago
when :quarterly then 3 . months . ago
when :weekly then 1 . week . ago
when :daily then 1 . day . ago
2013-12-24 00:50:36 +01:00
end
2014-08-19 07:28:21 +05:30
end
2013-12-24 00:50:36 +01:00
2014-08-19 07:28:21 +05:30
def self . update_top_topics ( period , sort , inner_join )
exec_sql ( " UPDATE top_topics
2014-08-12 19:42:46 +02:00
SET #{period}_#{sort}_count = c.count
FROM top_topics tt
INNER JOIN ( #{inner_join}) c ON tt.topic_id = c.topic_id
WHERE tt . topic_id = top_topics . topic_id
AND tt . #{period}_#{sort}_count <> c.count",
2014-08-19 07:28:21 +05:30
from : start_of ( period ) )
end
2013-12-24 00:50:36 +01:00
2014-10-04 08:37:20 +05:30
private_class_method :sort_orders , :update_counts_and_compute_scores_for , :remove_invisible_topics ,
:add_new_visible_topics , :update_posts_count_for , :update_views_count_for , :update_likes_count_for ,
:compute_top_score_for , :start_of , :update_top_topics
2013-12-24 00:50:36 +01:00
end
2014-02-07 11:07:36 +11:00
# == Schema Information
#
# Table name: top_topics
#
2015-07-28 12:31:31 -04:00
# id :integer not null, primary key
# topic_id :integer
# yearly_posts_count :integer default(0), not null
# yearly_views_count :integer default(0), not null
# yearly_likes_count :integer default(0), not null
# monthly_posts_count :integer default(0), not null
# monthly_views_count :integer default(0), not null
# monthly_likes_count :integer default(0), not null
# weekly_posts_count :integer default(0), not null
# weekly_views_count :integer default(0), not null
# weekly_likes_count :integer default(0), not null
# daily_posts_count :integer default(0), not null
# daily_views_count :integer default(0), not null
# daily_likes_count :integer default(0), not null
# daily_score :float default(0.0)
# weekly_score :float default(0.0)
# monthly_score :float default(0.0)
# yearly_score :float default(0.0)
# all_score :float default(0.0)
# daily_op_likes_count :integer default(0), not null
# weekly_op_likes_count :integer default(0), not null
# monthly_op_likes_count :integer default(0), not null
# yearly_op_likes_count :integer default(0), not null
# quarterly_posts_count :integer default(0), not null
# quarterly_views_count :integer default(0), not null
# quarterly_likes_count :integer default(0), not null
# quarterly_score :float default(0.0)
# quarterly_op_likes_count :integer default(0), not null
2014-02-07 11:07:36 +11:00
#
# Indexes
#
2015-07-28 12:31:31 -04:00
# index_top_topics_on_daily_likes_count (daily_likes_count)
# index_top_topics_on_daily_op_likes_count (daily_op_likes_count)
# index_top_topics_on_daily_posts_count (daily_posts_count)
# index_top_topics_on_daily_views_count (daily_views_count)
# index_top_topics_on_monthly_likes_count (monthly_likes_count)
# index_top_topics_on_monthly_op_likes_count (monthly_op_likes_count)
# index_top_topics_on_monthly_posts_count (monthly_posts_count)
# index_top_topics_on_monthly_views_count (monthly_views_count)
# index_top_topics_on_quarterly_likes_count (quarterly_likes_count)
# index_top_topics_on_quarterly_op_likes_count (quarterly_op_likes_count)
# index_top_topics_on_quarterly_posts_count (quarterly_posts_count)
# index_top_topics_on_quarterly_views_count (quarterly_views_count)
# index_top_topics_on_topic_id (topic_id) UNIQUE
# index_top_topics_on_weekly_likes_count (weekly_likes_count)
# index_top_topics_on_weekly_op_likes_count (weekly_op_likes_count)
# index_top_topics_on_weekly_posts_count (weekly_posts_count)
# index_top_topics_on_weekly_views_count (weekly_views_count)
# index_top_topics_on_yearly_likes_count (yearly_likes_count)
# index_top_topics_on_yearly_op_likes_count (yearly_op_likes_count)
# index_top_topics_on_yearly_posts_count (yearly_posts_count)
# index_top_topics_on_yearly_views_count (yearly_views_count)
2014-02-07 11:07:36 +11:00
#