Fix #4965: Account for metatag prefixes when searching/linking

Drop the ability to write e.g. `create alias foo -> char:bar` in a BUR
to change the tag's type as a side effect. You can only use these
tag type prefixes in tag edits now.

This feature was only intended to be used in tag edits. The fact it
worked elsewhere was unintended behavior.

This feature was problematic because it relied on `Tag.find_or_create_by_name`
automagically changing the tag's category when the tag name contained a
tag category prefix, e.g. `char:hatsune_miku`. This meant that merely
looking up a tag could have the side effect of changing its category.
It was also bad because `find_or_create_by_name` had a hidden dependency
on the current user, which may not be set or available in all contexts.
This commit is contained in:
evazion
2022-09-10 02:37:40 -05:00
parent f36f1ff37b
commit 015c6dc7db
6 changed files with 24 additions and 38 deletions

View File

@@ -215,7 +215,7 @@ class Artist < ApplicationRecord
# potential race condition but unlikely # potential race condition but unlikely
unless TagImplication.where(:antecedent_name => name, :consequent_name => "banned_artist").exists? unless TagImplication.where(:antecedent_name => name, :consequent_name => "banned_artist").exists?
Tag.find_or_create_by_name("artist:banned_artist") # ensure the banned_artist exists and is an artist tag. Tag.find_or_create_by_name("banned_artist", category: "artist", current_user: banner)
TagImplication.approve!(antecedent_name: name, consequent_name: "banned_artist", approver: banner) TagImplication.approve!(antecedent_name: name, consequent_name: "banned_artist", approver: banner)
end end

View File

@@ -590,7 +590,7 @@ class Post < ApplicationRecord
self.source = value self.source = value
in category, name if category.in?(PostEdit::CATEGORIZATION_METATAGS) in category, name if category.in?(PostEdit::CATEGORIZATION_METATAGS)
Tag.find_or_create_by_name("#{category}:#{name}", creator: CurrentUser.user) Tag.find_or_create_by_name(name, category: category, current_user: CurrentUser.user)
else else
nil nil

View File

@@ -193,39 +193,14 @@ class Tag < ApplicationRecord
names.map {|x| find_or_create_by_name(x).name} names.map {|x| find_or_create_by_name(x).name}
end end
def find_or_create_by_name(name, creator: CurrentUser.user) def find_or_create_by_name(name, category: nil, current_user: nil)
name = normalize_name(name) tag = find_or_create_by(name: normalize_name(name))
category = nil
if name =~ /\A(#{categories.regexp}):(.+)\Z/ if category.present? && current_user.present? && Pundit.policy!(current_user, tag).can_change_category?
category = $1 tag.update!(category: categories.value_for(category))
name = $2
end end
tag = find_by_name(name) tag
if tag
if category
category_id = categories.value_for(category)
# in case a category change hasn't propagated to this server yet,
# force an update the local cache. This may get overwritten in the
# next few lines if the category is changed.
tag.update_category_cache
if Pundit.policy!(creator, tag).can_change_category?
tag.update(category: category_id)
end
end
tag
else
Tag.new.tap do |t|
t.name = name
t.category = categories.value_for(category)
t.save
end
end
end end
end end
end end
@@ -440,7 +415,7 @@ class Tag < ApplicationRecord
concerning :CosplayTagMethods do concerning :CosplayTagMethods do
def create_character_tag_for_cosplay_tag def create_character_tag_for_cosplay_tag
character_name = name.delete_suffix("_(cosplay)") character_name = name.delete_suffix("_(cosplay)")
Tag.find_or_create_by_name("char:#{character_name}") Tag.find_or_create_by_name(character_name, category: "character", current_user: CurrentUser.user)
end end
def is_cosplay_tag? def is_cosplay_tag?

View File

@@ -98,6 +98,10 @@ class ArtistTest < ActiveSupport::TestCase
assert_equal("aaa banned_artist", @post.reload.tag_string) assert_equal("aaa banned_artist", @post.reload.tag_string)
end end
should "create the banned_artist tag if it doesn't already exist" do
assert_equal(true, Tag.exists?(name: "banned_artist", category: Tag.categories.artist))
end
should "set the approver of the banned_artist implication" do should "set the approver of the banned_artist implication" do
ta = TagImplication.where(:antecedent_name => "aaa", :consequent_name => "banned_artist").first ta = TagImplication.where(:antecedent_name => "aaa", :consequent_name => "banned_artist").first
assert_equal(@admin.id, ta.approver.id) assert_equal(@admin.id, ta.approver.id)

View File

@@ -165,6 +165,13 @@ class BulkUpdateRequestTest < ActiveSupport::TestCase
assert_equal(["Can't create alias tag -> tag_ ('tag_' cannot end with an underscore)"], @bur.errors.full_messages) assert_equal(["Can't create alias tag -> tag_ ('tag_' cannot end with an underscore)"], @bur.errors.full_messages)
end end
should "fail if the consequent name contains a tag type prefix" do
@bur = build(:bulk_update_request, script: "alias blah -> char:bar")
assert_equal(false, @bur.valid?)
assert_equal(["Can't create alias blah -> char:bar ('char:bar' cannot begin with 'char:')"], @bur.errors.full_messages)
end
should "be case-insensitive" do should "be case-insensitive" do
@bur = create_bur!("CREATE ALIAS AAA -> BBB", @admin) @bur = create_bur!("CREATE ALIAS AAA -> BBB", @admin)

View File

@@ -93,7 +93,7 @@ class TagTest < ActiveSupport::TestCase
tag = FactoryBot.create(:tag) tag = FactoryBot.create(:tag)
assert_difference("Tag.count", 0) do assert_difference("Tag.count", 0) do
assert_equal(Tag.categories.general, tag.category) assert_equal(Tag.categories.general, tag.category)
Tag.find_or_create_by_name("artist:#{tag.name}") Tag.find_or_create_by_name(tag.name, category: "artist", current_user: @builder)
tag.reload tag.reload
assert_equal(Tag.categories.artist, tag.category) assert_equal(Tag.categories.artist, tag.category)
end end
@@ -101,14 +101,14 @@ class TagTest < ActiveSupport::TestCase
should "not change category when the tag is too large to be changed by a builder" do should "not change category when the tag is too large to be changed by a builder" do
tag = FactoryBot.create(:tag, post_count: 1001) tag = FactoryBot.create(:tag, post_count: 1001)
Tag.find_or_create_by_name("artist:#{tag.name}", creator: @builder) Tag.find_or_create_by_name(tag.name, category: "artist", current_user: @builder)
assert_equal(0, tag.reload.category) assert_equal(0, tag.reload.category)
end end
should "not change category when the tag is too large to be changed by a member" do should "not change category when the tag is too large to be changed by a member" do
tag = FactoryBot.create(:tag, post_count: 51) tag = FactoryBot.create(:tag, post_count: 51)
Tag.find_or_create_by_name("artist:#{tag.name}", creator: FactoryBot.create(:member_user)) Tag.find_or_create_by_name(tag.name, category: "artist", current_user: create(:member_user))
assert_equal(0, tag.reload.category) assert_equal(0, tag.reload.category)
end end
@@ -118,7 +118,7 @@ class TagTest < ActiveSupport::TestCase
assert_equal(1, post.tag_count_general) assert_equal(1, post.tag_count_general)
assert_equal(0, post.tag_count_character) assert_equal(0, post.tag_count_character)
tag = Tag.find_or_create_by_name("char:test") tag = Tag.find_or_create_by_name("test", category: "char", current_user: @builder)
post.reload post.reload
assert_equal(0, post.tag_count_general) assert_equal(0, post.tag_count_general)
assert_equal(1, post.tag_count_character) assert_equal(1, post.tag_count_character)
@@ -134,7 +134,7 @@ class TagTest < ActiveSupport::TestCase
should "be created with the type when one doesn't exist" do should "be created with the type when one doesn't exist" do
assert_difference("Tag.count", 1) do assert_difference("Tag.count", 1) do
tag = Tag.find_or_create_by_name("artist:hoge") tag = Tag.find_or_create_by_name("hoge", category: "artist", current_user: @builder)
assert_equal("hoge", tag.name) assert_equal("hoge", tag.name)
assert_equal(Tag.categories.artist, tag.category) assert_equal(Tag.categories.artist, tag.category)
end end