FEATURE: Mixed case tagging (#6454)

- By default, behaviour is not changed: tags are made lowercase upon creation and edit.

- If force_lowercase_tags is disabled, then mixed case tags are allowed.

- Tags must remain case-insensitively unique. This is enforced by ActiveRecord and Postgres.

- A migration is added to provide a `UNIQUE` index on `lower(name)`. Migration includes a safety to correct any current tags that do not meet the criteria.

- A `where_name` scope is added to `models/tag.rb`, to allow easy case-insensitive lookups. This is used instead of `Tag.where(name: "blah")`.

- URLs remain lowercase. Mixed case URLs are functional, but have the lowercase equivalent as the canonical.
This commit is contained in:
David Taylor
2018-10-05 10:23:52 +01:00
committed by GitHub
parent 8430ea927e
commit 9bf522f227
23 changed files with 137 additions and 43 deletions

View File

@@ -13,7 +13,7 @@ describe DiscourseTagging do
let!(:tag1) { Fabricate(:tag, name: "fun") }
let!(:tag2) { Fabricate(:tag, name: "fun2") }
let!(:tag3) { Fabricate(:tag, name: "fun3") }
let!(:tag3) { Fabricate(:tag, name: "Fun3") }
before do
SiteSetting.tagging_enabled = true
@@ -186,7 +186,8 @@ describe DiscourseTagging do
it "returns only existing tag names" do
Fabricate(:tag, name: 'oldtag')
expect(described_class.tags_for_saving(['newtag', 'oldtag'], guardian).try(:sort)).to eq(['oldtag'])
Fabricate(:tag, name: 'oldTag2')
expect(described_class.tags_for_saving(['newtag', 'oldtag', 'oldtag2'], guardian)).to contain_exactly('oldtag', 'oldTag2')
end
end
@@ -205,5 +206,13 @@ describe DiscourseTagging do
expect(described_class.tags_for_saving(['math=fun', 'fun*2@gmail.com'], guardian).try(:sort)).to eq(['math=fun', 'fun2gmailcom'].sort)
end
end
describe "clean_tag" do
it "downcases new tags if setting enabled" do
expect(DiscourseTagging.clean_tag("HeLlO")).to eq("hello")
SiteSetting.force_lowercase_tags = false
expect(DiscourseTagging.clean_tag("HeLlO")).to eq("HeLlO")
end
end
end
end

View File

@@ -407,6 +407,7 @@ describe Search do
end
let!(:tag) { Fabricate(:tag) }
let!(:uppercase_tag) { Fabricate(:tag, name: "HeLlO") }
let(:tag_group) { Fabricate(:tag_group) }
let(:category) { Fabricate(:category) }
@@ -415,7 +416,7 @@ describe Search do
SiteSetting.tagging_enabled = true
post = Fabricate(:post, raw: 'I am special post')
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(Fabricate.build(:admin)), [tag.name])
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(Fabricate.build(:admin)), [tag.name, uppercase_tag.name])
post.topic.save
# we got to make this index (it is deferred)
@@ -424,6 +425,9 @@ describe Search do
result = Search.execute(tag.name)
expect(result.posts.length).to eq(1)
result = Search.execute("hElLo")
expect(result.posts.length).to eq(1)
SiteSetting.tagging_enabled = false
result = Search.execute(tag.name)
@@ -822,9 +826,10 @@ describe Search do
expect(Search.execute("sams post #sub-category").posts.length).to eq(1)
# tags
topic.tags = [Fabricate(:tag, name: 'alpha'), Fabricate(:tag, name: 'привет')]
topic.tags = [Fabricate(:tag, name: 'alpha'), Fabricate(:tag, name: 'привет'), Fabricate(:tag, name: 'HeLlO')]
expect(Search.execute('this is a test #alpha').posts.map(&:id)).to eq([post.id])
expect(Search.execute('this is a test #привет').posts.map(&:id)).to eq([post.id])
expect(Search.execute('this is a test #hElLo').posts.map(&:id)).to eq([post.id])
expect(Search.execute('this is a test #beta').posts.size).to eq(0)
end

View File

@@ -160,6 +160,7 @@ describe TopicQuery do
context 'tag filter' do
let(:tag) { Fabricate(:tag) }
let(:other_tag) { Fabricate(:tag) }
let(:uppercase_tag) { Fabricate(:tag, name: "HeLlO") }
before do
SiteSetting.tagging_enabled = true
@@ -169,6 +170,7 @@ describe TopicQuery do
let!(:tagged_topic1) { Fabricate(:topic, tags: [tag]) }
let!(:tagged_topic2) { Fabricate(:topic, tags: [other_tag]) }
let!(:tagged_topic3) { Fabricate(:topic, tags: [tag, other_tag]) }
let!(:tagged_topic4) { Fabricate(:topic, tags: [uppercase_tag]) }
let!(:no_tags_topic) { Fabricate(:topic) }
it "returns topics with the tag when filtered to it" do
@@ -186,6 +188,9 @@ describe TopicQuery do
expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics)
.to contain_exactly(tagged_topic1, tagged_topic2, tagged_topic3)
expect(TopicQuery.new(moderator, tags: ["hElLo"]).list_latest.topics)
.to contain_exactly(tagged_topic4)
end
it "can return topics with all specified tags" do