posts: stop updating fav_string attribute.

Stop updating the fav_string attribute on posts. The column still exists
on the table, but is no longer used or updated.

Like the pool_string in 7d503f08, the fav_string was used in the past to
facilitate `fav:X` searches. Posts had a hidden fav_string column that
contained a list of every user who favorited the post. These were
treated like fake hidden tags on the post so that a search for `fav:X`
was treated like a tag search.

The fav_string attribute has been unused for search purposes for a while
now. It was only kept because of technicalities that required
departitioning the favorites table first (340e1008e) before it could be
removed. Basically, removing favorites with `@favorite.destroy` was
slow because Rails always deletes object by ID, but we didn't have an
index on favorites.id, and we couldn't easily add one until the
favorites table was departitioned.

Fixes #4652. See https://github.com/danbooru/danbooru/issues/4652#issuecomment-754993802
for more discussion of issues caused by the fav_string (in short: write
amplification, post table bloat, and favorite inconsistency problems).
This commit is contained in:
evazion
2021-10-07 23:32:38 -05:00
parent 5ce36b482f
commit 1653392361
17 changed files with 190 additions and 239 deletions

View File

@@ -1,6 +1,5 @@
class FavoritesController < ApplicationController class FavoritesController < ApplicationController
respond_to :html, :xml, :json, :js respond_to :html, :xml, :json, :js
rescue_with Favorite::Error, status: 422
def index def index
authorize Favorite authorize Favorite
@@ -18,23 +17,18 @@ class FavoritesController < ApplicationController
end end
def create def create
authorize Favorite @favorite = authorize Favorite.new(post_id: params[:post_id], user: CurrentUser.user)
@post = Post.find(params[:post_id]) @favorite.save
@post.add_favorite!(CurrentUser.user) @post = @favorite.post.reload
flash.now[:notice] = "You have favorited this post"
flash.now[:notice] = "You have favorited this post"
respond_with(@post) respond_with(@post)
end end
def destroy def destroy
authorize Favorite @favorite = authorize Favorite.find_by!(post_id: params[:id], user: CurrentUser.user)
@post = Post.find_by_id(params[:id]) @favorite.destroy
@post = @favorite.post.reload
if @post
@post.remove_favorite!(CurrentUser.user)
else
Favorite.remove(post_id: params[:id], user: CurrentUser.user)
end
flash.now[:notice] = "You have unfavorited this post" flash.now[:notice] = "You have unfavorited this post"
respond_with(@post) respond_with(@post)

View File

@@ -5,9 +5,7 @@ class DeleteFavoritesJob < ApplicationJob
def perform(user) def perform(user)
Post.without_timeout do Post.without_timeout do
user.favorites.find_each do |favorite| user.favorites.destroy_all
Favorite.remove(post: favorite.post, user: user)
end
end end
end end
end end

View File

@@ -441,7 +441,7 @@ class PostQueryBuilder
user = User.find_by_name(username) user = User.find_by_name(username)
if user.present? && Pundit.policy!(current_user, user).can_see_favorites? if user.present? && Pundit.policy!(current_user, user).can_see_favorites?
Post.joins(:favorites).merge(Favorite.for_user(user.id)).order("favorites.id DESC") Post.joins(:favorites).merge(Favorite.where(user: user)).order("favorites.id DESC")
else else
Post.none Post.none
end end

View File

@@ -1,23 +1,19 @@
class Favorite < ApplicationRecord class Favorite < ApplicationRecord
class Error < StandardError; end belongs_to :post, counter_cache: :fav_count
belongs_to :user, counter_cache: :favorite_count
belongs_to :post validates :user_id, uniqueness: { scope: :post_id, message: "have already favorited this post" }
belongs_to :user after_create :upvote_post_on_create
after_destroy :unvote_post_on_destroy
scope :for_user, ->(user_id) { where(user_id: user_id) }
scope :public_favorites, -> { where(user: User.bit_prefs_match(:enable_private_favorites, false)) } scope :public_favorites, -> { where(user: User.bit_prefs_match(:enable_private_favorites, false)) }
def self.visible(user) def self.visible(user)
user.is_admin? ? all : for_user(user.id).or(public_favorites) user.is_admin? ? all : where(user: user).or(public_favorites)
end end
def self.search(params) def self.search(params)
q = search_attributes(params, :id, :post) q = search_attributes(params, :id, :post, :user)
if params[:user_id].present?
q = q.for_user(params[:user_id])
end
q.apply_default_order(params) q.apply_default_order(params)
end end
@@ -25,37 +21,16 @@ class Favorite < ApplicationRecord
[:post, :user] [:post, :user]
end end
def self.add(post:, user:) def upvote_post_on_create
Favorite.transaction do if Pundit.policy!(user, PostVote).create?
User.where(id: user.id).select("id").lock("FOR UPDATE").first PostVote.negative.destroy_by(post: post, user: user)
if Favorite.for_user(user.id).where(:user_id => user.id, :post_id => post.id).exists? # Silently ignore the error if the user has already upvoted the post.
raise Error, "You have already favorited this post" PostVote.create(post: post, user: user, score: 1)
end
Favorite.create!(:user_id => user.id, :post_id => post.id)
Post.where(:id => post.id).update_all("fav_count = fav_count + 1")
post.append_user_to_fav_string(user.id)
User.where(:id => user.id).update_all("favorite_count = favorite_count + 1")
user.favorite_count += 1
end end
end end
def self.remove(user:, post: nil, post_id: nil) def unvote_post_on_destroy
Favorite.transaction do PostVote.positive.destroy_by(post: post, user: user)
if post && post_id.nil?
post_id = post.id
end
User.where(id: user.id).select("id").lock("FOR UPDATE").first
return unless Favorite.for_user(user.id).where(:user_id => user.id, :post_id => post_id).exists?
Favorite.for_user(user.id).where(post_id: post_id).delete_all
Post.where(:id => post_id).update_all("fav_count = fav_count - 1")
post&.delete_user_from_fav_string(user.id)
User.where(:id => user.id).update_all("favorite_count = favorite_count - 1")
user.favorite_count -= 1
post.fav_count -= 1 if post
end
end end
end end

View File

@@ -10,7 +10,7 @@ class Post < ApplicationRecord
NOTE_COPY_TAGS = %w[translated partially_translated check_translation translation_request reverse_translation NOTE_COPY_TAGS = %w[translated partially_translated check_translation translation_request reverse_translation
annotated partially_annotated check_annotation annotation_request] annotated partially_annotated check_annotation annotation_request]
self.ignored_columns = [:pool_string] self.ignored_columns = [:pool_string, :fav_string]
deletable deletable
@@ -53,7 +53,7 @@ class Post < ApplicationRecord
has_many :children, -> {order("posts.id")}, :class_name => "Post", :foreign_key => "parent_id" has_many :children, -> {order("posts.id")}, :class_name => "Post", :foreign_key => "parent_id"
has_many :approvals, :class_name => "PostApproval", :dependent => :destroy has_many :approvals, :class_name => "PostApproval", :dependent => :destroy
has_many :disapprovals, :class_name => "PostDisapproval", :dependent => :destroy has_many :disapprovals, :class_name => "PostDisapproval", :dependent => :destroy
has_many :favorites has_many :favorites, dependent: :destroy
has_many :favorited_users, through: :favorites, source: :user has_many :favorited_users, through: :favorites, source: :user
has_many :replacements, class_name: "PostReplacement", :dependent => :destroy has_many :replacements, class_name: "PostReplacement", :dependent => :destroy
@@ -582,10 +582,10 @@ class Post < ApplicationRecord
pool&.add!(self) pool&.add!(self)
when /^fav:(.+)$/i when /^fav:(.+)$/i
add_favorite(CurrentUser.user) Favorite.create(post: self, user: CurrentUser.user)
when /^-fav:(.+)$/i when /^-fav:(.+)$/i
remove_favorite(CurrentUser.user) Favorite.destroy_by(post: self, user: CurrentUser.user)
when /^(up|down)vote:(.+)$/i when /^(up|down)vote:(.+)$/i
score = ($1 == "up" ? 1 : -1) score = ($1 == "up" ? 1 : -1)
@@ -695,56 +695,11 @@ class Post < ApplicationRecord
end end
module FavoriteMethods module FavoriteMethods
def clean_fav_string?
true
end
def clean_fav_string!
array = fav_string.split.uniq
self.fav_string = array.join(" ")
self.fav_count = array.size
update_column(:fav_string, fav_string)
update_column(:fav_count, fav_count)
end
def favorited_by?(user) def favorited_by?(user)
return false if user.is_anonymous? return false if user.is_anonymous?
Favorite.exists?(post: self, user: user) Favorite.exists?(post: self, user: user)
end end
def append_user_to_fav_string(user_id)
update_column(:fav_string, (fav_string + " fav:#{user_id}").strip)
clean_fav_string! if clean_fav_string?
end
def add_favorite(user)
add_favorite!(user)
true
rescue Favorite::Error
false
end
def add_favorite!(user)
Favorite.add(post: self, user: user)
vote!(1, user)
end
def delete_user_from_fav_string(user_id)
update_column(:fav_string, fav_string.gsub(/(?:\A| )fav:#{user_id}(?:\Z| )/, " ").strip)
end
def remove_favorite!(user)
Favorite.remove(post: self, user: user)
unvote!(user)
end
def remove_favorite(user)
remove_favorite!(user)
true
rescue Favorite::Error
false
end
# Users who publicly favorited this post, ordered by time of favorite. # Users who publicly favorited this post, ordered by time of favorite.
def visible_favorited_users(viewer) def visible_favorited_users(viewer)
favorited_users.order("favorites.id DESC").select do |fav_user| favorited_users.order("favorites.id DESC").select do |fav_user|
@@ -756,13 +711,6 @@ class Post < ApplicationRecord
FavoriteGroup.for_post(id) FavoriteGroup.for_post(id)
end end
def remove_from_favorites
Favorite.where(post_id: id).delete_all
user_ids = fav_string.scan(/\d+/)
User.where(:id => user_ids).update_all("favorite_count = favorite_count - 1")
PostVote.where(post_id: id).delete_all
end
def remove_from_fav_groups def remove_from_fav_groups
FavoriteGroup.for_post(id).find_each do |favgroup| FavoriteGroup.for_post(id).find_each do |favgroup|
favgroup.remove!(self) favgroup.remove!(self)
@@ -857,8 +805,8 @@ class Post < ApplicationRecord
transaction do transaction do
favorites.each do |fav| favorites.each do |fav|
remove_favorite!(fav.user) fav.destroy!
parent.add_favorite(fav.user) Favorite.create(post: parent, user: fav.user)
end end
end end
@@ -887,7 +835,6 @@ class Post < ApplicationRecord
decrement_tag_post_counts decrement_tag_post_counts
remove_from_all_pools remove_from_all_pools
remove_from_fav_groups remove_from_fav_groups
remove_from_favorites
destroy destroy
update_parent_on_destroy update_parent_on_destroy
end end

View File

@@ -4,6 +4,6 @@ class FavoritePolicy < ApplicationPolicy
end end
def destroy? def destroy?
!user.is_anonymous? record.user_id == user.id
end end
end end

View File

@@ -81,7 +81,6 @@ class PostPolicy < ApplicationPolicy
attributes += TagCategory.categories.map {|x| "tag_string_#{x}".to_sym} attributes += TagCategory.categories.map {|x| "tag_string_#{x}".to_sym}
attributes += [:file_url, :large_file_url, :preview_file_url] if visible? attributes += [:file_url, :large_file_url, :preview_file_url] if visible?
attributes -= [:id, :md5] if !visible? attributes -= [:id, :md5] if !visible?
attributes -= [:fav_string]
attributes attributes
end end

View File

@@ -1,18 +1,22 @@
$("#add-to-favorites, #add-fav-button, #remove-from-favorites, #remove-fav-button").toggle(); <% if @favorite.errors.any? %>
$("#remove-fav-button").addClass("animate"); Danbooru.Utility.error("You have already favorited this post");
$("span.post-votes[data-id=<%= @post.id %>]").replaceWith("<%= j render_post_votes @post, current_user: CurrentUser.user %>"); <% else %>
$("#favcount-for-post-<%= @post.id %>").text(<%= @post.fav_count %>); $("#add-to-favorites, #add-fav-button, #remove-from-favorites, #remove-fav-button").toggle();
$(".fav-buttons").toggleClass("fav-buttons-false").toggleClass("fav-buttons-true"); $("#remove-fav-button").addClass("animate");
$("span.post-votes[data-id=<%= @post.id %>]").replaceWith("<%= j render_post_votes @post, current_user: CurrentUser.user %>");
$("#favcount-for-post-<%= @post.id %>").text(<%= @post.fav_count %>);
$(".fav-buttons").toggleClass("fav-buttons-false").toggleClass("fav-buttons-true");
<% if policy(@post).can_view_favlist? %> <% if policy(@post).can_view_favlist? %>
var fav_count = <%= @post.fav_count %>; var fav_count = <%= @post.fav_count %>;
$("#favlist").html("<%= j render "posts/partials/show/favorite_list", post: @post %>"); $("#favlist").html("<%= j render "posts/partials/show/favorite_list", post: @post %>");
if (fav_count === 0) { if (fav_count === 0) {
$("#show-favlist-link, #hide-favlist-link, #favlist").hide(); $("#show-favlist-link, #hide-favlist-link, #favlist").hide();
} else if (!$("#favlist").is(":visible")) { } else if (!$("#favlist").is(":visible")) {
$("#show-favlist-link").show(); $("#show-favlist-link").show();
} }
<% end %>
Danbooru.Utility.notice("<%= j flash[:notice] %>");
<% end %> <% end %>
Danbooru.Utility.notice("<%= j flash[:notice] %>");

View File

@@ -35,6 +35,9 @@ en:
attributes: attributes:
artist_url: artist_url:
url: "" url: ""
favorite:
user: "You"
user_id: "You"
forum_post_vote: forum_post_vote:
creator_id: "Your vote" creator_id: "Your vote"
moderation_report: moderation_report:

View File

@@ -186,8 +186,8 @@ if Favorite.count == 0
Post.order("random()").limit(50).each do |post| Post.order("random()").limit(50).each do |post|
user = User.order("random()").first user = User.order("random()").first
post.add_favorite!(user) Favorite.create!(post: post, user: user)
post.add_favorite!(CurrentUser.user) Favorite.create!(post: post, user: Currentuser.user)
end end
else else
puts "Skipping favorites" puts "Skipping favorites"

View File

@@ -22,7 +22,7 @@ module Explore
context "#curated" do context "#curated" do
should "render" do should "render" do
@builder = create(:builder_user) @builder = create(:builder_user)
@post.add_favorite!(@builder) Favorite.create!(post: @post, user: @builder)
get curated_explore_posts_path get curated_explore_posts_path
assert_response :success assert_response :success
end end

View File

@@ -6,7 +6,7 @@ class FavoritesControllerTest < ActionDispatch::IntegrationTest
@user = create(:user) @user = create(:user)
@post = create(:post) @post = create(:post)
@faved_post = create(:post) @faved_post = create(:post)
@faved_post.add_favorite!(@user) create(:favorite, post: @faved_post, user: @user)
end end
context "index action" do context "index action" do
@@ -33,30 +33,48 @@ class FavoritesControllerTest < ActionDispatch::IntegrationTest
context "create action" do context "create action" do
should "create a favorite for the current user" do should "create a favorite for the current user" do
assert_difference("Favorite.count", 1) do assert_difference [-> { @post.favorites.count }, -> { @post.reload.fav_count }, -> { @user.reload.favorite_count }], 1 do
post_auth favorites_path(post_id: @post.id), @user, as: :javascript
assert_response :redirect
end
end
should "not allow creating duplicate favorites" do
create(:favorite, post: @post, user: @user)
assert_no_difference [-> { @post.favorites.count }, -> { @post.reload.fav_count }, -> { @user.reload.favorite_count }] do
post_auth favorites_path(post_id: @post.id), @user, as: :javascript post_auth favorites_path(post_id: @post.id), @user, as: :javascript
assert_response :redirect assert_response :redirect
end end
end end
should "allow banned users to create favorites" do should "allow banned users to create favorites" do
assert_difference("Favorite.count", 1) do @banned_user = create(:banned_user)
post_auth favorites_path(post_id: @post.id), create(:banned_user), as: :javascript
assert_difference [-> { @post.favorites.count }, -> { @post.reload.fav_count }, -> { @banned_user.reload.favorite_count }], 1 do
post_auth favorites_path(post_id: @post.id), @banned_user, as: :javascript
assert_response :redirect assert_response :redirect
end end
end end
should "not allow anonymous users to create favorites" do
assert_no_difference [-> { @post.favorites.count }, -> { @post.reload.fav_count }] do
post favorites_path(post_id: @post.id), as: :javascript
assert_response 403
end
end
end end
context "destroy action" do context "destroy action" do
should "remove the favorite from the current user" do should "remove the favorite for the current user" do
assert_difference("Favorite.count", -1) do assert_difference [-> { @faved_post.favorites.count }, -> { @faved_post.reload.fav_count }, -> { @user.reload.favorite_count }], -1 do
delete_auth favorite_path(@faved_post.id), @user, as: :javascript delete_auth favorite_path(@faved_post.id), @user, as: :javascript
assert_response :redirect assert_response :redirect
end end
end end
should "allow banned users to destroy favorites" do should "allow banned users to destroy favorites" do
assert_difference("Favorite.count", -1) do assert_difference [-> { @faved_post.favorites.count }, -> { @faved_post.reload.fav_count }, -> { @user.reload.favorite_count }], -1 do
delete_auth favorite_path(@faved_post.id), @user, as: :javascript delete_auth favorite_path(@faved_post.id), @user, as: :javascript
assert_response :redirect assert_response :redirect
end end

View File

@@ -34,7 +34,7 @@ module Moderator
end end
users = FactoryBot.create_list(:user, 2) users = FactoryBot.create_list(:user, 2)
users.each do |u| users.each do |u|
@child.add_favorite!(u) Favorite.create!(post: @child, user: u)
@child.reload @child.reload
end end

View File

@@ -5,17 +5,17 @@ class DeleteFavoritesJobTest < ActiveJob::TestCase
should "delete all favorites" do should "delete all favorites" do
user = create(:user) user = create(:user)
posts = create_list(:post, 3) posts = create_list(:post, 3)
favorites = posts.each { |post| post.add_favorite!(user) } favorites = posts.each { |post| Favorite.create!(post: post, user: user) }
assert_equal(3, user.favorite_count) assert_equal(3, user.favorite_count)
assert_equal(3, user.favorites.count) assert_equal(3, user.favorites.count)
assert_equal(3, Post.raw_tag_match("fav:#{user.id}").count) assert_equal(3, Post.user_tag_match("fav:#{user.name}", user).count)
DeleteFavoritesJob.perform_now(user) DeleteFavoritesJob.perform_now(user)
assert_equal(0, user.reload.favorite_count) assert_equal(0, user.reload.favorite_count)
assert_equal(0, user.favorites.count) assert_equal(0, user.favorites.count)
assert_equal(0, Post.raw_tag_match("fav:#{user.id}").count) assert_equal(0, Post.user_tag_match("fav:#{user.name}", user).count)
end end
end end
end end

View File

@@ -2,50 +2,95 @@ require 'test_helper'
class FavoriteTest < ActiveSupport::TestCase class FavoriteTest < ActiveSupport::TestCase
setup do setup do
@user1 = FactoryBot.create(:user) @user1 = create(:user)
@user2 = FactoryBot.create(:user) @user2 = create(:user)
@p1 = FactoryBot.create(:post) @p1 = create(:post)
@p2 = FactoryBot.create(:post) @p2 = create(:post)
CurrentUser.user = @user1
CurrentUser.ip_addr = "127.0.0.1"
end end
teardown do context "Favorites: " do
CurrentUser.user = nil context "removing a favorite" do
CurrentUser.ip_addr = nil should "update the post and user favorite counts" do
end fav = Favorite.create!(post: @p1, user: @user1)
context "A favorite" do assert_equal(1, @user1.reload.favorite_count)
should "delete from all tables" do assert_equal(1, @p1.reload.fav_count)
@p1.add_favorite!(@user1)
assert_equal(1, @user1.favorite_count)
Favorite.where(:user_id => @user1.id, :post_id => @p1.id).delete_all Favorite.destroy_by(post: @p1, user: @user1)
assert_equal(0, Favorite.count)
assert_equal(0, @user1.reload.favorite_count)
assert_equal(0, @p1.reload.fav_count)
end
should "remove the upvote if the user could vote" do
@user = create(:gold_user)
@vote = create(:post_vote, post: @p1, user: @user, score: 1)
fav = Favorite.create!(post: @p1, user: @user)
assert_equal(1, @user.reload.favorite_count)
assert_equal(1, @p1.reload.fav_count)
assert_equal(1, @p1.reload.score)
assert(PostVote.positive.exists?(post: @p1, user: @user))
Favorite.destroy_by(post: @p1, user: @user)
assert_equal(0, @user.reload.favorite_count)
assert_equal(0, @p1.reload.fav_count)
assert_equal(0, @p1.reload.score)
refute(PostVote.positive.exists?(post: @p1, user: @user))
end
end end
should "know which table it belongs to" do context "adding a favorite" do
@p1.add_favorite!(@user1) should "update the post and user favorite counts" do
@p2.add_favorite!(@user1) Favorite.create!(post: @p1, user: @user1)
@p1.add_favorite!(@user2)
favorites = @user1.favorites.order("id desc") assert_equal(1, @user1.reload.favorite_count)
assert_equal(2, favorites.count) assert_equal(1, @p1.reload.fav_count)
assert_equal(@p2.id, favorites[0].post_id) end
assert_equal(@p1.id, favorites[1].post_id)
favorites = @user2.favorites.order("id desc") should "not upvote the post if the user can't vote" do
assert_equal(1, favorites.count) Favorite.create!(post: @p1, user: @user1)
assert_equal(@p1.id, favorites[0].post_id)
end
should "not allow duplicates" do assert_equal(1, @user1.reload.favorite_count)
@p1.add_favorite!(@user1) assert_equal(1, @p1.reload.fav_count)
error = assert_raises(Favorite::Error) { @p1.add_favorite!(@user1) } assert_equal(0, @p1.reload.score)
refute(PostVote.positive.exists?(post: @p1, user: @user1))
end
assert_equal("You have already favorited this post", error.message) should "upvote the post if the user can vote" do
assert_equal(1, @user1.favorite_count) @user = create(:gold_user)
Favorite.create!(post: @p1, user: @user)
assert_equal(1, @user.reload.favorite_count)
assert_equal(1, @p1.reload.fav_count)
assert_equal(1, @p1.reload.score)
assert(PostVote.positive.exists?(post: @p1, user: @user))
end
should "convert a downvote into an upvote if the post was downvoted" do
@user = create(:gold_user)
@vote = create(:post_vote, post: @p1, user: @user, score: -1)
assert_equal(-1, @p1.reload.score)
Favorite.create!(post: @p1, user: @user)
assert_equal(1, @user.reload.favorite_count)
assert_equal(1, @p1.reload.fav_count)
assert_equal(1, @p1.reload.score)
assert(PostVote.positive.exists?(post: @p1, user: @user))
refute(PostVote.negative.exists?(post: @p1, user: @user))
end
should "not allow duplicate favorites" do
@f1 = Favorite.create(post: @p1, user: @user1)
@f2 = Favorite.create(post: @p1, user: @user1)
assert_equal(["You have already favorited this post"], @f2.errors.full_messages)
assert_equal(1, @user1.reload.favorite_count)
assert_equal(1, @p1.reload.fav_count)
assert_equal(0, @p1.reload.score)
end
end end
end end
end end

View File

@@ -33,7 +33,7 @@ class PostTest < ActiveSupport::TestCase
setup do setup do
@upload = UploadService.new(FactoryBot.attributes_for(:jpg_upload)).start! @upload = UploadService.new(FactoryBot.attributes_for(:jpg_upload)).start!
@post = @upload.post @post = @upload.post
Favorite.add(post: @post, user: @user) Favorite.create!(post: @post, user: @user)
create(:favorite_group, post_ids: [@post.id]) create(:favorite_group, post_ids: [@post.id])
perform_enqueued_jobs # perform IqdbAddPostJob perform_enqueued_jobs # perform IqdbAddPostJob
end end
@@ -51,7 +51,9 @@ class PostTest < ActiveSupport::TestCase
should "remove all favorites" do should "remove all favorites" do
@post.expunge! @post.expunge!
assert_equal(0, Favorite.for_user(@user.id).where("post_id = ?", @post.id).count) assert_equal(0, @post.favorites.count)
assert_equal(0, @user.favorites.count)
assert_equal(0, @user.reload.favorite_count)
end end
should "remove all favgroups" do should "remove all favgroups" do
@@ -248,7 +250,7 @@ class PostTest < ActiveSupport::TestCase
p1 = FactoryBot.create(:post) p1 = FactoryBot.create(:post)
c1 = FactoryBot.create(:post, :parent_id => p1.id) c1 = FactoryBot.create(:post, :parent_id => p1.id)
user = FactoryBot.create(:gold_user) user = FactoryBot.create(:gold_user)
c1.add_favorite!(user) create(:favorite, post: c1, user: user)
c1.delete!("test") c1.delete!("test")
p1.reload p1.reload
assert(Favorite.exists?(:post_id => c1.id, :user_id => user.id)) assert(Favorite.exists?(:post_id => c1.id, :user_id => user.id))
@@ -259,7 +261,7 @@ class PostTest < ActiveSupport::TestCase
p1 = FactoryBot.create(:post) p1 = FactoryBot.create(:post)
c1 = FactoryBot.create(:post, :parent_id => p1.id) c1 = FactoryBot.create(:post, :parent_id => p1.id)
user = FactoryBot.create(:gold_user) user = FactoryBot.create(:gold_user)
c1.add_favorite!(user) create(:favorite, post: c1, user: user)
c1.delete!("test", :move_favorites => true) c1.delete!("test", :move_favorites => true)
p1.reload p1.reload
assert(!Favorite.exists?(:post_id => c1.id, :user_id => user.id), "Child should not still have favorites") assert(!Favorite.exists?(:post_id => c1.id, :user_id => user.id), "Child should not still have favorites")
@@ -278,7 +280,7 @@ class PostTest < ActiveSupport::TestCase
user = FactoryBot.create(:gold_user) user = FactoryBot.create(:gold_user)
p1 = FactoryBot.create(:post) p1 = FactoryBot.create(:post)
c1 = FactoryBot.create(:post, :parent_id => p1.id) c1 = FactoryBot.create(:post, :parent_id => p1.id)
c1.add_favorite!(user) create(:favorite, post: c1, user: user)
assert_equal(true, p1.reload.has_active_children?) assert_equal(true, p1.reload.has_active_children?)
c1.delete!("test", :move_favorites => true) c1.delete!("test", :move_favorites => true)
@@ -837,18 +839,18 @@ class PostTest < ActiveSupport::TestCase
context "for a fav" do context "for a fav" do
should "add/remove the current user to the post's favorite listing" do should "add/remove the current user to the post's favorite listing" do
@post.update(tag_string: "aaa fav:self") @post.update(tag_string: "aaa fav:self")
assert_equal("fav:#{@user.id}", @post.fav_string) assert_equal(1, @post.favorites.where(user: @user).count)
@post.update(tag_string: "aaa -fav:self") @post.update(tag_string: "aaa -fav:self")
assert_equal("", @post.fav_string) assert_equal(0, @post.favorites.count)
end end
should "not fail when the fav: metatag is used twice" do should "not fail when the fav: metatag is used twice" do
@post.update(tag_string: "aaa fav:self fav:me") @post.update(tag_string: "aaa fav:self fav:me")
assert_equal("fav:#{@user.id}", @post.fav_string) assert_equal(1, @post.favorites.where(user: @user).count)
@post.update(tag_string: "aaa -fav:self -fav:me") @post.update(tag_string: "aaa -fav:self -fav:me")
assert_equal("", @post.fav_string) assert_equal(0, @post.favorites.count)
end end
end end
@@ -1531,33 +1533,33 @@ class PostTest < ActiveSupport::TestCase
setup do setup do
@user = FactoryBot.create(:contributor_user) @user = FactoryBot.create(:contributor_user)
@post = FactoryBot.create(:post) @post = FactoryBot.create(:post)
@post.add_favorite!(@user) create(:favorite, post: @post, user: @user)
@user.reload @user.reload
end end
should "decrement the user's favorite_count" do should "decrement the user's favorite_count" do
assert_difference("@user.favorite_count", -1) do assert_difference("@user.reload.favorite_count", -1) do
@post.remove_favorite!(@user) Favorite.destroy_by(post: @post, user: @user)
end end
end end
should "decrement the post's score for gold users" do should "decrement the post's score for gold users" do
assert_difference("@post.score", -1) do assert_difference("@post.reload.score", -1) do
@post.remove_favorite!(@user) Favorite.destroy_by(post: @post, user: @user)
end end
end end
should "not decrement the post's score for basic users" do should "not decrement the post's score for basic users" do
@member = FactoryBot.create(:user) @member = FactoryBot.create(:user)
assert_no_difference("@post.score") { @post.add_favorite!(@member) } assert_no_difference("@post.score") { create(:favorite, post: @post, user: @member) }
assert_no_difference("@post.score") { @post.remove_favorite!(@member) } assert_no_difference("@post.score") { Favorite.destroy_by(post: @post, user: @member) }
end end
should "not decrement the user's favorite_count if the user did not favorite the post" do should "not decrement the user's favorite_count if the user did not favorite the post" do
@post2 = FactoryBot.create(:post) @post2 = FactoryBot.create(:post)
assert_no_difference("@user.favorite_count") do assert_no_difference("@user.favorite_count") do
@post2.remove_favorite!(@user) Favorite.destroy_by(post: @post2, user: @user)
end end
end end
end end
@@ -1568,53 +1570,22 @@ class PostTest < ActiveSupport::TestCase
@post = FactoryBot.create(:post) @post = FactoryBot.create(:post)
end end
should "periodically clean the fav_string" do
@post.update_column(:fav_string, "fav:1 fav:1 fav:1")
@post.update_column(:fav_count, 3)
@post.stubs(:clean_fav_string?).returns(true)
@post.append_user_to_fav_string(2)
assert_equal("fav:1 fav:2", @post.fav_string)
assert_equal(2, @post.fav_count)
end
should "increment the user's favorite_count" do should "increment the user's favorite_count" do
assert_difference("@user.favorite_count", 1) do assert_difference("@user.favorite_count", 1) do
@post.add_favorite!(@user) create(:favorite, post: @post, user: @user)
end end
end end
should "increment the post's score for gold users" do should "increment the post's score for gold users" do
@post.add_favorite!(@user) create(:favorite, post: @post, user: @user)
assert_equal(1, @post.score) assert_equal(1, @post.reload.score)
end end
should "not increment the post's score for basic users" do should "not increment the post's score for basic users" do
@member = FactoryBot.create(:user) @member = FactoryBot.create(:user)
@post.add_favorite!(@member) create(:favorite, post: @post, user: @member)
assert_equal(0, @post.score) assert_equal(0, @post.score)
end end
should "update the fav strings on the post" do
@post.add_favorite!(@user)
@post.reload
assert_equal("fav:#{@user.id}", @post.fav_string)
assert(Favorite.exists?(:user_id => @user.id, :post_id => @post.id))
assert_raises(Favorite::Error) { @post.add_favorite!(@user) }
@post.reload
assert_equal("fav:#{@user.id}", @post.fav_string)
assert(Favorite.exists?(:user_id => @user.id, :post_id => @post.id))
@post.remove_favorite!(@user)
@post.reload
assert_equal("", @post.fav_string)
assert(!Favorite.exists?(:user_id => @user.id, :post_id => @post.id))
@post.remove_favorite!(@user)
@post.reload
assert_equal("", @post.fav_string)
assert(!Favorite.exists?(:user_id => @user.id, :post_id => @post.id))
end
end end
context "Moving favorites to a parent post" do context "Moving favorites to a parent post" do
@@ -1625,8 +1596,8 @@ class PostTest < ActiveSupport::TestCase
@user1 = FactoryBot.create(:user, enable_private_favorites: true) @user1 = FactoryBot.create(:user, enable_private_favorites: true)
@gold1 = FactoryBot.create(:gold_user) @gold1 = FactoryBot.create(:gold_user)
@child.add_favorite!(@user1) create(:favorite, post: @child, user: @user1)
@child.add_favorite!(@gold1) create(:favorite, post: @child, user: @gold1)
@child.give_favorites_to_parent @child.give_favorites_to_parent
@child.reload @child.reload
@@ -1636,12 +1607,10 @@ class PostTest < ActiveSupport::TestCase
should "move the favorites" do should "move the favorites" do
assert_equal(0, @child.fav_count) assert_equal(0, @child.fav_count)
assert_equal(0, @child.favorites.count) assert_equal(0, @child.favorites.count)
assert_equal("", @child.fav_string)
assert_equal([], @child.favorites.pluck(:user_id)) assert_equal([], @child.favorites.pluck(:user_id))
assert_equal(2, @parent.fav_count) assert_equal(2, @parent.fav_count)
assert_equal(2, @parent.favorites.count) assert_equal(2, @parent.favorites.count)
assert_equal("fav:#{@user1.id} fav:#{@gold1.id}", @parent.fav_string)
assert_equal([@user1.id, @gold1.id], @parent.favorites.pluck(:user_id)) assert_equal([@user1.id, @gold1.id], @parent.favorites.pluck(:user_id))
end end

View File

@@ -69,13 +69,12 @@ class UserDeletionTest < ActiveSupport::TestCase
should "remove any favorites" do should "remove any favorites" do
@post = create(:post) @post = create(:post)
Favorite.add(post: @post, user: @user) Favorite.create!(post: @post, user: @user)
perform_enqueued_jobs { @deletion.delete! } perform_enqueued_jobs { @deletion.delete! }
assert_equal(0, Favorite.count) assert_equal(0, Favorite.count)
assert_equal("", @post.reload.fav_string) assert_equal(0, @post.reload.fav_count)
assert_equal(0, @post.fav_count)
end end
end end
end end