feat: 別名を検索に展開(#20) (#243)

#20

#20 テスト・ケースのみ追記

#20

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #243
This commit was merged in pull request #243.
This commit is contained in:
2026-01-28 23:47:56 +09:00
parent effde89b07
commit 200d457d22
8 changed files with 168 additions and 39 deletions
+4 -2
View File
@@ -8,8 +8,10 @@ class Post < ApplicationRecord
has_many :active_post_tags, -> { kept }, class_name: 'PostTag', inverse_of: :post
has_many :post_tags_with_discarded, -> { with_discarded }, class_name: 'PostTag'
has_many :tags, through: :active_post_tags
has_many :user_post_views, dependent: :destroy
has_many :post_similarities
has_many :user_post_views, dependent: :delete_all
has_many :post_similarities, dependent: :delete_all
has_one_attached :thumbnail
before_validation :normalise_url
+33 -25
View File
@@ -3,53 +3,53 @@ class Tag < ApplicationRecord
;
end
has_many :post_tags, dependent: :delete_all, inverse_of: :tag
has_many :post_tags, inverse_of: :tag
has_many :active_post_tags, -> { kept }, class_name: 'PostTag', inverse_of: :tag
has_many :post_tags_with_discarded, -> { with_discarded }, class_name: 'PostTag'
has_many :posts, through: :active_post_tags
has_many :tag_aliases, dependent: :destroy
has_many :nico_tag_relations, foreign_key: :nico_tag_id, dependent: :destroy
has_many :linked_tags, through: :nico_tag_relations, source: :tag
has_many :reversed_nico_tag_relations, class_name: 'NicoTagRelation',
foreign_key: :tag_id,
dependent: :destroy
has_many :reversed_nico_tag_relations,
class_name: 'NicoTagRelation', foreign_key: :tag_id, dependent: :destroy
has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag
has_many :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
has_many :children, through: :tag_implications, source: :tag
has_many :reversed_tag_implications, class_name: 'TagImplication',
foreign_key: :tag_id,
dependent: :destroy
has_many :reversed_tag_implications,
class_name: 'TagImplication', foreign_key: :tag_id, dependent: :destroy
has_many :parents, through: :reversed_tag_implications, source: :parent_tag
has_many :tag_similarities, dependent: :delete_all
belongs_to :tag_name
delegate :name, to: :tag_name, allow_nil: true
validates :tag_name, presence: true
enum :category, { deerjikist: 'deerjikist',
meme: 'meme',
character: 'character',
general: 'general',
material: 'material',
nico: 'nico',
meta: 'meta' }
enum :category, deerjikist: 'deerjikist',
meme: 'meme',
character: 'character',
general: 'general',
material: 'material',
nico: 'nico',
meta: 'meta'
validates :category, presence: true, inclusion: { in: Tag.categories.keys }
validate :nico_tag_name_must_start_with_nico
validate :tag_name_must_be_canonical
scope :nico_tags, -> { where(category: :nico) }
CATEGORY_PREFIXES = {
'gen:' => 'general',
'djk:' => 'deerjikist',
'meme:' => 'meme',
'chr:' => 'character',
'mtr:' => 'material',
'meta:' => 'meta' }.freeze
'gen:' => :general,
'djk:' => :deerjikist,
'meme:' => :meme,
'chr:' => :character,
'mtr:' => :material,
'meta:' => :meta }.freeze
def name= val
(self.tag_name ||= build_tag_name).name = val
@@ -60,11 +60,11 @@ class Tag < ApplicationRecord
end
def self.tagme
@tagme ||= find_or_create_by_tag_name!('タグ希望', category: 'meta')
@tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta)
end
def self.bot
@bot ||= find_or_create_by_tag_name!('bot操作', category: 'meta')
@bot ||= find_or_create_by_tag_name!('bot操作', category: :meta)
end
def self.normalise_tags tag_names, with_tagme: true, deny_nico: true
@@ -74,8 +74,8 @@ class Tag < ApplicationRecord
tags = tag_names.map do |name|
pf, cat = CATEGORY_PREFIXES.find { |p, _| name.start_with?(p) } || ['', nil]
name = name.delete_prefix(pf)
find_or_create_by_tag_name!(name, category: (cat || 'general')).tap do |tag|
name = TagName.canonicalise(name.delete_prefix(pf)).first
find_or_create_by_tag_name!(name, category: (cat || :general)).tap do |tag|
if cat && tag.category != cat
tag.update!(category: cat)
end
@@ -111,6 +111,8 @@ class Tag < ApplicationRecord
def self.find_or_create_by_tag_name! name, category:
tn = TagName.find_or_create_by!(name: name.to_s.strip)
tn = tn.canonical if tn.canonical_id?
Tag.find_or_create_by!(tag_name_id: tn.id) do |t|
t.category = category
end
@@ -127,4 +129,10 @@ class Tag < ApplicationRecord
errors.add :name, 'ニコニコ・タグの命名規則に反してゐます.'
end
end
def tag_name_must_be_canonical
if tag_name&.canonical_id?
errors.add :tag_name, 'tag_names へは実体を示す必要があります.'
end
end
end
-6
View File
@@ -1,6 +0,0 @@
class TagAlias < ApplicationRecord
belongs_to :tag
validates :tag_id, presence: true
validates :name, presence: true, length: { maximum: 255 }, uniqueness: true
end
+33
View File
@@ -6,4 +6,37 @@ class TagName < ApplicationRecord
has_many :aliases, class_name: 'TagName', foreign_key: :canonical_id
validates :name, presence: true, length: { maximum: 255 }, uniqueness: true
validate :canonical_must_be_canonical
validate :alias_name_must_not_have_prefix
validate :canonical_must_not_be_present_with_tag_or_wiki_page
def self.canonicalise names
names = Array(names).map { |n| n.to_s.strip }.reject(&:blank?)
return [] if names.blank?
tns = TagName.includes(:canonical).where(name: names).index_by(&:name)
names.map { |name| tns[name]&.canonical&.name || name }.uniq
end
private
def canonical_must_be_canonical
if canonical&.canonical_id?
errors.add :canonical, 'canonical は実体を示す必要があります.'
end
end
def alias_name_must_not_have_prefix
if canonical_id? && name.to_s.include?(':')
errors.add :name, 'エーリアス名にプレフィクスを含むことはできません.'
end
end
def canonical_must_not_be_present_with_tag_or_wiki_page
if canonical_id? && (tag || wiki_page)
errors.add :canonical, 'タグもしくは Wiki の参照がある名前はエーリアスになれません.'
end
end
end