diff --git a/app/models/tag.rb b/app/models/tag.rb index 46e55d74f9..f2168ae904 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -39,6 +39,8 @@ class Tag < ApplicationRecord HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/ + RECENT_STATUS_LIMIT = 1000 + validates :name, presence: true, format: { with: HASHTAG_NAME_RE } validates :display_name, format: { with: HASHTAG_NAME_RE } validate :validate_name_change, if: -> { !new_record? && name_changed? } @@ -53,7 +55,7 @@ class Tag < ApplicationRecord scope :not_trendable, -> { where(trendable: false) } scope :recently_used, lambda { |account| joins(:statuses) - .where(statuses: { id: account.statuses.select(:id).limit(1000) }) + .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) }) .group(:id).order(Arel.sql('count(*) desc')) } scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 6177b7a25a..69aaeed0af 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -100,6 +100,38 @@ RSpec.describe Tag do end end + describe '.recently_used' do + let(:account) { Fabricate(:account) } + let(:other_person_status) { Fabricate(:status) } + let(:out_of_range) { Fabricate(:status, account: account) } + let(:older_in_range) { Fabricate(:status, account: account) } + let(:newer_in_range) { Fabricate(:status, account: account) } + let(:unused_tag) { Fabricate(:tag) } + let(:used_tag_one) { Fabricate(:tag) } + let(:used_tag_two) { Fabricate(:tag) } + let(:used_tag_on_out_of_range) { Fabricate(:tag) } + + before do + stub_const 'Tag::RECENT_STATUS_LIMIT', 2 + + other_person_status.tags << used_tag_one + + out_of_range.tags << used_tag_on_out_of_range + + older_in_range.tags << used_tag_one + older_in_range.tags << used_tag_two + + newer_in_range.tags << used_tag_one + end + + it 'returns tags used by account within last X statuses ordered most used first' do + results = described_class.recently_used(account) + + expect(results) + .to eq([used_tag_one, used_tag_two]) + end + end + describe '.find_normalized' do it 'returns tag for a multibyte case-insensitive name' do upcase_string = 'abcABCabcABCやゆよ'