このコミットが含まれているのは:
2026-09-27 03:47:27 +09:00
コミット 701830b643
18個のファイルの変更、260行の追加、187行の削除
+3 -1
ファイルの表示
@@ -133,12 +133,14 @@ class MaterialsController < ApplicationController
block = MaterialImportBlockMatcher.match_for_sha256(file_sha256) block = MaterialImportBlockMatcher.match_for_sha256(file_sha256)
return render_material_import_block(block) if block return render_material_import_block(block) if block
locale = resolve_locale!
uploaded_blob = build_uploaded_material_blob!(file, file_sha256) uploaded_blob = build_uploaded_material_blob!(file, file_sha256)
begin begin
Material.transaction do Material.transaction do
MaterialVersionRecorder.ensure_snapshot!(material, created_by_user: current_user) MaterialVersionRecorder.ensure_snapshot!(material, created_by_user: current_user)
tag = resolve_material_tag!(tag_name_raw) tag = resolve_material_tag!(locale, tag_name_raw)
material.assign_attributes(tag:, url:, updated_by_user: current_user) material.assign_attributes(tag:, url:, updated_by_user: current_user)
if uploaded_blob if uploaded_blob
material.file.attach(uploaded_blob) material.file.attach(uploaded_blob)
+7 -2
ファイルの表示
@@ -218,7 +218,7 @@ class PostsController < ApplicationController
preflight = PostCreatePreflight.new( preflight = PostCreatePreflight.new(
attributes: post_create_attributes, attributes: post_create_attributes,
thumbnail: params[:thumbnail], thumbnail: params[:thumbnail],
host: request.base_url).run host: request.base_url).run(locale)
return render json: dry_run_json(preflight) if bool?(:dry) return render json: dry_run_json(preflight) if bool?(:dry)
if preflight[:existing_post_id].present? if preflight[:existing_post_id].present?
post = Post.new(url: preflight[:url]) post = Post.new(url: preflight[:url])
@@ -273,13 +273,18 @@ class PostsController < ApplicationController
return head :forbidden unless current_user.gte_member? return head :forbidden unless current_user.gte_member?
return head :unsupported_media_type unless request.content_mime_type == Mime[:multipart_form] return head :unsupported_media_type unless request.content_mime_type == Mime[:multipart_form]
return head :payload_too_large if request.content_length.to_i > MAX_BULK_REQUEST_BYTES return head :payload_too_large if request.content_length.to_i > MAX_BULK_REQUEST_BYTES
locale = resolve_locale!
posts = parse_bulk_posts_manifest posts = parse_bulk_posts_manifest
thumbnails = parse_bulk_thumbnails(posts.length) thumbnails = parse_bulk_thumbnails(posts.length)
result = PostBulkCreator.new( result = PostBulkCreator.new(
actor: current_user, actor: current_user,
posts:, posts:,
thumbnails:, thumbnails:,
host: request.base_url).run host: request.base_url).run(locale)
render json: result render json: result
rescue JSON::ParserError rescue JSON::ParserError
render_bad_request 'posts manifest の JSON が不正です.' render_bad_request 'posts manifest の JSON が不正です.'
+8 -7
ファイルの表示
@@ -406,10 +406,10 @@ class TagsController < ApplicationController
return render_unprocessable_entity('カテゴリは必須です.', field: :category) if category.blank? return render_unprocessable_entity('カテゴリは必須です.', field: :category) if category.blank?
return render_unprocessable_entity '廃止状態は必須です.', field: :deprecated unless params.key?(:deprecated) return render_unprocessable_entity '廃止状態は必須です.', field: :deprecated unless params.key?(:deprecated)
return unless validate_tag_rename(tag, name)
locale = resolve_locale! locale = resolve_locale!
return unless validate_tag_rename(locale, tag, name)
alias_names = params[:aliases].to_s.split.uniq alias_names = params[:aliases].to_s.split.uniq
parent_names = params[:parent_tags].to_s.split.uniq parent_names = params[:parent_tags].to_s.split.uniq
deprecated = bool?(:deprecated) deprecated = bool?(:deprecated)
@@ -455,6 +455,8 @@ class TagsController < ApplicationController
return head :unauthorized unless current_user return head :unauthorized unless current_user
return head :forbidden unless current_user.gte_member? return head :forbidden unless current_user.gte_member?
locale = resolve_locale!
name = params[:name].presence name = params[:name].presence
category = params[:category].presence category = params[:category].presence
deprecated_given = params.key?(:deprecated) deprecated_given = params.key?(:deprecated)
@@ -462,7 +464,7 @@ class TagsController < ApplicationController
tag = Tag.find(params[:id]) tag = Tag.find(params[:id])
return unless validate_tag_rename(tag, name) return unless validate_tag_rename(locale, tag, name)
if category.present? && category == 'nico' if category.present? && category == 'nico'
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
@@ -684,7 +686,7 @@ class TagsController < ApplicationController
created_by_user:) created_by_user:)
end end
def validate_tag_rename tag, name def validate_tag_rename locale, tag, name
return true if name.blank? || name == tag.name return true if name.blank? || name == tag.name
if tag.in?([Tag.tagme, Tag.bot, Tag.no_deerjikist, Tag.video, Tag.niconico]) if tag.in?([Tag.tagme, Tag.bot, Tag.no_deerjikist, Tag.video, Tag.niconico])
@@ -692,9 +694,8 @@ class TagsController < ApplicationController
return false return false
end end
target_tag_name = TagName.find_by(name:) target_tag_name = TagName.find_by(language_code: locale.language_code, name:)
return true if target_tag_name.nil? return true unless target_tag_name&.primary_flg
return true if target_tag_name.canonical_id?
render_unprocessable_entity 'その名前は既に使はれてゐます.', field: :name render_unprocessable_entity 'その名前は既に使はれてゐます.', field: :name
false false
+4 -5
ファイルの表示
@@ -135,7 +135,7 @@ class Tag < ApplicationRecord
raise SectionLiteralParseError.new(raw_name, raw_name) raise SectionLiteralParseError.new(raw_name, raw_name)
end end
name = TagName.canonicalise(name).first name = TagName.canonicalise(locale, name).first
find_or_create_by_tag_name!(locale, name, category: (cat || :general)).tap do |tag| find_or_create_by_tag_name!(locale, name, category: (cat || :general)).tap do |tag|
if deny_deprecated && tag.deprecated? if deny_deprecated && tag.deprecated?
@@ -235,10 +235,7 @@ class Tag < ApplicationRecord
end end
tag = tn.tag tag = tn.tag
if tag return tag if tag
tag.update!(tag_name_id: tn.id) if tag.tag_name_id != tn.id
return tag
end
tag = Tag.create!(tag_name: tn, category:) tag = Tag.create!(tag_name: tn, category:)
tn.update!(tag:) tn.update!(tag:)
@@ -279,6 +276,8 @@ class Tag < ApplicationRecord
end end
TagVersioning.record!(source_tag, event_type: :discard, created_by_user:) TagVersioning.record!(source_tag, event_type: :discard, created_by_user:)
source_tag.tag_names.update_all(tag_id: source_tag.id, primary_key: false,
updated_at: Time.current)
source_tag.destroy! source_tag.destroy!
source_tag_name.update_columns(canonical_id: target_tag.tag_name_id, source_tag_name.update_columns(canonical_id: target_tag.tag_name_id,
+25 -19
ファイルの表示
@@ -1,24 +1,36 @@
class TagName < ApplicationRecord class TagName < ApplicationRecord
belongs_to :language, foreign_key: :language_code, primary_key: :code
belongs_to :tag, optional: true belongs_to :tag, optional: true
has_one :wiki_page has_one :wiki_page
belongs_to :canonical, class_name: 'TagName', optional: true validates :name, presence: true,
has_many :aliases, class_name: 'TagName', foreign_key: :canonical_id length: { maximum: 255 },
uniqueness: { scope: :language_code }
validates :name, presence: true, length: { maximum: 255 }, uniqueness: true
validate :canonical_must_be_canonical
validate :alias_name_must_not_have_prefix validate :alias_name_must_not_have_prefix
validate :canonical_must_not_be_present_with_tag_or_wiki_page validate :alias_must_not_have_wiki_page
validate :name_must_be_sanitised validate :name_must_be_sanitised
def self.canonicalise names def primary? = primary_flg
def canonical = TagName.find_by(language:, tag:, primary_flg: true)
def aliases = TagName.where(language:, tag:, primary_flg: false)
def self.canonicalise locale, names
names = Array(names).map { |n| n.to_s.strip }.reject(&:blank?) names = Array(names).map { |n| n.to_s.strip }.reject(&:blank?)
return [] if names.blank? return [] if names.blank?
tns = TagName.includes(:canonical).where(name: names).index_by(&:name) tns = TagName.where(language_code: locale.language_code, name: names).index_by(&:name)
names.map { |name| tns[name]&.canonical&.name || name }.uniq names.map { |name|
if tns[name].primary_flg
name
else
TagName.find_by(language_code: locale.language_code,
tag_id: tns[name].tag_id,
primary_flg: true).name
end
}.uniq
end end
def self.generate_name locale, tag, name def self.generate_name locale, tag, name
@@ -29,21 +41,15 @@ class TagName < ApplicationRecord
private private
def canonical_must_be_canonical
if canonical&.canonical_id?
errors.add :canonical, 'canonical は実体を示す必要があります.'
end
end
def alias_name_must_not_have_prefix def alias_name_must_not_have_prefix
if canonical_id? && name.to_s.include?(':') if !(primary?) && name.to_s.include?(':')
errors.add :name, 'エーリアス名にプレフィクスを含むことはできません.' errors.add :name, 'エーリアス名にプレフィクスを含むことはできません.'
end end
end end
def canonical_must_not_be_present_with_tag_or_wiki_page def alias_must_not_have_wiki_page
if canonical_id? && (tag || wiki_page) if !(primary?) && wiki_page
errors.add :canonical, 'タグもしくは Wiki の参照がある名前はエーリアスになれません.' errors.add :primary_flg, 'Wiki 参照がある名前はエーリアスになれません.'
end end
end end
+26 -19
ファイルの表示
@@ -15,31 +15,38 @@ class TagNameSanitisationRule < ApplicationRecord
end end
def apply! def apply!
TagName.find_each do |tn| Language.find_each do |language|
name = sanitise(tn.name) TagName.where(language:).find_each do |tn|
next if name == tn.name name = sanitise(tn.name)
next if name == tn.name
TagName.transaction do TagName.transaction do
existing_tn = TagName.find_by(name:) existing_tn = TagName.find_by(language:, name:)
if existing_tn if existing_tn
existing_tn = existing_tn.canonical || existing_tn unless existing_tn.primary_flg
next if existing_tn.id == tn.id existing_tn = TagName.find_by!(language:,
tag_id: existing_tn.tag_id,
primary_flg: true)
end
next if existing_tn.id == tn.id
existing_tag = Tag.find_by(tag_name_id: existing_tn.id) existing_tag = existing_tn.tag
source_tag = Tag.find_by(tag_name_id: tn.id) source_tag = tn.tag
if existing_tag if existing_tag
Tag.merge_tags!(existing_tag, source_tag) if tn.tag Tag.merge_tags!(existing_tag, source_tag) if tn.tag
elsif source_tag elsif source_tag
source_tag.update_columns(tag_name_id: existing_tn.id, updated_at: Time.current) source_tag.update_columns(tag_name_id: existing_tn.id, updated_at: Time.current)
existing_tn.update_columns(tag_id: source_tag.id, updated_at: Time.current)
end
tn.destroy!
next
end end
tn.destroy!
next # TagName 側の自動サニタイズを回避
tn.update_columns(name:, updated_at: Time.current)
end end
# TagName 側の自動サニタイズを回避
tn.update_columns(name:, updated_at: Time.current)
end end
end end
end end
+32 -27
ファイルの表示
@@ -8,13 +8,13 @@ class PostCreatePlan
@existing_tags_by_name = nil @existing_tags_by_name = nil
end end
def build! def build! locale
direct_tag_specs, tag_sections = parse_direct_tag_specs direct_tag_specs, tag_sections = parse_direct_tag_specs(locale)
default_tag_specs = build_default_tag_specs(direct_tag_specs) default_tag_specs = build_default_tag_specs(direct_tag_specs)
snapshot_tag_specs = merge_tag_specs(direct_tag_specs + default_tag_specs) snapshot_tag_specs = merge_tag_specs(direct_tag_specs + default_tag_specs)
preload_existing_tags_by_name!(snapshot_tag_specs.map { _1[:name] }) preload_existing_tags_by_name!(snapshot_tag_specs.map { _1[:name] })
validate_new_tag_specs!(snapshot_tag_specs) validate_new_tag_specs!(snapshot_tag_specs)
post_tag_specs = expand_parent_tag_specs(snapshot_tag_specs) post_tag_specs = expand_parent_tag_specs(locale, snapshot_tag_specs)
video_ms = normalise_video_ms(snapshot_tag_specs) video_ms = normalise_video_ms(snapshot_tag_specs)
validate_video_sections!(video_ms, tag_sections) validate_video_sections!(video_ms, tag_sections)
parent_post_ids = normalise_parent_post_ids parent_post_ids = normalise_parent_post_ids
@@ -42,13 +42,13 @@ class PostCreatePlan
def tag_names = @attributes[:tags].to_s.split def tag_names = @attributes[:tags].to_s.split
def parse_direct_tag_specs def parse_direct_tag_specs locale
tag_sections = { } tag_sections = { }
direct_tag_specs = [] direct_tag_specs = []
tag_names.each do |raw_name| tag_names.each do |raw_name|
tag_name, category, sections = parse_raw_tag_name(raw_name) tag_name, category, sections = parse_raw_tag_name(locale, raw_name)
existing_tag = existing_tags_by_name[tag_name] existing_tag = existing_tags_by_name(locale)[tag_name]
raise Tag::DeprecatedTagNormalisationError, [existing_tag.name] if existing_tag&.deprecated? raise Tag::DeprecatedTagNormalisationError, [existing_tag.name] if existing_tag&.deprecated?
direct_tag_specs << { direct_tag_specs << {
@@ -65,7 +65,7 @@ class PostCreatePlan
[merge_tag_specs(direct_tag_specs), tag_sections] [merge_tag_specs(direct_tag_specs), tag_sections]
end end
def parse_raw_tag_name raw_name def parse_raw_tag_name locale, raw_name
name = raw_name.to_s name = raw_name.to_s
prefix, category = prefix, category =
Tag::CATEGORY_PREFIXES.find { Tag::CATEGORY_PREFIXES.find {
@@ -74,7 +74,7 @@ class PostCreatePlan
name = name.sub(/\A#{ prefix }/i, '') name = name.sub(/\A#{ prefix }/i, '')
sections = [] sections = []
while (match = name.match(/\A(\S*?)\[([^\[\]\s]*)-([^\[\]\s]*)\](\S*)\z/)) while match = name.match(/\A(\S*?)\[([^\[\]\s]*)-([^\[\]\s]*)\](\S*)\z/)
name = "#{ match[1] }#{ match[4] }" name = "#{ match[1] }#{ match[4] }"
next if match[2].empty? && match[3].empty? next if match[2].empty? && match[3].empty?
@@ -87,17 +87,17 @@ class PostCreatePlan
raise Tag::SectionLiteralParseError.new(raw_name, raw_name) raise Tag::SectionLiteralParseError.new(raw_name, raw_name)
end end
[resolved_tag_name(name), category&.to_sym, sections] [resolved_tag_name(locale, name), category&.to_sym, sections]
end end
def build_default_tag_specs direct_tag_specs def build_default_tag_specs locale, direct_tag_specs
default_tag_specs = [] default_tag_specs = []
if direct_tag_specs.length < 10 && direct_tag_specs.none? { _1[:name] == TAGME_TAG_NAME } if direct_tag_specs.length < 10 && direct_tag_specs.none? { _1[:name] == TAGME_TAG_NAME }
default_tag_specs << { default_tag_specs << {
name: TAGME_TAG_NAME, name: TAGME_TAG_NAME,
category: :meta } category: :meta }
end end
if direct_tag_specs.none? { deerjikist_tag_spec?(_1) } if direct_tag_specs.none? { deerjikist_tag_spec?(locale, _1) }
default_tag_specs << { default_tag_specs << {
name: NO_DEERJIKIST_TAG_NAME, name: NO_DEERJIKIST_TAG_NAME,
category: :meta } category: :meta }
@@ -131,8 +131,10 @@ class PostCreatePlan
raise ActiveRecord::RecordInvalid, post raise ActiveRecord::RecordInvalid, post
end end
def expand_parent_tag_specs snapshot_tag_specs def expand_parent_tag_specs locale, snapshot_tag_specs
existing_snapshot_tags = snapshot_tag_specs.filter_map { existing_tags_by_name[_1[:name]] } existing_snapshot_tags = snapshot_tag_specs.filter_map do
existing_tags_by_name(locale)[_1[:name]]
end
expanded_parent_specs = expanded_parent_specs =
Tag.expand_parent_tags(existing_snapshot_tags) Tag.expand_parent_tags(existing_snapshot_tags)
.reject(&:deprecated?) .reject(&:deprecated?)
@@ -156,33 +158,33 @@ class PostCreatePlan
}.values.sort_by { _1[:name] } }.values.sort_by { _1[:name] }
end end
def existing_tags_by_name def existing_tags_by_name locale
@existing_tags_by_name ||= begin @existing_tags_by_name ||= begin
names = tag_names.map { canonical_tag_name_without_sections(_1) }.uniq names = tag_names.map { canonical_tag_name_without_sections(locale, _1) }.uniq
Tag.joins(:tag_name).where(tag_names: { name: names }).index_by(&:name) Tag.joins(:tag_name).where(tag_names: { name: names }).index_by(&:name)
end end
end end
def preload_existing_tags_by_name! names def preload_existing_tags_by_name! locale, names
wanted_names = Array(names).map { _1.to_s }.reject(&:blank?).uniq wanted_names = Array(names).map { _1.to_s }.reject(&:blank?).uniq
missing_names = wanted_names - existing_tags_by_name.keys missing_names = wanted_names - existing_tags_by_name(locale).keys
return if missing_names.empty? return if missing_names.empty?
existing_tags_by_name.merge!( existing_tags_by_name(locale).merge!(
Tag.joins(:tag_name) Tag.joins(:tag_name)
.where(tag_names: { name: missing_names }) .where(tag_names: { name: missing_names })
.index_by(&:name)) .index_by(&:name))
end end
def canonical_tag_name_without_sections raw_name def canonical_tag_name_without_sections locale, raw_name
name, = parse_raw_tag_name(raw_name) name, = parse_raw_tag_name(locale, raw_name)
name name
end end
def deerjikist_tag_spec? spec def deerjikist_tag_spec? locale, spec
return true if spec[:category] == :deerjikist return true if spec[:category] == :deerjikist
existing_tags_by_name[spec[:name]]&.deerjikist? existing_tags_by_name(locale)[spec[:name]]&.deerjikist?
end end
def normalise_parent_post_ids def normalise_parent_post_ids
@@ -260,10 +262,13 @@ class PostCreatePlan
end end
end end
def resolved_tag_name name def resolved_tag_name locale, name
tag_name = TagName.includes(:canonical).find_by(name:) tag_name = TagName.find_by!(language_code: locale.language_code, name:)
return name if tag_name.nil? unless tag_name.primary_flg
tag_name = TagName.find_by!(language_code: locale.language_code,
(tag_name.canonical || tag_name).name tag_id: tag_name.tag_id,
primary_flg: true)
end
tag_name.name
end end
end end
+10 -8
ファイルの表示
@@ -21,14 +21,14 @@ class PostCreator
ApplicationRecord.transaction do ApplicationRecord.transaction do
post.save! post.save!
post.thumbnail.attach(thumbnail_attachment) if thumbnail_attachment.present? post.thumbnail.attach(thumbnail_attachment) if thumbnail_attachment.present?
snapshot_tags = planned_snapshot_tags snapshot_tags = planned_snapshot_tags(locale)
post_tags = planned_post_tags post_tags = planned_post_tags(locale)
sections = planned_sections sections = planned_sections(locale)
TagVersioning.record_tag_snapshots!(snapshot_tags, created_by_user: @actor) TagVersioning.record_tag_snapshots!(snapshot_tags, created_by_user: @actor)
post.video_ms = planned_video_ms(locale) post.video_ms = planned_video_ms(locale)
post.save! post.save!
sync_post_tags!(post, post_tags, sections) sync_post_tags!(post, post_tags, sections)
sync_parent_posts!(post, planned_parent_post_ids) sync_parent_posts!(post, planned_parent_post_ids(locale))
PostVersionRecorder.record!(post:, event_type: :create, created_by_user: @actor) PostVersionRecorder.record!(post:, event_type: :create, created_by_user: @actor)
end end
post post
@@ -45,13 +45,15 @@ class PostCreator
thumbnail_base: @attributes[:thumbnail_base].presence) thumbnail_base: @attributes[:thumbnail_base].presence)
end end
def planned_snapshot_tags = planned_create_attributes[:snapshot_tags] def planned_snapshot_tags(locale) = planned_create_attributes(locale)[:snapshot_tags]
def planned_post_tags = planned_create_attributes[:post_tags] def planned_post_tags(locale) = planned_create_attributes(locale)[:post_tags]
def planned_sections = planned_create_attributes[:tag_sections] def planned_sections(locale) = planned_create_attributes(locale)[:tag_sections]
def planned_parent_post_ids = planned_create_attributes[:normalised_parent_post_ids] def planned_parent_post_ids locale
planned_create_attributes(locale)[:normalised_parent_post_ids]
end
def planned_video_ms locale def planned_video_ms locale
planned_create_attributes(locale)[:video_ms] planned_create_attributes(locale)[:video_ms]
+1
ファイルの表示
@@ -11,6 +11,7 @@ RSpec.describe 'discarded tag cleanup migrations' do
end end
context 'with legacy records' do context 'with legacy records' do
# Reproduce the historical schema required by these pre-multilingual migrations.
self.use_transactional_tests = false self.use_transactional_tests = false
before do before do
-9
ファイルの表示
@@ -7,15 +7,6 @@ FactoryBot.define do
trait :alias do trait :alias do
primary_flg { false } primary_flg { false }
after(:create) do |tag_name|
owner = tag_name.canonical&.tag
raise ArgumentError, 'alias requires a canonical tag' unless owner
# The alias validation still rejects tag_id; its contract is tested separately.
tag_name.update_columns(tag_id: owner.id)
tag_name.association(:tag).reset
end
end end
end end
end end
+4 -4
ファイルの表示
@@ -27,10 +27,10 @@ RSpec.describe Locale, type: :model do
expect(first.tag_names.find_by!(language_code: 'fr', primary_flg: true)) expect(first.tag_names.find_by!(language_code: 'fr', primary_flg: true))
.to have_attributes(name: 'name_from_generator', tag_id: first.id, .to have_attributes(name: 'name_from_generator', tag_id: first.id,
language_code: 'fr', script_code: 'Latn', language_code: 'fr', script_code: 'Latn',
primary_flg: true, canonical_id: nil) primary_flg: true)
expect(second.tag_names.find_by!(language_code: 'fr', primary_flg: true)) expect(second.tag_names.find_by!(language_code: 'fr', primary_flg: true))
.to have_attributes(tag_id: second.id, language_code: 'fr', .to have_attributes(tag_id: second.id, language_code: 'fr',
script_code: 'Latn', primary_flg: true, canonical_id: nil) script_code: 'Latn', primary_flg: true)
expect(TagName.where(language_code: 'fr', primary_flg: true, expect(TagName.where(language_code: 'fr', primary_flg: true,
tag_id: [first.id, second.id]).count).to eq(2) tag_id: [first.id, second.id]).count).to eq(2)
end end
@@ -53,13 +53,13 @@ RSpec.describe Locale, type: :model do
prepare_french_reference! prepare_french_reference!
tag = create(:tag, name: 'alias_only_tag') tag = create(:tag, name: 'alias_only_tag')
alias_name = create(:tag_name, :alias, name: 'alias_fr', alias_name = create(:tag_name, :alias, name: 'alias_fr',
canonical: tag.tag_name, tag:,
language_code: 'fr', script_code: 'Latn') language_code: 'fr', script_code: 'Latn')
create_french_locale! create_french_locale!
expect(alias_name.reload).to have_attributes( expect(alias_name.reload).to have_attributes(
canonical_id: tag.tag_name_id, tag_id: tag.id, tag_id: tag.id,
language_code: 'fr', script_code: 'Latn', primary_flg: false) language_code: 'fr', script_code: 'Latn', primary_flg: false)
generated = TagName.find_by!(tag_id: tag.id, language_code: 'fr', primary_flg: true) generated = TagName.find_by!(tag_id: tag.id, language_code: 'fr', primary_flg: true)
expect(generated).to have_attributes(tag_id: tag.id, language_code: 'fr', expect(generated).to have_attributes(tag_id: tag.id, language_code: 'fr',
+9 -6
ファイルの表示
@@ -79,7 +79,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
end end
end end
context 'when a conflicting canonical tag_name exists' do context 'when a conflicting primary tag name exists' do
let!(:existing) { create(:tag_name, name: 'foobar') } let!(:existing) { create(:tag_name, name: 'foobar') }
let!(:source) do let!(:source) do
create(:tag_name, name: 'tmp').tap do |tn| create(:tag_name, name: 'tmp').tap do |tn|
@@ -105,8 +105,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
it 'moves the tag to the existing tag_name' do it 'moves the tag to the existing tag_name' do
described_class.apply! described_class.apply!
expected_tag_name_id = existing.canonical_id || existing.id expect(source_tag.reload.tag_name_id).to eq(existing.id)
expect(source_tag.reload.tag_name_id).to eq(expected_tag_name_id)
expect(TagName.unscoped.exists?(source_tag_name_id)).to be(false) expect(TagName.unscoped.exists?(source_tag_name_id)).to be(false)
end end
end end
@@ -114,7 +113,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
context 'when the sanitised name is an alias of an existing tag' do context 'when the sanitised name is an alias of an existing tag' do
let!(:existing_tag) { create(:tag) } let!(:existing_tag) { create(:tag) }
let!(:alias_name) do let!(:alias_name) do
create(:tag_name, :alias, name: 'foobar', canonical: existing_tag.tag_name) create(:tag_name, :alias, name: 'foobar', tag: existing_tag)
end end
let!(:source) do let!(:source) do
create(:tag_name, name: 'tmp').tap do |tn| create(:tag_name, name: 'tmp').tap do |tn|
@@ -122,11 +121,15 @@ RSpec.describe TagNameSanitisationRule, type: :model do
end end
end end
it 'deletes only the source and preserves the alias and its canonical tag' do it 'deletes only the source and preserves the alias and its owning tag' do
described_class.apply! described_class.apply!
expect(TagName.unscoped.exists?(source.id)).to be(false) expect(TagName.unscoped.exists?(source.id)).to be(false)
expect(alias_name.reload.canonical).to eq(existing_tag.tag_name) expect(alias_name.reload).to have_attributes(
tag_id: existing_tag.id, language_code: 'ja', primary_flg: false)
expect(TagName.find_by!(tag_id: existing_tag.id,
language_code: 'ja', primary_flg: true))
.to eq(existing_tag.tag_name)
expect(Tag.find(existing_tag.id)).to eq(existing_tag) expect(Tag.find(existing_tag.id)).to eq(existing_tag)
end end
end end
+33 -12
ファイルの表示
@@ -32,37 +32,58 @@ RSpec.describe TagName, type: :model do
expect(tag.reload.tag_name.reload.tag_id).to eq(tag.id) expect(tag.reload.tag_name.reload.tag_id).to eq(tag.id)
expect(tag.tag_name.tag).to eq(tag) expect(tag.tag_name.tag).to eq(tag)
expect(described_class.find_by!(tag_id: tag.id, language_code: 'ja', primary_flg: true))
.to eq(tag.tag_name)
end end
it 'accepts an alias owned by the canonical tag' do it 'persists an alias owned by the same tag as its primary name' do
tag = create(:tag) tag = create(:tag)
alias_name = build(:tag_name, name: 'valid_alias', canonical: tag.tag_name, alias_name = create(:tag_name, :alias, name: 'valid_alias', tag:)
tag:, primary_flg: false)
expect(alias_name).to have_attributes( expect(alias_name.reload).to have_attributes(
canonical_id: tag.tag_name_id, tag_id: tag.id, tag_id: tag.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
expect(alias_name).to be_valid primary = described_class.find_by!(tag_id: alias_name.tag_id,
language_code: alias_name.language_code,
primary_flg: true)
expect(primary).to eq(tag.tag_name)
end end
describe '.canonicalise' do describe '.canonicalise' do
it 'resolves the primary name of the alias language on the same tag' do
tag = create(:tag, name: '日本語正本')
english_primary = create(:tag_name, tag:, name: 'english_primary',
language_code: 'en', script_code: 'Latn')
english_alias = create(:tag_name, :alias, tag:, name: 'english_alias',
language_code: 'en', script_code: 'Latn')
representative_id = tag.tag_name_id
primary = described_class.find_by!(tag_id: english_alias.tag_id,
language_code: english_alias.language_code,
primary_flg: true)
expect(primary).to eq(english_primary)
expect(described_class.canonicalise(locale, [english_alias.name]))
.to eq([english_primary.name])
expect(tag.reload.tag_name_id).to eq(representative_id)
end
it 'resolves only aliases in the requested language' do it 'resolves only aliases in the requested language' do
japanese = create(:tag, name: 'japanese_canonical') japanese = create(:tag, name: 'japanese_canonical')
english_name = create(:tag_name, name: 'english_canonical', english_name = create(:tag_name, name: 'english_canonical',
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english = create(:tag, tag_name: english_name) english = create(:tag, tag_name: english_name)
japanese_alias = create(:tag_name, :alias, name: 'shared_alias', japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: japanese.tag_name) tag: japanese)
english_alias = create(:tag_name, :alias, name: 'temporary_alias', english_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: english_name, tag: english,
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english_alias.update_columns(name: 'shared_alias')
expect(japanese_alias.reload).to have_attributes( expect(japanese_alias.reload).to have_attributes(
canonical_id: japanese.tag_name_id, tag_id: japanese.id, tag_id: japanese.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
expect(english_alias.reload).to have_attributes( expect(english_alias.reload).to have_attributes(
canonical_id: english_name.id, tag_id: english.id, tag_id: english.id,
primary_flg: false, language_code: 'en') primary_flg: false, language_code: 'en')
expect(described_class.canonicalise(locale, ['shared_alias'])) expect(described_class.canonicalise(locale, ['shared_alias']))
.to eq(['english_canonical']) .to eq(['english_canonical'])
+27 -22
ファイルの表示
@@ -37,17 +37,16 @@ RSpec.describe Tag, type: :model do
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english = create(:tag, tag_name: english_name) english = create(:tag, tag_name: english_name)
japanese_alias = create(:tag_name, :alias, name: 'shared_alias', japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: japanese.tag_name) tag: japanese)
english_alias = create(:tag_name, :alias, name: 'temporary_alias', english_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: english_name, tag: english,
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english_alias.update_columns(name: 'shared_alias')
expect(japanese_alias.reload).to have_attributes( expect(japanese_alias.reload).to have_attributes(
canonical_id: japanese.tag_name_id, tag_id: japanese.id, tag_id: japanese.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
expect(english_alias.reload).to have_attributes( expect(english_alias.reload).to have_attributes(
canonical_id: english_name.id, tag_id: english.id, tag_id: english.id,
primary_flg: false, language_code: 'en') primary_flg: false, language_code: 'en')
expect(described_class.normalise_tags!( expect(described_class.normalise_tags!(
english_locale, ['shared_alias'], english_locale, ['shared_alias'],
@@ -270,12 +269,12 @@ RSpec.describe Tag, type: :model do
expect(tag.category).to eq('character') expect(tag.category).to eq('character')
end end
it 'reuses the canonical tag for an alias without changing its category' do it 'reuses the owning tag for an alias without changing its category' do
tag = create(:tag, category: :character) tag = create(:tag, category: :character)
representative_id = tag.tag_name_id representative_id = tag.tag_name_id
alias_name = create(:tag_name, :alias, name: 'lookup_alias', canonical: tag.tag_name) alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
expect(alias_name.reload).to have_attributes( expect(alias_name.reload).to have_attributes(
canonical_id: tag.tag_name_id, tag_id: tag.id, tag_id: tag.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
found = nil found = nil
@@ -289,14 +288,14 @@ RSpec.describe Tag, type: :model do
expect(tag.reload.tag_name_id).to eq(representative_id) expect(tag.reload.tag_name_id).to eq(representative_id)
end end
it 'reuses the canonical tag through another alias' do it 'reuses the owning tag through another alias' do
tag = create(:tag) tag = create(:tag)
canonical = tag.tag_name primary = tag.tag_name
alias_name = create(:tag_name, :alias, name: 'lookup_alias', canonical:) alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
other_alias = create(:tag_name, :alias, name: 'lookup_other_alias', canonical:) other_alias = create(:tag_name, :alias, name: 'lookup_other_alias', tag:)
expect([alias_name.reload, other_alias.reload]).to all(have_attributes( expect([alias_name.reload, other_alias.reload]).to all(have_attributes(
canonical_id: canonical.id, tag_id: tag.id, tag_id: tag.id,
primary_flg: false, language_code: 'ja')) primary_flg: false, language_code: 'ja'))
expect { expect {
@@ -305,7 +304,7 @@ RSpec.describe Tag, type: :model do
expect(found).to eq(tag) expect(found).to eq(tag)
}.to change(Tag, :count).by(0).and change(TagName, :count).by(0) }.to change(Tag, :count).by(0).and change(TagName, :count).by(0)
expect(tag.tag_name).to eq(canonical) expect(tag.reload.tag_name).to eq(primary)
end end
end end
@@ -329,7 +328,8 @@ RSpec.describe Tag, type: :model do
expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false) expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false)
expect(target_link).to be_present expect(target_link).to be_present
expect(Tag.unscoped.exists?(source_tag.id)).to be(false) expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id) expect(source_tag_name.reload).to have_attributes(
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
expect(target_tag.reload.post_count).to eq(1) expect(target_tag.reload.post_count).to eq(1)
end end
end end
@@ -355,7 +355,8 @@ RSpec.describe Tag, type: :model do
expect(target_post_tag.reload.sections).to contain_exactly(target_section) expect(target_post_tag.reload.sections).to contain_exactly(target_section)
expect(Tag.unscoped.exists?(source_tag.id)).to be(false) expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id) expect(source_tag_name.reload).to have_attributes(
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
expect(target_tag.reload.post_count).to eq(1) expect(target_tag.reload.post_count).to eq(1)
end end
end end
@@ -363,7 +364,7 @@ RSpec.describe Tag, type: :model do
it 'keeps source history and records the new target alias after deleting the source' do it 'keeps source history and records the new target alias after deleting the source' do
user = create_member_user! user = create_member_user!
source_name = source_tag.name source_name = source_tag.name
source_alias = create(:tag_name, :alias, name: 'merge_alias', canonical: source_tag_name) source_alias = create(:tag_name, :alias, name: 'merge_alias', tag: source_tag)
TagVersioning.ensure_snapshot!(source_tag, created_by_user: user) TagVersioning.ensure_snapshot!(source_tag, created_by_user: user)
original_version = source_tag.tag_versions.first original_version = source_tag.tag_versions.first
@@ -433,7 +434,8 @@ RSpec.describe Tag, type: :model do
expect(Tag.unscoped.exists?(source_tag.id)).to be(false) expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false) expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false)
expect(target_link).to be_present expect(target_link).to be_present
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id) expect(source_tag_name.reload).to have_attributes(
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
expect(target_tag.reload.post_count).to eq(1) expect(target_tag.reload.post_count).to eq(1)
end end
end end
@@ -464,7 +466,8 @@ RSpec.describe Tag, type: :model do
expect(target_link).to be_present expect(target_link).to be_present
expect(Tag.unscoped.exists?(source_tag.id)).to be(false) expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id) expect(source_tag_name.reload).to have_attributes(
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
expect(target_tag.reload.post_count).to eq(1) expect(target_tag.reload.post_count).to eq(1)
end end
end end
@@ -493,7 +496,8 @@ RSpec.describe Tag, type: :model do
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect(Tag.unscoped.exists?(earlier_source.id)).to be(true) expect(Tag.unscoped.exists?(earlier_source.id)).to be(true)
expect(earlier_name.reload.canonical_id).to be_nil expect(earlier_name.reload).to have_attributes(
tag_id: earlier_source.id, language_code: 'ja', primary_flg: true)
expect(TagVersion.where(tag_id: [earlier_source.id, source_tag.id, target_tag.id])) expect(TagVersion.where(tag_id: [earlier_source.id, source_tag.id, target_tag.id]))
.to be_empty .to be_empty
expect(Tag.unscoped.exists?(source_tag.id)).to be(true) expect(Tag.unscoped.exists?(source_tag.id)).to be(true)
@@ -502,7 +506,8 @@ RSpec.describe Tag, type: :model do
expect(source_post_tag.sections).to contain_exactly(source_section) expect(source_post_tag.sections).to contain_exactly(source_section)
expect(PostTag.find_by(post: post_record, tag: target_tag)).to be_nil expect(PostTag.find_by(post: post_record, tag: target_tag)).to be_nil
expect(source_tag.reload.post_count).to eq(1) expect(source_tag.reload.post_count).to eq(1)
expect(source_tag_name.reload.canonical_id).to be_nil expect(source_tag_name.reload).to have_attributes(
tag_id: source_tag.id, language_code: 'ja', primary_flg: true)
expect(target_tag.reload.post_count).to eq(0) expect(target_tag.reload.post_count).to eq(0)
end end
end end
+9 -5
ファイルの表示
@@ -202,17 +202,19 @@ RSpec.describe 'Locale propagation on write paths', type: :request do
expect(response).to have_http_status(:ok), response.body expect(response).to have_http_status(:ok), response.body
expect(TagName.find_by!(language_code: 'en', name: 'english_alias')) expect(TagName.find_by!(language_code: 'en', name: 'english_alias'))
.to have_attributes( .to have_attributes(
script_code: 'Latn', canonical_id: english_name.id, script_code: 'Latn',
tag_id: tag.id, primary_flg: false, language_code: 'en') tag_id: tag.id, primary_flg: false, language_code: 'en')
expect(tag.reload.tag_name_id).to eq(english_name.id) expect(tag.reload.tag_name_id).to eq(english_name.id)
primary = TagName.find_by!(tag_id: tag.id, language_code: 'en', primary_flg: true)
expect(primary).to have_attributes(name: 'english_original', script_code: 'Latn')
end end
it 'does not take an alias from another language' do it 'does not take an alias from another language' do
japanese = create(:tag, name: 'japanese_owner') japanese = create(:tag, name: 'japanese_owner')
japanese_alias = create(:tag_name, :alias, name: 'shared_alias', japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: japanese.tag_name) tag: japanese)
expect(japanese_alias.reload).to have_attributes( expect(japanese_alias.reload).to have_attributes(
canonical_id: japanese.tag_name_id, tag_id: japanese.id, tag_id: japanese.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
put "/tags/#{ tag.id }", params: { put "/tags/#{ tag.id }", params: {
@@ -221,12 +223,14 @@ RSpec.describe 'Locale propagation on write paths', type: :request do
expect(response).to have_http_status(:ok), response.body expect(response).to have_http_status(:ok), response.body
expect(japanese_alias.reload).to have_attributes( expect(japanese_alias.reload).to have_attributes(
canonical_id: japanese.tag_name_id, tag_id: japanese.id, tag_id: japanese.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
expect(TagName.find_by!(language_code: 'en', name: 'shared_alias')) expect(TagName.find_by!(language_code: 'en', name: 'shared_alias'))
.to have_attributes( .to have_attributes(
canonical_id: english_name.id, tag_id: tag.id, tag_id: tag.id, script_code: 'Latn',
primary_flg: false, language_code: 'en') primary_flg: false, language_code: 'en')
expect(TagName.where(name: 'shared_alias').pluck(:language_code, :tag_id))
.to contain_exactly(['ja', japanese.id], ['en', tag.id])
end end
end end
end end
+4 -4
ファイルの表示
@@ -129,7 +129,7 @@ RSpec.describe 'Posts API', type: :request do
let!(:tag) { create(:tag, tag_name:, category: :general) } let!(:tag) { create(:tag, tag_name:, category: :general) }
let!(:tag_name2) { create(:tag_name, name: 'unko') } let!(:tag_name2) { create(:tag_name, name: 'unko') }
let!(:tag2) { create(:tag, tag_name: tag_name2, category: :deerjikist) } let!(:tag2) { create(:tag, tag_name: tag_name2, category: :deerjikist) }
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', canonical: tag_name) } let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
let!(:hit_post) do let!(:hit_post) do
Post.create!(uploaded_user: user, title: 'hello spec world', Post.create!(uploaded_user: user, title: 'hello spec world',
@@ -351,7 +351,7 @@ RSpec.describe 'Posts API', type: :request do
let!(:baz_tag) { create(:tag, tag_name: baz_tag_name, category: :general) } let!(:baz_tag) { create(:tag, tag_name: baz_tag_name, category: :general) }
let!(:foo_alias_tag_name) do let!(:foo_alias_tag_name) do
create(:tag_name, :alias, name: 'not_spec_foo_alias', canonical: foo_tag_name) create(:tag_name, :alias, name: 'not_spec_foo_alias', tag: foo_tag)
end end
let!(:foo_only_post) do let!(:foo_only_post) do
@@ -854,7 +854,7 @@ RSpec.describe 'Posts API', type: :request do
15.times.map do |i| 15.times.map do |i|
tag_name = create(:tag_name, name: "show_query_tag_#{ i }") tag_name = create(:tag_name, name: "show_query_tag_#{ i }")
tag = create(:tag, tag_name:, category: :general) tag = create(:tag, tag_name:, category: :general)
create(:tag_name, :alias, name: "show_query_alias_#{ i }", canonical: tag_name) create(:tag_name, :alias, name: "show_query_alias_#{ i }", tag:)
PostTag.create!(post: post_record, tag:) PostTag.create!(post: post_record, tag:)
tag tag
end end
@@ -999,7 +999,7 @@ RSpec.describe 'Posts API', type: :request do
describe 'POST /posts' do describe 'POST /posts' do
let(:member) { create(:user, :member) } let(:member) { create(:user, :member) }
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', canonical: tag_name) } let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
it '401 when not logged in' do it '401 when not logged in' do
sign_out sign_out
+50 -28
ファイルの表示
@@ -4,7 +4,7 @@ require 'rails_helper'
RSpec.describe 'Tags API', type: :request do RSpec.describe 'Tags API', type: :request do
let!(:tn) { create(:tag_name, name: 'spec_tag') } let!(:tn) { create(:tag_name, name: 'spec_tag') }
let!(:tag) { create(:tag, tag_name: tn, category: :general) } let!(:tag) { create(:tag, tag_name: tn, category: :general) }
let!(:alias_tn) { create(:tag_name, :alias, name: 'unko', canonical: tn) } let!(:alias_tn) { create(:tag_name, :alias, name: 'unko', tag:) }
let!(:post) { Post.create!(url: 'https://example.com/unkounkounko') } let!(:post) { Post.create!(url: 'https://example.com/unkounkounko') }
let!(:post_tag) { PostTag.create!(post:, tag:) } let!(:post_tag) { PostTag.create!(post:, tag:) }
let!(:tn2) { create(:tag_name, name: 'unknown') } let!(:tn2) { create(:tag_name, name: 'unknown') }
@@ -466,7 +466,7 @@ RSpec.describe 'Tags API', type: :request do
it 'excludes external tags but preserves internal alias matches when nico is false' do it 'excludes external tags but preserves internal alias matches when nico is false' do
internal = create(:tag, category: :general, name: 'switch_internal', post_count: 1) internal = create(:tag, category: :general, name: 'switch_internal', post_count: 1)
alias_target = create(:tag, category: :general, name: 'alias_target', post_count: 1) alias_target = create(:tag, category: :general, name: 'alias_target', post_count: 1)
create(:tag_name, :alias, name: 'switch_alias', canonical: alias_target.tag_name) create(:tag_name, :alias, name: 'switch_alias', tag: alias_target)
create(:external_tag, name: 'switch_external', post_count: 1) create(:external_tag, name: 'switch_external', post_count: 1)
get '/tags/autocomplete', params: { q: 'switch', nico: '0' } get '/tags/autocomplete', params: { q: 'switch', nico: '0' }
@@ -479,13 +479,13 @@ RSpec.describe 'Tags API', type: :request do
end end
['%', '_'].each do |wildcard| ['%', '_'].each do |wildcard|
it "treats #{ wildcard } literally for canonical, alias, and external names" do it "treats #{ wildcard } literally for primary, alias, and external names" do
literal = "literal#{ wildcard }match" literal = "literal#{ wildcard }match"
create(:tag, category: :general, name: literal, post_count: 1) create(:tag, category: :general, name: literal, post_count: 1)
alias_target = create( alias_target = create(
:tag, category: :general, name: 'literal_alias_target', post_count: 1) :tag, category: :general, name: 'literal_alias_target', post_count: 1)
create(:tag_name, :alias, name: "#{ literal }_alias", create(:tag_name, :alias, name: "#{ literal }_alias",
canonical: alias_target.tag_name) tag: alias_target)
create(:external_tag, name: literal, post_count: 1) create(:external_tag, name: literal, post_count: 1)
create(:tag, category: :general, name: 'literalXmatch', post_count: 2) create(:tag, category: :general, name: 'literalXmatch', post_count: 2)
create(:external_tag, name: 'literalXmatch', post_count: 2) create(:external_tag, name: 'literalXmatch', post_count: 2)
@@ -509,7 +509,7 @@ RSpec.describe 'Tags API', type: :request do
expect(t['matched_alias']).to be(nil) expect(t['matched_alias']).to be(nil)
end end
it 'returns matching canonical tags by q with aliases' do it 'returns matching primary tag names by q with aliases' do
get '/tags/autocomplete', params: { q: 'unk' } get '/tags/autocomplete', params: { q: 'unk' }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -772,7 +772,8 @@ RSpec.describe 'Tags API', type: :request do
} }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.reload.tag_name) expect(TagName.find_by!(language_code: 'ja', name: 'spec_tag'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
patch "/tags/#{ tag.id }", params: { name: 'spec_tag' } patch "/tags/#{ tag.id }", params: { name: 'spec_tag' }
@@ -781,8 +782,10 @@ RSpec.describe 'Tags API', type: :request do
tag.reload tag.reload
expect(tag.name).to eq('spec_tag') expect(tag.name).to eq('spec_tag')
expect(tag.tag_name.canonical_id).to be_nil expect(tag.tag_name).to have_attributes(
expect(TagName.find_by!(name: 'patch_roundtrip_target').canonical).to eq(tag.tag_name) tag_id: tag.id, language_code: 'ja', primary_flg: true)
expect(TagName.find_by!(language_code: 'ja', name: 'patch_roundtrip_target'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
end end
it '別 tag の正規名には変更できない' do it '別 tag の正規名には変更できない' do
@@ -801,7 +804,8 @@ RSpec.describe 'Tags API', type: :request do
) )
expect(tag.reload.name).to eq('spec_tag') expect(tag.reload.name).to eq('spec_tag')
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('unko') expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
.to contain_exactly('unko')
expect(wiki_page.reload.tag_name).to eq(tag.tag_name) expect(wiki_page.reload.tag_name).to eq(tag.tag_name)
end end
@@ -1170,14 +1174,17 @@ RSpec.describe 'Tags API', type: :request do
expect(tag.name).to eq('put_renamed_tag') expect(tag.name).to eq('put_renamed_tag')
expect(tag.category).to eq('meme') expect(tag.category).to eq('meme')
expect(TagName.find_by(name: 'put_alias_a').canonical).to eq(tag.tag_name) expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_a'))
expect(TagName.find_by(name: 'put_alias_b').canonical).to eq(tag.tag_name) .to have_attributes(tag_id: tag.id, primary_flg: false)
expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_b'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
old_name_alias = TagName.find_by(name: 'spec_tag') old_name_alias = TagName.find_by(name: 'spec_tag')
expect(old_name_alias).to be_present expect(old_name_alias).to be_present
expect(old_name_alias.canonical).to eq(tag.tag_name) expect(old_name_alias).to have_attributes(
tag_id: tag.id, language_code: 'ja', primary_flg: false)
expect(alias_tn.reload.canonical).to be_nil expect(alias_tn.reload.tag_id).to be_nil
expect(tag.parents.map(&:name)).to contain_exactly( expect(tag.parents.map(&:name)).to contain_exactly(
'put_kept_parent', 'put_kept_parent',
@@ -1212,7 +1219,8 @@ RSpec.describe 'Tags API', type: :request do
tag.reload tag.reload
expect(TagName.find_by(name: 'put_alias_self_test').canonical).to eq(tag.tag_name) expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_self_test'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
expect(json['aliases']).to include('put_alias_self_test') expect(json['aliases']).to include('put_alias_self_test')
expect(json['aliases']).not_to include('spec_tag') expect(json['aliases']).not_to include('spec_tag')
end end
@@ -1283,10 +1291,14 @@ RSpec.describe 'Tags API', type: :request do
tag.reload tag.reload
expect(tag.name).to eq('spec_tag') expect(tag.name).to eq('spec_tag')
expect(TagName.find_by!(name: 'put_roundtrip_b').canonical).to eq(tag.tag_name) expect(TagName.find_by!(language_code: 'ja', name: 'put_roundtrip_b'))
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('put_roundtrip_b', 'unko') .to have_attributes(tag_id: tag.id, primary_flg: false)
expect(tag.tag_name.aliases.map(&:name)).not_to include('spec_tag') expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
expect(alias_tn.reload.canonical).to eq(tag.tag_name) .to contain_exactly('put_roundtrip_b', 'unko')
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
.not_to include('spec_tag')
expect(alias_tn.reload).to have_attributes(
tag_id: tag.id, language_code: 'ja', primary_flg: false)
version = tag.tag_versions.order(:version_no).last version = tag.tag_versions.order(:version_no).last
@@ -1395,7 +1407,7 @@ RSpec.describe 'Tags API', type: :request do
) )
end end
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新 canonical へ移す' do it 'wiki を持つ tag を旧 alias へ戻しても wiki を新しい正本名へ移す' do
wiki_page = wiki_page =
Wiki::Commit.create_content!( Wiki::Commit.create_content!(
tag_name: tag.tag_name, tag_name: tag.tag_name,
@@ -1429,7 +1441,8 @@ RSpec.describe 'Tags API', type: :request do
expect(wiki_page.reload.tag_name).to eq(tag.tag_name) expect(wiki_page.reload.tag_name).to eq(tag.tag_name)
expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').wiki_page).to be_nil expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').wiki_page).to be_nil
expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').canonical).to eq(tag.tag_name) expect(TagName.find_by!(language_code: 'ja', name: 'put_wiki_roundtrip_b'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
versions = wiki_page.wiki_versions.order(:version_no).last(2) versions = wiki_page.wiki_versions.order(:version_no).last(2)
@@ -1443,9 +1456,11 @@ RSpec.describe 'Tags API', type: :request do
category: :general) category: :general)
stolen_alias = create(:tag_name, :alias, stolen_alias = create(:tag_name, :alias,
name: 'put_stolen_alias', name: 'put_stolen_alias',
canonical: old_owner.tag_name) tag: old_owner)
expect(old_owner.tag_name.aliases.map(&:name)).to include('put_stolen_alias') expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
primary_flg: false).pluck(:name))
.to include('put_stolen_alias')
expect { expect {
put "/tags/#{ tag.id }", params: { put "/tags/#{ tag.id }", params: {
@@ -1461,8 +1476,11 @@ RSpec.describe 'Tags API', type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(stolen_alias.reload.canonical).to eq(tag.tag_name) expect(stolen_alias.reload).to have_attributes(
expect(old_owner.reload.tag_name.aliases.map(&:name)).not_to include('put_stolen_alias') tag_id: tag.id, language_code: 'ja', primary_flg: false)
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
primary_flg: false).pluck(:name))
.not_to include('put_stolen_alias')
old_owner_versions = old_owner.tag_versions.order(:version_no) old_owner_versions = old_owner.tag_versions.order(:version_no)
@@ -1479,7 +1497,7 @@ RSpec.describe 'Tags API', type: :request do
category: :general) category: :general)
stolen_alias = create(:tag_name, :alias, stolen_alias = create(:tag_name, :alias,
name: 'put_alias_collision_name', name: 'put_alias_collision_name',
canonical: old_owner.tag_name) tag: old_owner)
wiki_page = wiki_page =
Wiki::Commit.create_content!( Wiki::Commit.create_content!(
tag_name: tag.tag_name, tag_name: tag.tag_name,
@@ -1502,9 +1520,13 @@ RSpec.describe 'Tags API', type: :request do
stolen_alias.reload stolen_alias.reload
expect(tag.name).to eq('put_alias_collision_name') expect(tag.name).to eq('put_alias_collision_name')
expect(stolen_alias.canonical_id).to be_nil expect(stolen_alias).to have_attributes(
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.tag_name) tag_id: tag.id, language_code: 'ja', primary_flg: true)
expect(old_owner.tag_name.aliases.map(&:name)).not_to include('put_alias_collision_name') expect(TagName.find_by!(language_code: 'ja', name: 'spec_tag'))
.to have_attributes(tag_id: tag.id, primary_flg: false)
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
primary_flg: false).pluck(:name))
.not_to include('put_alias_collision_name')
old_owner_versions = old_owner.tag_versions.order(:version_no) old_owner_versions = old_owner.tag_versions.order(:version_no)
+8 -9
ファイルの表示
@@ -40,17 +40,16 @@ RSpec.describe PostCreatePlan do
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english = create(:tag, tag_name: english_name, category: :character) english = create(:tag, tag_name: english_name, category: :character)
japanese_alias = create(:tag_name, :alias, name: 'shared_alias', japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: japanese.tag_name) tag: japanese)
english_alias = create(:tag_name, :alias, name: 'temporary_alias', english_alias = create(:tag_name, :alias, name: 'shared_alias',
canonical: english_name, tag: english,
language_code: 'en', script_code: 'Latn') language_code: 'en', script_code: 'Latn')
english_alias.update_columns(name: 'shared_alias')
expect(japanese_alias.reload).to have_attributes( expect(japanese_alias.reload).to have_attributes(
canonical_id: japanese.tag_name_id, tag_id: japanese.id, tag_id: japanese.id,
primary_flg: false, language_code: 'ja') primary_flg: false, language_code: 'ja')
expect(english_alias.reload).to have_attributes( expect(english_alias.reload).to have_attributes(
canonical_id: english_name.id, tag_id: english.id, tag_id: english.id,
primary_flg: false, language_code: 'en') primary_flg: false, language_code: 'en')
plan = described_class.new(attributes: { tags: 'shared_alias' }).build!(locale) plan = described_class.new(attributes: { tags: 'shared_alias' }).build!(locale)
@@ -79,9 +78,9 @@ RSpec.describe PostCreatePlan do
expect([TagName.count, Tag.count]).to eq(counts) expect([TagName.count, Tag.count]).to eq(counts)
end end
it 'resolves aliases and keeps tag sections separate from canonical names' do it 'resolves aliases and keeps tag sections separate from primary names' do
canonical = create_tag!('虹夏', :character) tag = create_tag!('虹夏', :character)
create(:tag_name, :alias, name: 'にじか', canonical: canonical.tag_name) create(:tag_name, :alias, name: 'にじか', tag:)
create_tag!('動画', :meta) create_tag!('動画', :meta)
plan = described_class.new( plan = described_class.new(