cache reply_to_user_id in post to eliminate a very nasty n+1 query

This commit is contained in:
Sam 2013-03-19 16:51:39 -07:00
parent ec948dc660
commit 2a047df4f1
4 changed files with 34 additions and 9 deletions

View File

@ -19,6 +19,7 @@ class Post < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :topic, counter_cache: :posts_count belongs_to :topic, counter_cache: :posts_count
belongs_to :reply_to_user, class_name: "User"
has_many :post_replies has_many :post_replies
has_many :replies, through: :post_replies has_many :replies, through: :post_replies
@ -190,12 +191,6 @@ class Post < ActiveRecord::Base
(quote_count == 0) && (reply_to_post_number.present?) (quote_count == 0) && (reply_to_post_number.present?)
end end
# Get the post that we reply to.
def reply_to_user
return if reply_to_post_number.blank?
Post.where(topic_id: topic_id, post_number: reply_to_post_number).first.try(:user)
end
def reply_notification_target def reply_notification_target
return if reply_to_post_number.blank? return if reply_to_post_number.blank?
Post.where("topic_id = :topic_id AND post_number = :post_number AND user_id <> :user_id", Post.where("topic_id = :topic_id AND post_number = :post_number AND user_id <> :user_id",
@ -268,8 +263,14 @@ class Post < ActiveRecord::Base
PostRevisor.new(self).revise!(updated_by, new_raw, opts) PostRevisor.new(self).revise!(updated_by, new_raw, opts)
end end
# TODO: move into PostCreator
# Various callbacks # Various callbacks
before_create do before_create do
if reply_to_post_number.present?
self.reply_to_user_id ||= Post.select(:user_id).where(topic_id: topic_id, post_number: reply_to_post_number).first.try(:user_id)
end
self.post_number ||= Topic.next_post_number(topic_id, reply_to_post_number.present?) self.post_number ||= Topic.next_post_number(topic_id, reply_to_post_number.present?)
self.cooked ||= cook(raw, topic_id: topic_id) self.cooked ||= cook(raw, topic_id: topic_id)
self.sort_order = post_number self.sort_order = post_number
@ -278,6 +279,7 @@ class Post < ActiveRecord::Base
end end
# TODO: Move some of this into an asynchronous job? # TODO: Move some of this into an asynchronous job?
# TODO: Move into PostCreator
after_create do after_create do
# Update attributes on the topic - featured users and last posted. # Update attributes on the topic - featured users and last posted.
attrs = {last_posted_at: created_at, last_post_user_id: user_id} attrs = {last_posted_at: created_at, last_post_user_id: user_id}

View File

@ -402,6 +402,11 @@ class User < ActiveRecord::Base
admin? || TrustLevel.compare(trust_level, level) admin? || TrustLevel.compare(trust_level, level)
end end
# a touch faster than automatic
def admin?
admin
end
def change_trust_level(level) def change_trust_level(level)
raise "Invalid trust level #{level}" unless TrustLevel.valid_level?(level) raise "Invalid trust level #{level}" unless TrustLevel.valid_level?(level)
self.trust_level = TrustLevel.levels[level] self.trust_level = TrustLevel.levels[level]

View File

@ -0,0 +1,16 @@
class AddReplyToUserIdToPost < ActiveRecord::Migration
def up
# caching this column makes the topic page WAY faster
add_column :posts, :reply_to_user_id, :integer
execute 'UPDATE posts p SET reply_to_user_id = (
SELECT u.id from users u
JOIN posts p2 ON p2.user_id = u.id AND
p2.post_number = p.reply_to_post_number AND
p2.topic_id = p.topic_id
)'
end
def down
remove_column :posts, :reply_to_user_id
end
end

View File

@ -34,6 +34,7 @@ class TopicView
@all_posts = @posts @all_posts = @posts
filter_posts(options) filter_posts(options)
@draft_key = @topic.draft_key @draft_key = @topic.draft_key
@ -130,7 +131,8 @@ class TopicView
@max = post_number - 1 @max = post_number - 1
@posts = @posts.reverse_order.where("post_number < ?", post_number) @posts = @posts.reverse_order.where("post_number < ?", post_number)
@posts = @posts.includes(:topic).joins(:user).limit(SiteSetting.posts_per_page)
@posts = @posts.includes(:reply_to_user).includes(:topic).joins(:user).limit(SiteSetting.posts_per_page)
@min = @max - @posts.size @min = @max - @posts.size
@min = 1 if @min < 1 @min = 1 if @min < 1
end end
@ -140,7 +142,7 @@ class TopicView
@initial_load = false @initial_load = false
@min = post_number @min = post_number
@posts = @posts.regular_order.where("post_number > ?", post_number) @posts = @posts.regular_order.where("post_number > ?", post_number)
@posts = @posts.includes(:topic).joins(:user).limit(SiteSetting.posts_per_page) @posts = @posts.includes(:reply_to_user).includes(:topic).joins(:user).limit(SiteSetting.posts_per_page)
@max = @min + @posts.size @max = @min + @posts.size
end end
@ -275,7 +277,7 @@ class TopicView
def filter_posts_in_range(min, max) def filter_posts_in_range(min, max)
@min, @max = min, max @min, @max = min, max
@posts = @posts.where("post_number between ? and ?", @min, @max).includes(:user).regular_order @posts = @posts.where("post_number between ? and ?", @min, @max).includes(:user).includes(:reply_to_user).regular_order
end end
def find_topic(topic_id) def find_topic(topic_id)