DEV: Allow default scope to be configurable per topic. (#15018)

DEV: Allow default scope to be configurable per topic. (#15018)

Not exposing this as a plugin API yet as we’re testing it out with a plugin.

diff --git a/lib/topic_view.rb b/lib/topic_view.rb
index 184b113..d501476 100644
--- a/lib/topic_view.rb
+++ b/lib/topic_view.rb
@@ -79,6 +79,25 @@ class TopicView
     @custom_filters || {}
   end
 
+  # Configure a default scope to be applied to @filtered_posts.
+  # The registered block is called with @filtered_posts and an instance of
+  # `TopicView`.
+  #
+  # This API should be considered experimental until it is exposed in
+  # `Plugin::Instance`.
+  def self.apply_custom_default_scope(&block)
+    custom_default_scopes << block
+  end
+
+  def self.custom_default_scopes
+    @custom_default_scopes ||= []
+  end
+
+  # For testing
+  def self.reset_custom_default_scopes
+    @custom_default_scopes = nil
+  end
+
   def initialize(topic_or_topic_id, user = nil, options = {})
     @topic = find_topic(topic_or_topic_id)
     @user = user
@@ -108,7 +127,7 @@ class TopicView
     @page = @page.to_i > 1 ? @page.to_i : calculate_page
 
     setup_filtered_posts
-    @filtered_posts = apply_default_order(@filtered_posts)
+    @filtered_posts = apply_default_scope(@filtered_posts)
     filter_posts(options)
 
     if @posts && !@skip_custom_fields
@@ -160,7 +179,7 @@ class TopicView
       if is_mega_topic?
         nil
       else
-        Gaps.new(filtered_post_ids, apply_default_order(unfiltered_posts).pluck(:id))
+        Gaps.new(filtered_post_ids, apply_default_scope(unfiltered_posts).pluck(:id))
       end
     end
   end
@@ -287,8 +306,10 @@ class TopicView
     elsif opts[:post_ids].present?
       filter_posts_by_ids(opts[:post_ids])
     elsif opts[:filter_post_number].present?
+      # Only used for megatopics where we do not load the entire post stream
       filter_posts_by_post_number(opts[:filter_post_number], opts[:asc])
     elsif opts[:best].present?
+      # Only used for wordpress
       filter_best(opts[:best], opts)
     else
       filter_posts_paged(@page)
@@ -738,7 +759,7 @@ class TopicView
         :image_upload
       )
 
-    @posts = apply_default_order(@posts)
+    @posts = apply_default_scope(@posts)
     @posts = filter_post_types(@posts)
     @posts = @posts.with_deleted if @guardian.can_see_deleted_posts?(@topic.category)
     @posts
@@ -762,8 +783,14 @@ class TopicView
     result
   end
 
-  def apply_default_order(scope)
-    scope.order(sort_order: :asc)
+  def apply_default_scope(scope)
+    scope = scope.order(sort_order: :asc)
+
+    self.class.custom_default_scopes.each do |block|
+      scope = block.call(scope, self)
+    end
+
+    scope
   end
 
   def setup_filtered_posts
diff --git a/spec/components/topic_view_spec.rb b/spec/components/topic_view_spec.rb
index fb1260f..755247d 100644
--- a/spec/components/topic_view_spec.rb
+++ b/spec/components/topic_view_spec.rb
@@ -968,4 +968,27 @@ describe TopicView do
       expect(topic_view.reviewable_counts.keys).to contain_exactly(reviewable.target_id)
     end
   end
+
+  describe '.apply_custom_default_scope' do
+    fab!(:post) { Fabricate(:post, topic: topic, created_at: 2.hours.ago) }
+    fab!(:post_2) { Fabricate(:post, topic: topic, created_at: 1.hour.ago) }
+
+    after do
+      TopicView.reset_custom_default_scopes
+    end
+
+    it 'allows a custom default scope to be configured' do
+      topic_view = TopicView.new(topic, admin)
+
+      expect(topic_view.filtered_post_ids).to eq([post.id, post_2.id])
+
+      TopicView.apply_custom_default_scope do |scope, _|
+        scope.unscope(:order).order("posts.created_at DESC")
+      end
+
+      topic_view = TopicView.new(topic, admin)
+
+      expect(topic_view.filtered_post_ids).to eq([post_2.id, post.id])
+    end
+  end
 end

GitHub sha: a6aff40e4bdab43946265b9e0961efb5ea109372

This commit appears in #15018 which was approved by martin. It was merged by tgxworld.