Merge pull request #1459 from Retrospring/fix/prefetch-reaction-state

Pre-emptively fetch answer reaction state
This commit is contained in:
Karina J. Kwiatek 2023-12-08 22:59:53 +01:00 committed by GitHub
commit 115166997b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 47 additions and 36 deletions

View file

@ -8,13 +8,11 @@ class AnswerController < ApplicationController
turbo_stream_actions :pin, :unpin turbo_stream_actions :pin, :unpin
def show def show
@answer = Answer.includes(question: [:user], smiles: [:user]).find(params[:id]) @answer = Answer.for_user(current_user).includes(question: [:user], smiles: [:user]).find(params[:id])
@display_all = true @display_all = true
@subscribed_answer_ids = []
return unless user_signed_in? return unless user_signed_in?
@subscribed_answer_ids = Subscription.where(user: current_user, answer: @answer).pluck(:answer_id)
mark_notifications_as_read mark_notifications_as_read
end end

View file

@ -5,8 +5,6 @@ module PaginatesAnswers
@answers = yield(last_id: params[:last_id]) @answers = yield(last_id: params[:last_id])
answer_ids = @answers.map(&:id) answer_ids = @answers.map(&:id)
@answers_last_id = answer_ids.min @answers_last_id = answer_ids.min
answer_ids += @pinned_answers.pluck(:id) if @pinned_answers.present? @more_data_available = !yield(last_id: @answers_last_id, size: 1).select("answers.id").count.zero?
@more_data_available = !yield(last_id: @answers_last_id, size: 1).count.zero?
@subscribed_answer_ids = Subscription.where(user: current_user, answer_id: answer_ids).pluck(:answer_id) if user_signed_in?
end end
end end

View file

@ -8,14 +8,11 @@ class DiscoverController < ApplicationController
top_x = 10 # only display the top X items top_x = 10 # only display the top X items
@popular_answers = Answer.where("created_at > ?", Time.now.ago(1.week)).order(:smile_count).reverse_order.limit(top_x).includes(:question, :user, :comments) @popular_answers = Answer.for_user(current_user).where("created_at > ?", Time.now.utc.ago(1.week)).order(:smile_count).reverse_order.limit(top_x).includes(:question, :user, :comments)
@most_discussed = Answer.where("created_at > ?", Time.now.ago(1.week)).order(:comment_count).reverse_order.limit(top_x).includes(:question, :user, :comments) @most_discussed = Answer.for_user(current_user).where("created_at > ?", Time.now.utc.ago(1.week)).order(:comment_count).reverse_order.limit(top_x).includes(:question, :user, :comments)
@popular_questions = Question.where("created_at > ?", Time.now.ago(1.week)).order(:answer_count).reverse_order.limit(top_x).includes(:user) @popular_questions = Question.where("created_at > ?", Time.now.ago(1.week)).order(:answer_count).reverse_order.limit(top_x).includes(:user)
@new_users = User.where("asked_count > 0").order(:id).reverse_order.limit(top_x).includes(:profile) @new_users = User.where("asked_count > 0").order(:id).reverse_order.limit(top_x).includes(:profile)
answer_ids = @popular_answers.map(&:id) + @most_discussed.map(&:id)
@subscribed_answer_ids = Subscription.where(user: current_user, answer_id: answer_ids).pluck(:answer_id)
# .user = the user # .user = the user
# .question_count = how many questions did the user ask # .question_count = how many questions did the user ask
@users_with_most_questions = Question.select('user_id, COUNT(*) AS question_count'). @users_with_most_questions = Question.select('user_id, COUNT(*) AS question_count').
@ -28,7 +25,7 @@ class DiscoverController < ApplicationController
# .user = the user # .user = the user
# .answer_count = how many questions did the user answer # .answer_count = how many questions did the user answer
@users_with_most_answers = Answer.select('user_id, COUNT(*) AS answer_count'). @users_with_most_answers = Answer.select('user_id, COUNT(*) AS answer_count').
where("created_at > ?", Time.now.ago(1.week)). where("created_at > ?", week_ago).
group(:user_id). group(:user_id).
order('answer_count'). order('answer_count').
reverse_order.limit(top_x) reverse_order.limit(top_x)

View file

@ -8,8 +8,7 @@ class QuestionController < ApplicationController
@answers = @question.cursored_answers(last_id: params[:last_id], current_user:) @answers = @question.cursored_answers(last_id: params[:last_id], current_user:)
answer_ids = @answers.map(&:id) answer_ids = @answers.map(&:id)
@answers_last_id = answer_ids.min @answers_last_id = answer_ids.min
@more_data_available = !@question.cursored_answers(last_id: @answers_last_id, size: 1, current_user:).count.zero? @more_data_available = !@question.cursored_answers(last_id: @answers_last_id, size: 1, current_user:).select("answers.id").count.zero?
@subscribed = Subscription.where(user: current_user, answer_id: answer_ids).pluck(:answer_id) if user_signed_in?
respond_to do |format| respond_to do |format|
format.html format.html

View file

@ -32,10 +32,9 @@ class TimelineController < ApplicationController
def paginate_timeline def paginate_timeline
@timeline = yield(last_id: params[:last_id]) @timeline = yield(last_id: params[:last_id])
timeline_ids = @timeline.map(&:id) timeline_ids = @timeline.select("answers.id").map(&:id)
@timeline_last_id = timeline_ids.min @timeline_last_id = timeline_ids.min
@more_data_available = !yield(last_id: @timeline_last_id, size: 1).count.zero? @more_data_available = !yield(last_id: @timeline_last_id, size: 1).select("answers.id").count.zero?
@subscribed_answer_ids = Subscription.where(user: current_user, answer_id: timeline_ids).pluck(:answer_id)
respond_to do |format| respond_to do |format|
format.html { render "timeline/timeline" } format.html { render "timeline/timeline" }

View file

@ -8,8 +8,8 @@ class UserController < ApplicationController
after_action :mark_notification_as_read, only: %i[show] after_action :mark_notification_as_read, only: %i[show]
def show def show
@pinned_answers = @user.answers.pinned.includes([{ user: :profile }, :question]).order(pinned_at: :desc).limit(10).load_async @pinned_answers = @user.answers.for_user(current_user).pinned.includes([{ user: :profile }, :question]).order(pinned_at: :desc).limit(10).load_async
paginate_answers { |args| @user.cursored_answers(**args) } paginate_answers { |args| @user.cursored_answers(current_user_id: current_user, **args) }
respond_to do |format| respond_to do |format|
format.html format.html

View file

@ -15,6 +15,21 @@ class Answer < ApplicationRecord
# rubocop:enable Rails/UniqueValidationWithoutIndex # rubocop:enable Rails/UniqueValidationWithoutIndex
scope :pinned, -> { where.not(pinned_at: nil) } scope :pinned, -> { where.not(pinned_at: nil) }
scope :for_user, lambda { |current_user|
next select("answers.*", "false as is_subscribed", "false as has_reacted") if current_user.nil?
select("answers.*",
"EXISTS(SELECT 1
FROM subscriptions
WHERE answer_id = answers.id
AND user_id = #{current_user.id}) as is_subscribed",
"EXISTS(SELECT 1
FROM reactions
WHERE parent_id = answers.id
AND parent_type = 'Answer'
AND user_id = #{current_user.id}) as has_reacted",
)
}
SHORT_ANSWER_MAX_LENGTH = 640 SHORT_ANSWER_MAX_LENGTH = 640

View file

@ -6,7 +6,8 @@ module Answer::TimelineMethods
define_cursor_paginator :cursored_public_timeline, :public_timeline define_cursor_paginator :cursored_public_timeline, :public_timeline
def public_timeline(current_user: nil) def public_timeline(current_user: nil)
includes([{ user: :profile }, :question]) for_user(current_user)
.includes([{ user: :profile }, :question])
.then do |query| .then do |query|
next query unless current_user next query unless current_user

View file

@ -8,6 +8,7 @@ module List::TimelineMethods
# @return [ActiveRecord::Relation<Answer>] the lists' timeline # @return [ActiveRecord::Relation<Answer>] the lists' timeline
def timeline(current_user: nil) def timeline(current_user: nil)
Answer Answer
.for_user(current_user)
.includes([{ user: :profile }, :question]) .includes([{ user: :profile }, :question])
.then do |query| .then do |query|
next query unless current_user next query unless current_user

View file

@ -7,6 +7,7 @@ module Question::AnswerMethods
def ordered_answers(current_user: nil) def ordered_answers(current_user: nil)
answers answers
.for_user(current_user)
.includes([{ user: :profile }, :question]) .includes([{ user: :profile }, :question])
.then do |query| .then do |query|
next query unless current_user next query unless current_user

View file

@ -6,8 +6,9 @@ module User::AnswerMethods
define_cursor_paginator :cursored_answers, :ordered_answers define_cursor_paginator :cursored_answers, :ordered_answers
# @return [ActiveRecord::Relation<Answer>] List of a user's answers # @return [ActiveRecord::Relation<Answer>] List of a user's answers
def ordered_answers def ordered_answers(current_user_id:)
answers answers
.for_user(current_user_id)
.order(:created_at) .order(:created_at)
.reverse_order .reverse_order
.includes(question: { user: [:profile] }) .includes(question: { user: [:profile] })

View file

@ -8,6 +8,7 @@ module User::TimelineMethods
# @return [ActiveRecord::Relation<Answer>] the user's timeline # @return [ActiveRecord::Relation<Answer>] the user's timeline
def timeline def timeline
Answer Answer
.for_user(self)
.then do |query| .then do |query|
blocked_and_muted_user_ids = blocked_user_ids_cached + muted_user_ids_cached blocked_and_muted_user_ids = blocked_user_ids_cached + muted_user_ids_cached
next query if blocked_and_muted_user_ids.empty? next query if blocked_and_muted_user_ids.empty?

View file

@ -1,5 +1,5 @@
.dropdown-menu.dropdown-menu-end{ role: :menu } .dropdown-menu.dropdown-menu-end{ role: :menu }
- if subscribed_answer_ids&.include?(answer.id) - if answer.is_subscribed
= render "subscriptions/destroy", answer: answer = render "subscriptions/destroy", answer: answer
- else - else
= render "subscriptions/create", answer: answer = render "subscriptions/create", answer: answer

View file

@ -1,4 +1,4 @@
- provide(:title, answer_title(@answer)) - provide(:title, answer_title(@answer))
- provide(:og, answer_opengraph(@answer)) - provide(:og, answer_opengraph(@answer))
.container-lg.container--main .container-lg.container--main
= render "answerbox", a: @answer, display_all: @display_all, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a: @answer, display_all: @display_all

View file

@ -1,4 +1,4 @@
%button.btn.btn-link.answerbox__action{ type: :button, name: "ab-smile", data: { a_id: a.id, action: current_user&.smiled?(a) ? :unsmile : :smile, selection_hotkey: "s" }, disabled: !user_signed_in? } %button.btn.btn-link.answerbox__action{ type: :button, name: "ab-smile", data: { a_id: a.id, action: a.has_reacted ? :unsmile : :smile, selection_hotkey: "s" }, disabled: !user_signed_in? }
%i.fa.fa-fw.fa-smile-o %i.fa.fa-fw.fa-smile-o
%span{ id: "ab-smile-count-#{a.id}" }= a.smile_count %span{ id: "ab-smile-count-#{a.id}" }= a.smile_count
- unless display_all - unless display_all
@ -13,4 +13,4 @@
.btn-group .btn-group
%button.btn.btn-default.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } } %button.btn.btn-default.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
%span.caret %span.caret
= render "actions/answer", answer: a, subscribed_answer_ids: = render "actions/answer", answer: a

View file

@ -22,7 +22,7 @@
.answerbox__answer-date .answerbox__answer-date
= link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), data: { selection_hotkey: "l" }) = link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), data: { selection_hotkey: "l" })
.col-md-6.d-flex.d-md-block.answerbox__actions .col-md-6.d-flex.d-md-block.answerbox__actions
= render "answerbox/actions", a:, display_all:, subscribed_answer_ids: = render "answerbox/actions", a:, display_all:
- else - else
.row .row
.col-md-6.text-start.text-muted .col-md-6.text-start.text-muted
@ -34,7 +34,7 @@
%i.fa.fa-thumbtack %i.fa.fa-thumbtack
= t(".pinned") = t(".pinned")
.col-md-6.d-md-flex.answerbox__actions .col-md-6.d-md-flex.answerbox__actions
= render "answerbox/actions", a:, display_all:, subscribed_answer_ids: = render "answerbox/actions", a:, display_all:
.card-footer{ id: "ab-comments-section-#{a.id}", class: display_all.nil? ? "d-none" : nil } .card-footer{ id: "ab-comments-section-#{a.id}", class: display_all.nil? ? "d-none" : nil }
= turbo_frame_tag("ab-reactions-list-#{a.id}", src: reactions_path(a.question, a), loading: :lazy) do = turbo_frame_tag("ab-reactions-list-#{a.id}", src: reactions_path(a.question, a), loading: :lazy) do
.d-flex.smiles .d-flex.smiles

View file

@ -1,3 +1,3 @@
.tab-pane.active.fade.show{ role: :tabpanel, id: "answers" } .tab-pane.active.fade.show{ role: :tabpanel, id: "answers" }
- answers.each do |a| - answers.each do |a|
= render "answerbox", a:, subscribed_answer_ids: = render "answerbox", a:

View file

@ -1,3 +1,3 @@
.tab-pane.fade{ role: :tabpanel, id: "comments" } .tab-pane.fade{ role: :tabpanel, id: "comments" }
- comments.each do |a| - comments.each do |a|
= render "answerbox", a:, subscribed_answer_ids: = render "answerbox", a:

View file

@ -8,7 +8,7 @@
%button.d-none{ data: { hotkey: "j", action: "navigation#down" } } %button.d-none{ data: { hotkey: "j", action: "navigation#down" } }
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } } %button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
- @answers.each do |a| - @answers.each do |a|
= render "answerbox", a:, show_question: false, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a:, show_question: false
- if @more_data_available - if @more_data_available
.d-flex.justify-content-center.justify-content-sm-start#paginator .d-flex.justify-content-center.justify-content-sm-start#paginator

View file

@ -1,6 +1,6 @@
= turbo_stream.append "answers" do = turbo_stream.append "answers" do
- @answers.each do |a| - @answers.each do |a|
= render "answerbox", a:, show_question: false, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a:, show_question: false
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available

View file

@ -2,7 +2,7 @@
%button.d-none{ data: { hotkey: "j", action: "navigation#down" } } %button.d-none{ data: { hotkey: "j", action: "navigation#down" } }
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } } %button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
- @timeline.each do |answer| - @timeline.each do |answer|
= render "answerbox", a: answer, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a: answer
- if @more_data_available - if @more_data_available
.d-flex.justify-content-center#paginator .d-flex.justify-content-center#paginator

View file

@ -1,6 +1,6 @@
= turbo_stream.append "timeline" do = turbo_stream.append "timeline" do
- @timeline.each do |answer| - @timeline.each do |answer|
= render "answerbox", a: answer, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a: answer
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available

View file

@ -4,11 +4,11 @@
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } } %button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
#pinned-answers #pinned-answers
- @pinned_answers.each do |a| - @pinned_answers.each do |a|
= render "answerbox", a:, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a:
#answers #answers
- @answers.each do |a| - @answers.each do |a|
= render "answerbox", a:, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a:
- if @more_data_available - if @more_data_available
.d-flex.justify-content-center.justify-content-sm-start#paginator .d-flex.justify-content-center.justify-content-sm-start#paginator

View file

@ -1,6 +1,6 @@
= turbo_stream.append "answers" do = turbo_stream.append "answers" do
- @answers.each do |a| - @answers.each do |a|
= render "answerbox", a:, subscribed_answer_ids: @subscribed_answer_ids = render "answerbox", a:
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available