Use subqueries to check reaction/subscription state

This commit is contained in:
Karina Kwiatek 2023-11-26 19:32:50 +01:00
parent f91d2f2d7f
commit b55e6da9a5
24 changed files with 45 additions and 43 deletions

View File

@ -8,15 +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
@reacted_answer_ids = []
@subscribed_answer_ids = []
return unless user_signed_in? return unless user_signed_in?
@reacted_answer_ids = Reaction.where(user: current_user, parent: @answer).pluck(:parent_id)
@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,12 +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?
return unless user_signed_in?
@reacted_answer_ids = Reaction.where(user: current_user, parent_type: "Answer", parent_id: answer_ids).pluck(:parent_id)
@subscribed_answer_ids = Subscription.where(user: current_user, answer_id: answer_ids).pluck(:answer_id)
end end
end end

View File

@ -8,15 +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.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.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)
@reacted_answer_ids = Reaction.where(user: current_user, parent_type: "Answer", parent_id: answer_ids).pluck(:parent_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').

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,11 +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?
@reacted_answer_ids = Reaction.where(user: current_user, parent_type: "Answer", parent_id: timeline_ids).pluck(:parent_id)
@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,20 @@ 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, reacted_answer_ids: @reacted_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: reacted_answer_ids&.include?(a.id) ? :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:, reacted_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:, reacted_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:, reacted_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:, reacted_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, reacted_answer_ids: @reacted_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, reacted_answer_ids: @reacted_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, reacted_answer_ids: @reacted_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, reacted_answer_ids: @reacted_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, reacted_answer_ids: @reacted_answer_ids = render "answerbox", a:
#answers #answers
- @answers.each do |a| - @answers.each do |a|
= render "answerbox", a:, subscribed_answer_ids: @subscribed_answer_ids, reacted_answer_ids: @reacted_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, reacted_answer_ids: @reacted_answer_ids = render "answerbox", a:
= turbo_stream.update "paginator" do = turbo_stream.update "paginator" do
- if @more_data_available - if @more_data_available