FIX: Reindex posts when `Topic#title` or `Category#name` changes.

FIX: Reindex posts when Topic#title or Category#name changes.

diff --git a/app/models/category.rb b/app/models/category.rb
index 7ba81fe..7c9a0ba 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -764,6 +764,10 @@ class Category < ActiveRecord::Base
   end
 
   def index_search
+    if saved_change_to_attribute?(:name)
+      SearchIndexer.queue_category_posts_reindex(self.id)
+    end
+
     SearchIndexer.index(self)
   end
 
diff --git a/app/models/topic.rb b/app/models/topic.rb
index c47d457..cbf29d1 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -338,8 +338,7 @@ class Topic < ActiveRecord::Base
       ApplicationController.banner_json_cache.clear
     end
 
-    if tags_changed || saved_change_to_attribute?(:category_id)
-
+    if tags_changed || saved_change_to_attribute?(:category_id) || saved_change_to_attribute?(:title)
       SearchIndexer.queue_post_reindex(self.id)
 
       if tags_changed
diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb
index aae80d4..075bd39 100644
--- a/app/services/search_indexer.rb
+++ b/app/services/search_indexer.rb
@@ -104,8 +104,8 @@ class SearchIndexer
     update_index(table: 'topic', id: topic_id, raw_data: [title, scrubbed_cooked])
   end
 
-  def self.update_posts_index(post_id, title, category, tags, cooked)
-    update_index(table: 'post', id: post_id, raw_data: [title, category, tags, scrub_html_for_search(cooked)])
+  def self.update_posts_index(post_id, topic_title, category_name, topic_tags, cooked)
+    update_index(table: 'post', id: post_id, raw_data: [topic_title, category_name, topic_tags, scrub_html_for_search(cooked)])
   end
 
   def self.update_users_index(user_id, username, name)
@@ -120,6 +120,20 @@ class SearchIndexer
     update_index(table: 'tag', id: tag_id, raw_data: [name.downcase])
   end
 
+  def self.queue_category_posts_reindex(category_id)
+    return if @disabled
+
+    DB.exec(<<~SQL, category_id: category_id, version: REINDEX_VERSION)
+      UPDATE post_search_data
+      SET version = :version
+      FROM posts
+      INNER JOIN topics ON posts.topic_id = topics.id
+      INNER JOIN categories ON topics.category_id = categories.id
+      WHERE post_search_data.post_id = posts.id
+      AND categories.id = :category_id
+    SQL
+  end
+
   def self.queue_post_reindex(topic_id)
     return if @disabled
 
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index bc4239f..d1be623 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -10,30 +10,48 @@ describe Search do
     SearchIndexer.enable
   end
 
-  context 'post indexing observer' do
-    before do
-      @category = Fabricate(:category_with_definition, name: 'america')
-      @topic = Fabricate(:topic, title: 'sam saffron test topic', category: @category)
-      @post = Fabricate(:post, topic: @topic, raw: 'this <b>fun test</b> <img src="bla" title="my image">')
-      @indexed = @post.post_search_data.search_data
-    end
+  context 'post indexing' do
+    fab!(:category) { Fabricate(:category_with_definition, name: 'america') }
+    fab!(:topic) { Fabricate(:topic, title: 'sam saffron test topic', category: category) }
+    let!(:post) { Fabricate(:post, topic: topic, raw: 'this <b>fun test</b> <img src="bla" title="my image">') }
+    let!(:post2) { Fabricate(:post, topic: topic) }
 
     it "should index correctly" do
-      expect(@indexed).to match(/fun/)
-      expect(@indexed).to match(/sam/)
-      expect(@indexed).to match(/america/)
+      search_data = post.post_search_data.search_data
+
+      expect(search_data).to match(/fun/)
+      expect(search_data).to match(/sam/)
+      expect(search_data).to match(/america/)
+
+      expect do
+        topic.update!(title: "harpi is the new title")
+      end.to change { post2.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+
+      expect(post.post_search_data.reload.search_data).to match(/harpi/)
+    end
+
+    it 'should update posts index when topic category changes' do
+      expect do
+        topic.update!(category: Fabricate(:category))
+      end.to change { post.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+        .and change { post2.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+    end
 
-      @topic.title = "harpi is the new title"
-      @topic.save!
-      @post.post_search_data.reload
+    it 'should update posts index when topic tags changes' do
+      SiteSetting.tagging_enabled = true
+      tag = Fabricate(:tag)
 
-      @indexed = @post.post_search_data.search_data
+      expect do
+        DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), [tag.name])
+        topic.save!
+      end.to change { post.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+        .and change { post2.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
 
-      expect(@indexed).to match(/harpi/)
+      expect(topic.tags).to eq([tag])
     end
   end
 
-  context 'user indexing observer' do
+  context 'user indexing' do
     before do
       @user = Fabricate(:user, username: 'fred', name: 'bob jones')
       @indexed = @user.user_search_data.search_data
@@ -45,16 +63,25 @@ describe Search do
     end
   end
 
-  context 'category indexing observer' do
-    before do
-      @category = Fabricate(:category_with_definition, name: 'america')
-      @indexed = @category.category_search_data.search_data
-    end
+  context 'category indexing' do
+    let!(:category) { Fabricate(:category_with_definition, name: 'america') }
+    let!(:topic) { Fabricate(:topic, category: category) }
+    let!(:post) { Fabricate(:post, topic: topic) }
+    let!(:post2) { Fabricate(:post, topic: topic) }
+    let!(:post3) { Fabricate(:post) }
 
-    it "should pick up on name" do
-      expect(@indexed).to match(/america/)
+    it "should index correctly" do
+      expect(category.category_search_data.search_data).to match(/america/)
     end
 
+    it 'should update posts index when category name changes' do
+      expect do
+        category.update!(name: 'some new name')
+      end.to change { post.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+        .and change { post2.reload.post_search_data.version }.from(SearchIndexer::INDEX_VERSION).to(SearchIndexer::REINDEX_VERSION)
+
+      expect(post3.post_search_data.version).to eq(SearchIndexer::INDEX_VERSION)
+    end
   end
 
   it 'strips zero-width characters from search terms' do

GitHub sha: ff7678e2