ReplyingComments: add tests, fix on deleted, less gas

Fixes showing replies for comments that were deleted or moderated.

Doesn't show replies if the parent commenter has flagged the replier in the
thread.
This commit is contained in:
Peter Bhat Harkins 2019-05-22 20:00:24 -05:00
parent a0078521e1
commit a4af5a18f8
5 changed files with 171 additions and 2 deletions

View File

@ -0,0 +1,5 @@
class UpdateReplyingCommentsToVersion8 < ActiveRecord::Migration[5.2]
def change
update_view :replying_comments, version: 8, revert_to_version: 7
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2018_12_02_194809) do
ActiveRecord::Schema.define(version: 2019_05_29_133507) do
create_table "comments", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.datetime "created_at", null: false
@ -332,7 +332,7 @@ ActiveRecord::Schema.define(version: 2018_12_02_194809) do
add_foreign_key "votes", "users", name: "votes_user_id_fk"
create_view "replying_comments", sql_definition: <<-SQL
select `read_ribbons`.`user_id` AS `user_id`,`comments`.`id` AS `comment_id`,`read_ribbons`.`story_id` AS `story_id`,`comments`.`parent_comment_id` AS `parent_comment_id`,`comments`.`created_at` AS `comment_created_at`,`parent_comments`.`user_id` AS `parent_comment_author_id`,`comments`.`user_id` AS `comment_author_id`,`stories`.`user_id` AS `story_author_id`,`read_ribbons`.`updated_at` < `comments`.`created_at` AS `is_unread`,(select `votes`.`vote` from `votes` where `votes`.`user_id` = `read_ribbons`.`user_id` and `votes`.`comment_id` = `comments`.`id`) AS `current_vote_vote`,(select `votes`.`reason` from `votes` where `votes`.`user_id` = `read_ribbons`.`user_id` and `votes`.`comment_id` = `comments`.`id`) AS `current_vote_reason` from (((`read_ribbons` join `comments` on(`comments`.`story_id` = `read_ribbons`.`story_id`)) join `stories` on(`stories`.`id` = `comments`.`story_id`)) left join `comments` `parent_comments` on(`parent_comments`.`id` = `comments`.`parent_comment_id`)) where `read_ribbons`.`is_following` = 1 and `comments`.`user_id` <> `read_ribbons`.`user_id` and `comments`.`is_deleted` = 0 and `comments`.`is_moderated` = 0 and (`parent_comments`.`user_id` = `read_ribbons`.`user_id` or `parent_comments`.`user_id` is null and `stories`.`user_id` = `read_ribbons`.`user_id`) and `comments`.`upvotes` - `comments`.`downvotes` >= 0 and (`parent_comments`.`id` is null or `parent_comments`.`upvotes` - `parent_comments`.`downvotes` >= 0) and cast(`stories`.`upvotes` as signed) - cast(`stories`.`downvotes` as signed) >= 0
select `read_ribbons`.`user_id` AS `user_id`,`comments`.`id` AS `comment_id`,`read_ribbons`.`story_id` AS `story_id`,`comments`.`parent_comment_id` AS `parent_comment_id`,`comments`.`created_at` AS `comment_created_at`,`parent_comments`.`user_id` AS `parent_comment_author_id`,`comments`.`user_id` AS `comment_author_id`,`stories`.`user_id` AS `story_author_id`,`read_ribbons`.`updated_at` < `comments`.`created_at` AS `is_unread`,(select `votes`.`vote` from `votes` where `votes`.`user_id` = `read_ribbons`.`user_id` and `votes`.`comment_id` = `comments`.`id`) AS `current_vote_vote`,(select `votes`.`reason` from `votes` where `votes`.`user_id` = `read_ribbons`.`user_id` and `votes`.`comment_id` = `comments`.`id`) AS `current_vote_reason` from (((`read_ribbons` join `comments` on(`comments`.`story_id` = `read_ribbons`.`story_id`)) join `stories` on(`stories`.`id` = `comments`.`story_id`)) left join `comments` `parent_comments` on(`parent_comments`.`id` = `comments`.`parent_comment_id`)) where `read_ribbons`.`is_following` = 1 and `comments`.`user_id` <> `read_ribbons`.`user_id` and `comments`.`is_deleted` = 0 and `comments`.`is_moderated` = 0 and (`parent_comments`.`user_id` = `read_ribbons`.`user_id` or `parent_comments`.`user_id` is null and `stories`.`user_id` = `read_ribbons`.`user_id`) and `comments`.`upvotes` - `comments`.`downvotes` >= 0 and (`parent_comments`.`id` is null or `parent_comments`.`upvotes` - `parent_comments`.`downvotes` >= 0 and `parent_comments`.`is_moderated` = 0 and `parent_comments`.`is_deleted` = 0) and !exists(select 1 from (`votes` `f` join `comments` `c` on(`f`.`comment_id` = `c`.`id`)) where `f`.`vote` < 0 and `f`.`user_id` = `parent_comments`.`user_id` and `c`.`user_id` = `comments`.`user_id` and `f`.`story_id` = `comments`.`story_id`) and cast(`stories`.`upvotes` as signed) - cast(`stories`.`downvotes` as signed) >= 0
SQL
end

View File

@ -0,0 +1,52 @@
SELECT
read_ribbons.user_id,
comments.id as comment_id,
read_ribbons.story_id as story_id,
comments.parent_comment_id,
comments.created_at as comment_created_at,
parent_comments.user_id as parent_comment_author_id,
comments.user_id as comment_author_id,
stories.user_id as story_author_id,
(read_ribbons.updated_at < comments.created_at) as is_unread,
(select votes.vote from votes where votes.user_id = read_ribbons.user_id and votes.comment_id = comments.id) as current_vote_vote,
(select votes.reason from votes where votes.user_id = read_ribbons.user_id and votes.comment_id = comments.id) as current_vote_reason
FROM
read_ribbons
JOIN
comments ON comments.story_id = read_ribbons.story_id
JOIN
stories ON stories.id = comments.story_id
LEFT JOIN
comments parent_comments ON parent_comments.id = comments.parent_comment_id
WHERE
read_ribbons.is_following = 1
AND comments.user_id != read_ribbons.user_id
AND comments.is_deleted = FALSE
AND comments.is_moderated = FALSE
AND (
parent_comments.user_id = read_ribbons.user_id
OR (parent_comments.user_id IS NULL
AND stories.user_id = read_ribbons.user_id)
)
AND (comments.upvotes - comments.downvotes) >= 0 -- comment doesn't have negative score
AND (
parent_comments.id IS NULL
OR (
-- parent doesn't have negative score, isn't deleted, isn't moderated
(parent_comments.upvotes - parent_comments.downvotes) >= 0
AND parent_comments.is_moderated = FALSE
AND parent_comments.is_deleted = FALSE
)
)
AND ( -- parent user has not flagged replier in the thread
NOT EXISTS (
SELECT 1 FROM votes f JOIN comments c ON f.comment_id = c.id
WHERE
f.vote < 0
AND f.user_id = parent_comments.user_id
AND c.user_id = comments.user_id
AND f.story_id = comments.story_id
)
)
AND (CAST(stories.upvotes as signed) - CAST(stories.downvotes as signed)) >= 0
;

View File

@ -1,5 +1,6 @@
FactoryBot.define do
factory :vote do
association(:user)
vote { 1 }
end
end

View File

@ -0,0 +1,111 @@
require "rails_helper"
RSpec::Matchers.define :have_reply do |expected|
match do |actual|
ReplyingComment.for_user(actual.user).map(&:comment).include? expected
end
failure_message do |actual|
"expected that comment #{actual.id} would be in " \
"#{ReplyingComment.for_user(expected.user).map(&:comment_id)}"
end
end
describe ReplyingComment do
def followed_parent
p = create(:comment)
ReadRibbon.create(user_id: p.user_id, story_id: p.story_id, updated_at: p.created_at - 1.second)
p
end
def flag_comment(comment, by = create(:user))
Vote.vote_thusly_on_story_or_comment_for_user_because(
-1, comment.story_id, comment.id, by.id, 'T'
)
end
describe "is listed when" do
it "it's a direct reply" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
expect(p).to have_reply(c)
end
end
describe "is not listed when" do
it "parent has a negative score" do
p = followed_parent
flag_comment(p)
flag_comment(p)
c = create(:comment, story_id: p.story_id, parent_comment: p)
expect(p).to_not have_reply(c)
end
it "it has a negative score" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
flag_comment(c)
flag_comment(c)
expect(p).to_not have_reply(c)
end
it "parent is deleted" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
p.delete_for_user(p.user)
expect(p).to_not have_reply(c)
end
it "it is deleted" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
c.delete_for_user(c.user)
expect(p).to_not have_reply(c)
end
it "parent is moderated" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
p.delete_for_user(create(:user, :admin), "obvs because I disagree with your politics")
expect(p).to_not have_reply(c)
end
it "it is moderated" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
c.delete_for_user(create(:user, :admin), "obvs because I disagree with your politics")
expect(p).to_not have_reply(c)
end
it "it is on a story with a negative score" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
Vote.vote_thusly_on_story_or_comment_for_user_because(
-1, p.story_id, nil, create(:user).id, 'O'
)
Vote.vote_thusly_on_story_or_comment_for_user_because(
-1, p.story_id, nil, create(:user).id, 'O'
)
expect(p.story.reload.score).to be < 0
expect(p).to_not have_reply(c)
end
it "commenter has not flagged child commenter in the story" do
p = followed_parent
c = create(:comment, story_id: p.story_id, parent_comment: p)
expect(p).to have_reply(c)
flag_comment(c, p.user)
expect(p).to_not have_reply(c)
end
end
end