Skip to content

Commit

Permalink
PERF: reduce SQL queries by aggreating records
Browse files Browse the repository at this point in the history
  • Loading branch information
erickguan committed Dec 19, 2016
1 parent afc0e37 commit 30741f5
Showing 1 changed file with 110 additions and 28 deletions.
138 changes: 110 additions & 28 deletions plugin.rb
Expand Up @@ -6,6 +6,45 @@
register_asset 'stylesheets/previews_common.scss'
register_asset 'stylesheets/previews_mobile.scss'

module TopicListAddon
def load_topics
@topics = super

# SQL only works with postgresql 9.4+
# TODO: better to keep track of previewed posts' id so they can be loaded at once
accepted_anwser_post_ids = @topics.map { |t| t.custom_fields["accepted_answer_post_id"]&.to_i }.compact
posts = Post.all.joins("JOIN unnest('{#{accepted_anwser_post_ids.join(',')}}'::int[]) WITH ORDINALITY t(id, ord) USING (id)").order("t.ord")
idx = 0
@topics.each do |t|
next unless t.custom_fields["accepted_answer_post_id"]
t.previewed_post = posts[idx]
idx += 1
end
normal_topic_ids = topics.delete_if { |t| t.custom_fields["accepted_answer_post_id"]&.to_i }.map(&:id)
posts = Post.where("post_number = 1 AND topic_id IN (?)", normal_topic_ids).joins("JOIN unnest('{#{normal_topic_ids.join(',')}}'::int[]) WITH ORDINALITY t(topic_id, ord) USING (topic_id)").order("t.ord")
idx = 0
@topics.each do |t|
next if t.custom_fields["accepted_answer_post_id"]
t.previewed_post = posts[idx]
idx += 1
end

if @current_user
previewed_post_ids = @topics.map { |t| t.previewed_post&.id }.compact
post_actions_map = {}
PostAction.where("post_id IN (?) AND user_id = ?", previewed_post_ids, @current_user.id).each do |pa|
(post_actions_map[pa.post_id] ||= []) << pa
end
@topics.each do |t|
next unless t.previewed_post
t.previewed_post_actions = post_actions_map[t.previewed_post.id]
end
end

@topics
end
end

after_initialize do
Category.register_custom_field_type('topic_list_category_badge_move', :boolean)
Topic.register_custom_field_type('thumbnails', :json)
Expand Down Expand Up @@ -45,6 +84,62 @@ def save_thumbnails(id, thumbnails)
end
end

class ::Topic
attr_accessor :previewed_post
attr_accessor :previewed_post_actions
end

require_dependency 'guardian/post_guardian'
module ::PostGuardian
def previewed_post_can_act?(post, topic, action_key, opts={})
taken = opts[:taken_actions].try(:keys).to_a
is_flag = PostActionType.is_flag?(action_key)
already_taken_this_action = taken.any? && taken.include?(PostActionType.types[action_key])
already_did_flagging = taken.any? && (taken & PostActionType.flag_types.values).any?

result = if authenticated? && post && !@user.anonymous?

return false if action_key == :notify_moderators && !SiteSetting.enable_private_messages

# we allow flagging for trust level 1 and higher
# always allowed for private messages
(is_flag && not(already_did_flagging) && (@user.has_trust_level?(TrustLevel[1]) || topic.private_message?)) ||

# not a flagging action, and haven't done it already
not(is_flag || already_taken_this_action) &&

# nothing except flagging on archived topics
not(topic.try(:archived?)) &&

# nothing except flagging on deleted posts
not(post.trashed?) &&

# don't like your own stuff
not(action_key == :like && is_my_own?(post)) &&

# new users can't notify_user because they are not allowed to send private messages
not(action_key == :notify_user && !@user.has_trust_level?(SiteSetting.min_trust_to_send_messages)) &&

# non-staff can't send an official warning
not(action_key == :notify_user && !is_staff? && opts[:is_warning].present? && opts[:is_warning] == 'true') &&

# can't send private messages if they're disabled globally
not(action_key == :notify_user && !SiteSetting.enable_private_messages) &&

# no voting more than once on single vote topics
not(action_key == :vote && opts[:voted_in_topic] && topic.has_meta_data_boolean?(:single_vote))
end

!!result
end
end

TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields
TopicList.preloaded_custom_fields << "thumbnails" if TopicList.respond_to? :preloaded_custom_fields
TopicList.class_eval do
prepend TopicListAddon
end

require 'cooked_post_processor'
class ::CookedPostProcessor

Expand Down Expand Up @@ -89,20 +184,17 @@ class ::TopicListItemSerializer
:topic_post_bookmarked,
:topic_post_is_current_users

def first_post_id
first = Post.find_by(topic_id: object.id, post_number: 1)
first ? first.id : false
def include_topic_post_id?
object.previewed_post.present?
end

def topic_post_id
accepted_id = object.custom_fields["accepted_answer_post_id"].to_i
return accepted_id > 0 ? accepted_id : first_post_id
object.previewed_post.id
end
alias :include_topic_post_id? :first_post_id

def excerpt
cooked = Post.where(id: topic_post_id).pluck('cooked')
excerpt = PrettyText.excerpt(cooked[0], SiteSetting.topic_list_excerpt_length, keep_emoji_images: true)
cooked = object.previewed_post.cooked
excerpt = PrettyText.excerpt(cooked, SiteSetting.topic_list_excerpt_length, keep_emoji_images: true)
excerpt.gsub!(/(\[#{I18n.t 'excerpt_image'}\])/, "") if excerpt
excerpt
end
Expand Down Expand Up @@ -142,61 +234,51 @@ def get_thumbnails_from_image_url
end

def topic_post_actions
return [] if !scope.current_user
PostAction.where(post_id: topic_post_id, user_id: scope.current_user.id)
object.previewed_post_actions || []
end

def topic_like_action
topic_post_actions.select {|a| a.post_action_type_id == PostActionType.types[:like]}
end

def topic_post
Post.find(topic_post_id)
end

def topic_post_bookmarked
!!topic_post_actions.any?{|a| a.post_action_type_id == PostActionType.types[:bookmark]}
end
alias :include_topic_post_bookmarked? :first_post_id
alias :include_topic_post_bookmarked? :topic_post_id

def topic_post_liked
topic_like_action.any?
end
alias :include_topic_post_liked? :first_post_id
alias :include_topic_post_liked? :topic_post_id

def topic_post_like_count
topic_post.like_count
object.previewed_post.like_count
end
alias :include_topic_post_like_count? :first_post_id

def include_topic_post_like_count?
first_post_id && topic_post_like_count > 0
object.previewed_post&.id && topic_post_like_count > 0
end

def topic_post_can_like
post = topic_post
return false if !scope.current_user || topic_post_is_current_users
scope.post_can_act?(post, PostActionType.types[:like], taken_actions: topic_post_actions)
scope.previewed_post_can_act?(object.previewed_post, object, PostActionType.types[:like], taken_actions: topic_post_actions)
end
alias :include_topic_post_can_like? :first_post_id
alias :include_topic_post_can_like? :topic_post_id

def topic_post_is_current_users
return scope.current_user && (topic_post.user_id == scope.current_user.id)
return scope.current_user && (object.previewed_post.user_id == scope.current_user.id)
end
alias :include_topic_post_is_current_users? :first_post_id
alias :include_topic_post_is_current_users? :topic_post_id

def topic_post_can_unlike
return false if !scope.current_user
action = topic_like_action[0]
!!(action && (action.user_id == scope.current_user.id) && (action.created_at > SiteSetting.post_undo_action_window_mins.minutes.ago))
end
alias :include_topic_post_can_unlike? :first_post_id
alias :include_topic_post_can_unlike? :topic_post_id

end

TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields
TopicList.preloaded_custom_fields << "thumbnails" if TopicList.respond_to? :preloaded_custom_fields

add_to_serializer(:basic_category, :topic_list_excerpt) {object.custom_fields["topic_list_excerpt"]}
add_to_serializer(:basic_category, :topic_list_thumbnail) {object.custom_fields["topic_list_thumbnail"]}
add_to_serializer(:basic_category, :topic_list_action) {object.custom_fields["topic_list_action"]}
Expand Down

0 comments on commit 30741f5

Please sign in to comment.