tags: track tag histories.

Track the history of the tag `category` and `is_deprecated` fields in
the `tag_versions` table.

Adds generic Versionable and VersionFor concerns that encapsulate most
of the history tracking logic. These concerns are designed to make it
easy to add history to any model.

There are a couple notable differences between tag versions and other versions:

* There is no 1 hour edit merge window. All changes to the `category`
  and `is_deprecated` fields produce a new version in the tag history.

* New versions aren't created when a tag is created. Versions are only
  created when a tag is edited for the first time. The tag's initial
  version isn't created until *after* the tag is edited for the first time.

For example, if you change the category of a tag that was last updated
10 years ago, that will create an initial version of the tag backdated
to 10 years ago, plus a new version for your edit.

This is for a few reasons:

* So that we don't have to create new tag versions every time a new tag
  is created. This would be wasteful because most tags never have their
  category or deprecation status change.
* So that if you make a typo tag, your name isn't recorded in the tag's
  history forever.
* So that we can create new tags in various places without having to know
  who created the tag (which may be unknown if the current user isn't set).
* Because we don't know the full history of most tags, so we have to
  deal with incomplete histories anyway.

This has a few important consequences:

* Most tags won't have any tag versions. They only gain tag versions if
  they're edited.
* You can't track /tag_versions to see newly created tags. It only
  shows changes to already existing tags.
* Tag version IDs won't be in strict chronological order. Higher IDs may
  have created_at timestamps before lower IDs. For example, if you
  change the category of a tag that is 10 years old, that will create an
  initial version with a high ID, but with a created_at timestamp dated
  to 10 years ago.

Fixes #4402: Track tag category changes
This commit is contained in:
evazion
2022-09-09 22:16:59 -05:00
parent 0c327a2228
commit 54a45a3021
12 changed files with 388 additions and 24 deletions

View File

@@ -345,6 +345,22 @@ class PostTest < ActiveSupport::TestCase
@post = FactoryBot.create(:post)
end
context "with a new tag" do
should "create the new tag" do
tag1 = create(:tag, name: "foo", post_count: 100, category: Tag.categories.character)
create(:post, tag_string: "foo bar")
tag2 = Tag.find_by_name("bar")
assert_equal(101, tag1.reload.post_count)
assert_equal(Tag.categories.character, tag1.category)
assert_equal(0, tag1.versions.count)
assert_equal(1, tag2.post_count)
assert_equal(Tag.categories.general, tag2.category)
assert_equal(0, tag2.versions.count)
end
end
context "with a banned artist" do
setup do
CurrentUser.scoped(FactoryBot.create(:admin_user)) do
@@ -490,7 +506,7 @@ class PostTest < ActiveSupport::TestCase
should "not remove the tag if the tag was already in the post" do
bad_tag = create(:tag, name: "bad_tag")
old_post = create(:post, tag_string: "bad_tag")
bad_tag.update!(is_deprecated: true)
bad_tag.update!(is_deprecated: true, updater: create(:user))
old_post.update!(tag_string: "asd bad_tag")
assert_equal("asd bad_tag", old_post.reload.tag_string)
@@ -525,9 +541,30 @@ class PostTest < ActiveSupport::TestCase
context "tagged with a metatag" do
context "for a tag category prefix" do
should "set the category of a new tag" do
create(:post, tag_string: "char:hoge")
create(:post, tag_string: "char:chen")
tag = Tag.find_by_name("chen")
assert_equal(Tag.categories.character, Tag.find_by_name("hoge").category)
assert_equal(Tag.categories.character, tag.category)
assert_equal(0, tag.versions.count)
end
should "change the category of an existing tag" do
user = create(:user)
tag = create(:tag, name: "hoge", post_count: 1)
post = as(user) { create(:post, tag_string: "char:hoge") }
assert_equal(Tag.categories.character, tag.reload.category)
assert_equal(2, tag.versions.count)
assert_equal(1, tag.first_version.version)
assert_nil(tag.first_version.updater)
assert_nil(tag.first_version.previous_version)
assert_equal(Tag.categories.general, tag.first_version.category)
assert_equal(2, tag.last_version.version)
assert_equal(user, tag.last_version.updater)
assert_equal(tag.first_version, tag.last_version.previous_version)
assert_equal(Tag.categories.character, tag.last_version.category)
end
should "change the category for an aliased tag" do