aliases/implications: change automatic retirement rules.

Change the rules for automatically retiring aliases and implications:

* Retire aliases to tags that are empty, or that are for a general or
  artist tag that hasn't received any new posts in the last two years.
* Retire implications from tags that are empty.
* Don't retire aliases or implications for character, copyright, or
  meta tags any more, unless the tags are empty.
This commit is contained in:
evazion
2021-11-05 05:46:50 -05:00
parent 65ab7f1eb5
commit a5f589f9e0
6 changed files with 136 additions and 92 deletions

View File

@@ -1,6 +1,9 @@
# Removes tag aliases and implications if they haven't had any new uploads in # Removes inactive aliases and implications. 'Inactive' means aliases to empty
# the last two years. Runs weekly. Posts a message to the forum when aliases or # tags, implications from empty tags, and gentag and artist aliases that
# implications are retired. # haven't had any new posts in the last two years.
#
# Runs weekly. Posts a message to the forum when aliases or implications are
# retired.
# #
# @see DanbooruMaintenance#weekly # @see DanbooruMaintenance#weekly
module TagRelationshipRetirementService module TagRelationshipRetirementService
@@ -8,20 +11,15 @@ module TagRelationshipRetirementService
THRESHOLD = 2.years THRESHOLD = 2.years
def forum_topic_title FORUM_TOPIC_TITLE = "Retired tag aliases & implications"
"Retired tag aliases & implications" FORUM_TOPIC_BODY = "This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships."
end
def forum_topic_body
"This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships."
end
def forum_topic def forum_topic
topic = ForumTopic.where(title: forum_topic_title).first topic = ForumTopic.where(title: FORUM_TOPIC_TITLE).first
if topic.nil? if topic.nil?
CurrentUser.scoped(User.system) do CurrentUser.scoped(User.system) do
topic = ForumTopic.create!(creator: User.system, title: forum_topic_title, category_id: 1) topic = ForumTopic.create!(creator: User.system, title: FORUM_TOPIC_TITLE, category_id: 1)
ForumPost.create!(creator: User.system, body: forum_topic_body, topic: topic) ForumPost.create!(creator: User.system, body: FORUM_TOPIC_BODY, topic: topic)
end end
end end
topic topic
@@ -30,26 +28,23 @@ module TagRelationshipRetirementService
def find_and_retire! def find_and_retire!
messages = [] messages = []
[TagAlias, TagImplication].each do |model| inactive_relationships.each do |rel|
each_candidate(model) do |rel| rel.update!(status: "retired")
rel.update(status: "retired") messages << "The #{rel.relationship} [[#{rel.antecedent_name}]] -> [[#{rel.consequent_name}]] has been retired."
messages << rel.retirement_message
end
end end
updater = ForumUpdater.new(forum_topic) updater = ForumUpdater.new(forum_topic)
updater.update(messages.sort.join("\n")) updater.update(messages.sort.join("\n"))
end end
def each_candidate(model) def inactive_relationships
model.active.where("created_at < ?", THRESHOLD.ago).find_each do |rel| (inactive_aliases + TagAlias.active.empty + TagImplication.active.empty).uniq
if is_unused?(rel.consequent_name)
yield(rel)
end
end
end end
def is_unused?(name) def inactive_aliases
!Post.raw_tag_match(name).exists?(["created_at > ?", THRESHOLD.ago]) aliases = TagAlias.general.or(TagAlias.artist).active.where("tag_aliases.created_at < ?", THRESHOLD.ago)
aliases.select do |tag_alias|
!tag_alias.consequent_tag.posts.exists?(["created_at > ?", THRESHOLD.ago])
end
end end
end end

View File

@@ -6,6 +6,8 @@ class TagAlias < TagRelationship
before_create :delete_conflicting_relationships before_create :delete_conflicting_relationships
scope :empty, -> { joins(:consequent_tag).merge(Tag.empty) }
def self.to_aliased(names) def self.to_aliased(names)
names = Array(names).map(&:to_s) names = Array(names).map(&:to_s)
return [] if names.empty? return [] if names.empty?

View File

@@ -15,6 +15,8 @@ class TagImplication < TagRelationship
validate :meets_tag_size_requirements, on: :request validate :meets_tag_size_requirements, on: :request
validate :has_wiki_page, on: :request validate :has_wiki_page, on: :request
scope :empty, -> { joins(:antecedent_tag).merge(Tag.empty) }
concerning :HierarchyMethods do concerning :HierarchyMethods do
class_methods do class_methods do
def ancestors_of(names) def ancestors_of(names)

View File

@@ -16,6 +16,12 @@ class TagRelationship < ApplicationRecord
scope :deleted, -> {where(status: "deleted")} scope :deleted, -> {where(status: "deleted")}
scope :retired, -> {where(status: "retired")} scope :retired, -> {where(status: "retired")}
# TagAlias.artist, TagAlias.general, TagAlias.character, TagAlias.copyright, TagAlias.meta
# TagImplication.artist, TagImplication.general, TagImplication.character, TagImplication.copyright, TagImplication.meta
TagCategory.categories.each do |category|
scope category, -> { joins(:consequent_tag).where(consequent_tag: { category: TagCategory.mapping[category] }) }
end
before_validation :normalize_names before_validation :normalize_names
validates :status, inclusion: { in: STATUSES } validates :status, inclusion: { in: STATUSES }
validates :antecedent_name, presence: true validates :antecedent_name, presence: true
@@ -103,10 +109,6 @@ class TagRelationship < ApplicationRecord
# "TagAlias" -> "tag alias", "TagImplication" -> "tag implication" # "TagAlias" -> "tag alias", "TagImplication" -> "tag implication"
self.class.name.underscore.tr("_", " ") self.class.name.underscore.tr("_", " ")
end end
def retirement_message
"The #{relationship} [[#{antecedent_name}]] -> [[#{consequent_name}]] has been retired."
end
end end
def antecedent_and_consequent_are_different def antecedent_and_consequent_are_different

View File

@@ -0,0 +1,105 @@
require 'test_helper'
class RetireTagRelationshipsJobTest < ActiveJob::TestCase
context "RetireTagRelationshipsJob" do
should "create a new forum topic if one doesn't already exist" do
create(:tag_alias, created_at: 3.years.ago, antecedent_name: "0", consequent_name: "1")
RetireTagRelationshipsJob.perform_now
assert_equal(true, ForumTopic.exists?(title: TagRelationshipRetirementService::FORUM_TOPIC_TITLE))
assert_equal(true, ForumPost.exists?(body: TagRelationshipRetirementService::FORUM_TOPIC_BODY))
end
should "retire inactive gentag and artist aliases" do
ta0 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "0", consequent_name: "artist")
ta1 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "1", consequent_name: "general")
ta2 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "2", consequent_name: "character")
ta3 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "3", consequent_name: "copyright")
ta4 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "4", consequent_name: "meta")
as(User.system) do
create(:post, created_at: 3.years.ago, tag_string: "art:artist")
create(:post, created_at: 3.years.ago, tag_string: "gen:general")
create(:post, created_at: 3.years.ago, tag_string: "char:character")
create(:post, created_at: 3.years.ago, tag_string: "copy:copyright")
create(:post, created_at: 3.years.ago, tag_string: "meta:meta")
end
RetireTagRelationshipsJob.perform_now
assert_equal(true, ta0.reload.is_retired?)
assert_equal(true, ta1.reload.is_retired?)
assert_equal(false, ta2.reload.is_retired?)
assert_equal(false, ta3.reload.is_retired?)
assert_equal(false, ta4.reload.is_retired?)
end
should "not retire old aliases with recent posts" do
ta0 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "0", consequent_name: "artist")
ta1 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "1", consequent_name: "general")
ta2 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "2", consequent_name: "character")
ta3 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "3", consequent_name: "copyright")
ta4 = create(:tag_alias, created_at: 3.years.ago, antecedent_name: "4", consequent_name: "meta")
as(User.system) do
create(:post, created_at: 1.week.ago, tag_string: "art:artist")
create(:post, created_at: 1.week.ago, tag_string: "gen:general")
create(:post, created_at: 1.week.ago, tag_string: "char:character")
create(:post, created_at: 1.week.ago, tag_string: "copy:copyright")
create(:post, created_at: 1.week.ago, tag_string: "meta:meta")
end
RetireTagRelationshipsJob.perform_now
assert_equal(false, ta0.reload.is_retired?)
assert_equal(false, ta1.reload.is_retired?)
assert_equal(false, ta2.reload.is_retired?)
assert_equal(false, ta3.reload.is_retired?)
assert_equal(false, ta4.reload.is_retired?)
end
should "retire empty aliases" do
create(:tag, name: "artist", post_count: 0, category: Tag.categories.artist)
create(:tag, name: "general", post_count: 0, category: Tag.categories.general)
create(:tag, name: "character", post_count: 0, category: Tag.categories.character)
create(:tag, name: "copyright", post_count: 0, category: Tag.categories.copyright)
create(:tag, name: "meta", post_count: 0, category: Tag.categories.meta)
ta0 = create(:tag_alias, antecedent_name: "0", consequent_name: "artist")
ta1 = create(:tag_alias, antecedent_name: "1", consequent_name: "general")
ta2 = create(:tag_alias, antecedent_name: "2", consequent_name: "character")
ta3 = create(:tag_alias, antecedent_name: "3", consequent_name: "copyright")
ta4 = create(:tag_alias, antecedent_name: "4", consequent_name: "meta")
RetireTagRelationshipsJob.perform_now
assert_equal(true, ta0.reload.is_retired?)
assert_equal(true, ta1.reload.is_retired?)
assert_equal(true, ta2.reload.is_retired?)
assert_equal(true, ta3.reload.is_retired?)
assert_equal(true, ta4.reload.is_retired?)
end
should "retire empty implications" do
create(:tag, name: "artist", post_count: 0, category: Tag.categories.artist)
create(:tag, name: "general", post_count: 0, category: Tag.categories.general)
create(:tag, name: "character", post_count: 0, category: Tag.categories.character)
create(:tag, name: "copyright", post_count: 0, category: Tag.categories.copyright)
create(:tag, name: "meta", post_count: 0, category: Tag.categories.meta)
ta0 = create(:tag_implication, antecedent_name: "artist", consequent_name: "1")
ta1 = create(:tag_implication, antecedent_name: "general", consequent_name: "1")
ta2 = create(:tag_implication, antecedent_name: "character", consequent_name: "1")
ta3 = create(:tag_implication, antecedent_name: "copyright", consequent_name: "1")
ta4 = create(:tag_implication, antecedent_name: "meta", consequent_name: "1")
RetireTagRelationshipsJob.perform_now
assert_equal(true, ta0.reload.is_retired?)
assert_equal(true, ta1.reload.is_retired?)
assert_equal(true, ta2.reload.is_retired?)
assert_equal(true, ta3.reload.is_retired?)
assert_equal(true, ta4.reload.is_retired?)
end
end
end

View File

@@ -1,62 +0,0 @@
require 'test_helper'
class TagRelationshipRetirementServiceTest < ActiveSupport::TestCase
context ".forum_topic" do
subject { TagRelationshipRetirementService }
should "create a new topic if one doesn't already exist" do
assert_difference(-> { ForumTopic.count }) do
subject.forum_topic
end
end
should "create a new post if one doesn't already exist" do
assert_difference(-> { ForumPost.count }) do
subject.forum_topic
end
end
end
context ".each_candidate" do
subject { TagRelationshipRetirementService }
setup do
subject.stubs(:is_unused?).returns(true)
@new_alias = create(:tag_alias, antecedent_name: "aaa", consequent_name: "bbb")
@old_alias = create(:tag_alias, antecedent_name: "ccc", consequent_name: "ddd", created_at: 3.years.ago)
end
should "find old tag relationships" do
subject.each_candidate(TagAlias) do |rel|
assert_equal(@old_alias, rel)
end
end
should "not find new tag relationships" do
subject.each_candidate(TagAlias) do |rel|
assert_not_equal(@new_alias, rel)
end
end
end
context ".is_unused?" do
subject { TagRelationshipRetirementService }
setup do
@new_alias = create(:tag_alias, antecedent_name: "aaa", consequent_name: "bbb")
@new_post = create(:post, tag_string: "bbb")
@old_alias = create(:tag_alias, antecedent_name: "ccc", consequent_name: "ddd", created_at: 3.years.ago)
@old_post = create(:post, tag_string: "ddd", created_at: 3.years.ago)
end
should "return true if no recent post exists" do
assert(subject.is_unused?("ddd"))
end
should "return false if a recent post exists" do
refute(subject.is_unused?("bbb"))
end
end
end