diff --git a/app/models/post.rb b/app/models/post.rb index 3f9d178f53c..4ee640960a5 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -157,7 +157,7 @@ class Post < ActiveRecord::Base end def self.best_of - where("(post_number = 1) or (score >= ?)", SiteSetting.best_of_score_threshold) + where(["(post_number = 1) or (percent_rank <= ?)", SiteSetting.best_of_percent_filter.to_f / 100.0]) end def update_flagged_posts_count diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 38a860679a7..9465a664022 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -106,6 +106,7 @@ class SiteSetting < ActiveRecord::Base setting(:best_of_score_threshold, 15) setting(:best_of_posts_required, 50) setting(:best_of_likes_required, 1) + setting(:best_of_percent_filter, 5) # we need to think of a way to force users to enter certain settings, this is a minimal config thing setting(:notification_email, 'info@discourse.org') diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 8124f6796ae..b18bc11da35 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -329,6 +329,7 @@ en: best_of_score_threshold: "The minimum score of a post to be included in the 'best of'" best_of_posts_required: "Minimum posts in a topic before 'best of' mode is enabled" best_of_likes_required: "Minimum likes in a topic before the 'best of' mode will be enabled" + best_of_percent_filter: "When a user clicks best of, show the top % of posts" enable_private_messages: "Allow trust level 1 users to create private messages and conversations" enable_long_polling: "Message bus used for notification can use long polling" diff --git a/config/routes.rb b/config/routes.rb index 7c6d6c16c74..364001614e7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -171,7 +171,7 @@ Discourse::Application.routes.draw do get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} # Topic routes - get 't/:slug/:topic_id/best_of' => 'topics#show', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} + get 't/:slug/:topic_id/best_of' => 'topics#show', :defaults => {best_of: true}, :constraints => {:topic_id => /\d+/, :post_number => /\d+/} get 't/:topic_id/best_of' => 'topics#show', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} put 't/:slug/:topic_id' => 'topics#update', :constraints => {:topic_id => /\d+/} put 't/:slug/:topic_id/star' => 'topics#star', :constraints => {:topic_id => /\d+/} diff --git a/db/migrate/20130322183614_add_percent_rank_to_posts.rb b/db/migrate/20130322183614_add_percent_rank_to_posts.rb new file mode 100644 index 00000000000..25554a94e05 --- /dev/null +++ b/db/migrate/20130322183614_add_percent_rank_to_posts.rb @@ -0,0 +1,12 @@ +class AddPercentRankToPosts < ActiveRecord::Migration + def change + add_column :posts, :percent_rank, :float, default: 1.0 + + execute "UPDATE posts SET percent_rank = x.percent_rank + FROM (SELECT id, percent_rank() + OVER (PARTITION BY topic_id ORDER BY SCORE DESC) + FROM posts) AS x + WHERE x.id = posts.id" + + end +end diff --git a/lib/score_calculator.rb b/lib/score_calculator.rb index 96421d970f9..901c887ac3e 100644 --- a/lib/score_calculator.rb +++ b/lib/score_calculator.rb @@ -21,6 +21,15 @@ class ScoreCalculator # First update the scores of the posts exec_sql(post_score_sql, @weightings) + # Update the percent rankings of the posts + + exec_sql("UPDATE posts SET percent_rank = x.percent_rank + FROM (SELECT id, percent_rank() + OVER (PARTITION BY topic_id ORDER BY SCORE DESC) as percent_rank + FROM posts) AS x + WHERE x.id = posts.id") + + # Update the best of flag exec_sql " UPDATE topics SET has_best_of = @@ -41,7 +50,7 @@ class ScoreCalculator private - def exec_sql(sql, params) + def exec_sql(sql, params=nil) ActiveRecord::Base.exec_sql(sql, params) end diff --git a/spec/components/score_calculator_spec.rb b/spec/components/score_calculator_spec.rb index dd9d7eaec72..899f7cd8e17 100644 --- a/spec/components/score_calculator_spec.rb +++ b/spec/components/score_calculator_spec.rb @@ -3,19 +3,25 @@ require 'score_calculator' describe ScoreCalculator do - before do - @post = Fabricate(:post, reads: 111) - @topic = @post.topic - end + let!(:post) { Fabricate(:post, reads: 111) } + let!(:another_post) { Fabricate(:post, topic: post.topic, reads: 222) } + let(:topic) { post.topic } context 'with weightings' do before do ScoreCalculator.new(reads: 3).calculate - @post.reload + post.reload + another_post.reload end it 'takes the supplied weightings into effect' do - @post.score.should == 333 + post.score.should == 333 + another_post.score.should == 666 + end + + it "creates the percent_ranks" do + another_post.percent_rank.should == 0.0 + post.percent_rank.should == 1.0 end end @@ -23,15 +29,15 @@ describe ScoreCalculator do it "won't update the site settings when the site settings don't match" do ScoreCalculator.new(reads: 3).calculate - @topic.reload - @topic.has_best_of.should be_false + topic.reload + topic.has_best_of.should be_false end it "removes the best_of flag if the topic no longer qualifies" do - @topic.update_column(:has_best_of, true) + topic.update_column(:has_best_of, true) ScoreCalculator.new(reads: 3).calculate - @topic.reload - @topic.has_best_of.should be_false + topic.reload + topic.has_best_of.should be_false end it "won't update the site settings when the site settings don't match" do @@ -40,8 +46,8 @@ describe ScoreCalculator do SiteSetting.expects(:best_of_score_threshold).returns(100) ScoreCalculator.new(reads: 3).calculate - @topic.reload - @topic.has_best_of.should be_true + topic.reload + topic.has_best_of.should be_true end end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index cd20084dd35..773478f761f 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -628,12 +628,12 @@ describe Post do end context 'best_of' do - let!(:p1) { Fabricate(:post, post_args.merge(score: 4)) } - let!(:p2) { Fabricate(:post, post_args.merge(score: 10)) } - let!(:p3) { Fabricate(:post, post_args.merge(score: 5)) } + let!(:p1) { Fabricate(:post, post_args.merge(score: 4, percent_rank: 0.33)) } + let!(:p2) { Fabricate(:post, post_args.merge(score: 10, percent_rank: 0.66)) } + let!(:p3) { Fabricate(:post, post_args.merge(score: 5, percent_rank: 0.99)) } it "returns the OP and posts above the threshold in best of mode" do - SiteSetting.stubs(:best_of_score_threshold).returns(10) + SiteSetting.stubs(:best_of_percent_filter).returns(66) Post.best_of.order(:post_number).should == [p1, p2] end