コミットを比較
14
コミット
| 作成者 | SHA1 | 日付 | |
|---|---|---|---|
|
|
a0f1d0c4c3 | ||
|
|
b04f3e698d | ||
|
|
5d2e85a690 | ||
|
|
3b2acb95be | ||
|
|
1de00191ea | ||
|
|
d1bab299ab | ||
|
|
089727c153 | ||
|
|
894f9f9571 | ||
|
|
2490ed91c4 | ||
|
|
2dbd5260ea | ||
|
|
2e25c6e0ba | ||
|
|
a0c2788e00 | ||
|
|
31fc32b377 | ||
|
|
068355720d |
@@ -92,6 +92,4 @@ class ApplicationController < ActionController::API
|
|||||||
|
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_locale! = Locale.find_by(code: params[:locale]) || Locale.nipponese
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ class MaterialsController < 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!
|
|
||||||
tag_name_raw = params[:tag].to_s.strip
|
tag_name_raw = params[:tag].to_s.strip
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
file_sha256 = MaterialFileSha256.from_upload(file)
|
file_sha256 = MaterialFileSha256.from_upload(file)
|
||||||
@@ -90,7 +89,7 @@ class MaterialsController < ApplicationController
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
Material.transaction do
|
Material.transaction do
|
||||||
tag = resolve_material_tag!(locale, tag_name_raw)
|
tag = resolve_material_tag!(tag_name_raw)
|
||||||
material = Material.new(tag:, url:,
|
material = Material.new(tag:, url:,
|
||||||
created_by_user: current_user,
|
created_by_user: current_user,
|
||||||
updated_by_user: current_user)
|
updated_by_user: current_user)
|
||||||
@@ -133,14 +132,12 @@ 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!(locale, tag_name_raw)
|
tag = resolve_material_tag!(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)
|
||||||
@@ -239,8 +236,10 @@ class MaterialsController < ApplicationController
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_material_tag! locale, tag_name_raw
|
def resolve_material_tag! tag_name_raw
|
||||||
Tag.find_or_create_by_tag_name!(locale, tag_name_raw, category: :material)
|
tag_name = TagName.find_or_create_by!(name: tag_name_raw)
|
||||||
|
tag = tag_name.tag
|
||||||
|
tag || Tag.create!(tag_name:, category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
def material_index_needs_tag_name? filters
|
def material_index_needs_tag_name? filters
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ class NicoTagsController < 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!
|
|
||||||
|
|
||||||
id = params[:id].to_i
|
id = params[:id].to_i
|
||||||
|
|
||||||
tag = ExternalTag.find(id)
|
tag = ExternalTag.find(id)
|
||||||
@@ -91,9 +89,8 @@ class NicoTagsController < ApplicationController
|
|||||||
linked_tags = nil
|
linked_tags = nil
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
linked_tags = Tag.normalise_tags!(locale, linked_tag_names,
|
linked_tags = Tag.normalise_tags!(linked_tag_names, with_tagme: false,
|
||||||
with_tagme: false,
|
with_no_deerjikist: false)
|
||||||
with_no_deerjikist: false)
|
|
||||||
|
|
||||||
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
||||||
|
|
||||||
|
|||||||
@@ -213,12 +213,10 @@ class PostsController < 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!
|
|
||||||
|
|
||||||
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(locale)
|
host: request.base_url).run
|
||||||
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])
|
||||||
@@ -244,7 +242,7 @@ class PostsController < ApplicationController
|
|||||||
:post_tag_specs,
|
:post_tag_specs,
|
||||||
:tag_sections,
|
:tag_sections,
|
||||||
:normalised_parent_post_ids).symbolize_keys).merge(
|
:normalised_parent_post_ids).symbolize_keys).merge(
|
||||||
thumbnail: params[:thumbnail])).create!(locale)
|
thumbnail: params[:thumbnail])).create!
|
||||||
|
|
||||||
post.reload
|
post.reload
|
||||||
render json: PostRepr.base(post), status: :created
|
render json: PostRepr.base(post), status: :created
|
||||||
@@ -273,18 +271,13 @@ 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(locale)
|
host: request.base_url).run
|
||||||
|
|
||||||
render json: result
|
render json: result
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
render_bad_request 'posts manifest の JSON が不正です.'
|
render_bad_request 'posts manifest の JSON が不正です.'
|
||||||
@@ -317,7 +310,6 @@ class PostsController < ApplicationController
|
|||||||
base_version_no = parse_base_version_no
|
base_version_no = parse_base_version_no
|
||||||
return render_bad_request('base_version_no は必須です.') if !(force) && !(base_version_no)
|
return render_bad_request('base_version_no は必須です.') if !(force) && !(base_version_no)
|
||||||
|
|
||||||
locale = Locale.find_by(code: params[:locale].presence) || Locale.nipponese
|
|
||||||
title = params[:title].presence
|
title = params[:title].presence
|
||||||
tag_names = params[:tags].to_s.split
|
tag_names = params[:tags].to_s.split
|
||||||
original_created_from = params[:original_created_from]
|
original_created_from = params[:original_created_from]
|
||||||
@@ -339,8 +331,7 @@ class PostsController < ApplicationController
|
|||||||
base_snapshot = post_snapshot_from_version(base_version)
|
base_snapshot = post_snapshot_from_version(base_version)
|
||||||
current_snapshot = post_snapshot_from_record(post)
|
current_snapshot = post_snapshot_from_record(post)
|
||||||
end
|
end
|
||||||
incoming_snapshot = post_incoming_snapshot(locale:,
|
incoming_snapshot = post_incoming_snapshot(title:,
|
||||||
title:,
|
|
||||||
original_created_from:,
|
original_created_from:,
|
||||||
original_created_before:,
|
original_created_before:,
|
||||||
tag_names:,
|
tag_names:,
|
||||||
@@ -369,7 +360,7 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
apply_post_snapshot!(locale, post, snapshot_to_apply)
|
apply_post_snapshot!(post, snapshot_to_apply)
|
||||||
end
|
end
|
||||||
|
|
||||||
return render json: conflict_json, status: :conflict if conflict_json
|
return render json: conflict_json, status: :conflict if conflict_json
|
||||||
@@ -710,14 +701,11 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_incoming_snapshot locale:, title:, original_created_from:, original_created_before:,
|
def post_incoming_snapshot title:, original_created_from:, original_created_before:,
|
||||||
tag_names:, video_ms_param:, duration_param:, parent_post_ids:
|
tag_names:, video_ms_param:, duration_param:, parent_post_ids:
|
||||||
validate_original_created_values!(original_created_from, original_created_before)
|
validate_original_created_values!(original_created_from, original_created_before)
|
||||||
|
Tag.normalise_tags!(tag_names, with_tagme: false, deny_deprecated: true,
|
||||||
Tag.normalise_tags!(locale, tag_names,
|
with_sections: true) => { tags:, sections: }
|
||||||
with_tagme: false,
|
|
||||||
deny_deprecated: true,
|
|
||||||
with_sections: true) => { tags:, sections: }
|
|
||||||
|
|
||||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||||
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
||||||
@@ -852,7 +840,7 @@ class PostsController < ApplicationController
|
|||||||
(added_by_current & removed_by_me).present? || (removed_by_current & added_by_me).present?
|
(added_by_current & removed_by_me).present? || (removed_by_current & added_by_me).present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_post_snapshot! locale, post, snapshot
|
def apply_post_snapshot! post, snapshot
|
||||||
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user)
|
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user)
|
||||||
|
|
||||||
post.update!(title: snapshot[:title],
|
post.update!(title: snapshot[:title],
|
||||||
@@ -860,7 +848,7 @@ class PostsController < ApplicationController
|
|||||||
original_created_from: snapshot[:original_created_from],
|
original_created_from: snapshot[:original_created_from],
|
||||||
original_created_before: snapshot[:original_created_before])
|
original_created_before: snapshot[:original_created_before])
|
||||||
|
|
||||||
Tag.normalise_tags!(locale, snapshot[:tag_names],
|
Tag.normalise_tags!(snapshot[:tag_names],
|
||||||
with_tagme: false,
|
with_tagme: false,
|
||||||
deny_deprecated: true,
|
deny_deprecated: true,
|
||||||
with_sections: true) => { tags:, sections: }
|
with_sections: true) => { tags:, sections: }
|
||||||
|
|||||||
@@ -406,9 +406,7 @@ 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)
|
||||||
|
|
||||||
locale = resolve_locale!
|
return unless validate_tag_rename(tag, name)
|
||||||
|
|
||||||
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
|
||||||
@@ -436,7 +434,7 @@ class TagsController < ApplicationController
|
|||||||
alias_names.delete(name)
|
alias_names.delete(name)
|
||||||
|
|
||||||
update_aliases!(tag, alias_names)
|
update_aliases!(tag, alias_names)
|
||||||
update_parent_tags!(locale, tag, parent_names)
|
update_parent_tags!(tag, parent_names)
|
||||||
|
|
||||||
tag.reload
|
tag.reload
|
||||||
|
|
||||||
@@ -455,8 +453,6 @@ 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)
|
||||||
@@ -464,7 +460,7 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
tag = Tag.find(params[:id])
|
tag = Tag.find(params[:id])
|
||||||
|
|
||||||
return unless validate_tag_rename(locale, tag, name)
|
return unless validate_tag_rename(tag, name)
|
||||||
|
|
||||||
if category.present? && category == 'nico'
|
if category.present? && category == 'nico'
|
||||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||||
@@ -686,7 +682,7 @@ class TagsController < ApplicationController
|
|||||||
created_by_user:)
|
created_by_user:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_tag_rename locale, tag, name
|
def validate_tag_rename 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])
|
||||||
@@ -694,8 +690,9 @@ class TagsController < ApplicationController
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
target_tag_name = TagName.find_by(language_code: locale.language_code, name:)
|
target_tag_name = TagName.find_by(name:)
|
||||||
return true unless target_tag_name&.primary_flg
|
return true if target_tag_name.nil?
|
||||||
|
return true if target_tag_name.canonical_id?
|
||||||
|
|
||||||
render_unprocessable_entity 'その名前は既に使はれてゐます.', field: :name
|
render_unprocessable_entity 'その名前は既に使はれてゐます.', field: :name
|
||||||
false
|
false
|
||||||
@@ -779,10 +776,9 @@ class TagsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_parent_tags! locale, tag, parent_names
|
def update_parent_tags! tag, parent_names
|
||||||
parent_tags = Tag.normalise_tags!(locale, parent_names,
|
parent_tags = Tag.normalise_tags!(parent_names, with_tagme: false,
|
||||||
with_tagme: false,
|
with_no_deerjikist: false)
|
||||||
with_no_deerjikist: false)
|
|
||||||
|
|
||||||
old_parent_tags = tag.parents.to_a
|
old_parent_tags = tag.parents.to_a
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
class Language < ApplicationRecord
|
|
||||||
has_many :languages, class_name: 'Locale', foreign_key: :language_code
|
|
||||||
end
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
class Locale < ApplicationRecord
|
|
||||||
after_create_commit :generate_tag_names!
|
|
||||||
|
|
||||||
belongs_to :language, foreign_key: :language_code, primary_key: :code
|
|
||||||
belongs_to :script, foreign_key: :script_code, primary_key: :code
|
|
||||||
|
|
||||||
def self.nipponese = Locale.find('ja')
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def generate_tag_names!
|
|
||||||
tag_ids = TagName.where(language_code:, primary_flg: true).pluck(:tag_id)
|
|
||||||
Tag.where.not(id: tag_ids).find_each do
|
|
||||||
TagName.create!(tag_id: _1.id,
|
|
||||||
language_code:,
|
|
||||||
name: TagName.generate_name(self, _1, _1.name),
|
|
||||||
script_code:,
|
|
||||||
primary_flg: true,
|
|
||||||
# TODO: 公証実装したら書く.
|
|
||||||
# auto_generated: true,
|
|
||||||
canonical_id: nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
class Script < ApplicationRecord
|
|
||||||
;
|
|
||||||
end
|
|
||||||
+17
-31
@@ -50,7 +50,6 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
has_many :tag_versions
|
has_many :tag_versions
|
||||||
|
|
||||||
has_many :tag_names
|
|
||||||
belongs_to :tag_name
|
belongs_to :tag_name
|
||||||
delegate :wiki_page, to: :tag_name
|
delegate :wiki_page, to: :tag_name
|
||||||
|
|
||||||
@@ -96,19 +95,17 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
def has_deerjikists = deerjikists.loaded? ? deerjikists.any? : deerjikists.exists?
|
def has_deerjikists = deerjikists.loaded? ? deerjikists.any? : deerjikists.exists?
|
||||||
|
|
||||||
def self.tagme = find_or_create_by_tag_name!(Locale.nipponese, 'タグ希望', category: :meta)
|
def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta)
|
||||||
def self.bot = find_or_create_by_tag_name!(Locale.nipponese, 'bot操作', category: :meta)
|
def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta)
|
||||||
def self.no_deerjikist =
|
def self.no_deerjikist = find_or_create_by_tag_name!('ニジラー情報不詳', category: :meta)
|
||||||
find_or_create_by_tag_name!(Locale.nipponese, 'ニジラー情報不詳', category: :meta)
|
def self.video = find_or_create_by_tag_name!('動画', category: :meta)
|
||||||
def self.video = find_or_create_by_tag_name!(Locale.nipponese, '動画', category: :meta)
|
def self.niconico = find_or_create_by_tag_name!('ニコニコ', category: :meta)
|
||||||
def self.niconico = find_or_create_by_tag_name!(Locale.nipponese, 'ニコニコ', category: :meta)
|
def self.youtube = find_or_create_by_tag_name!('YouTube', category: :meta)
|
||||||
def self.youtube = find_or_create_by_tag_name!(Locale.nipponese, 'YouTube', category: :meta)
|
|
||||||
|
|
||||||
def self.normalise_tags! locale, tag_names,
|
def self.normalise_tags! tag_names, with_tagme: true,
|
||||||
with_tagme: true,
|
with_no_deerjikist: true,
|
||||||
with_no_deerjikist: true,
|
deny_deprecated: false,
|
||||||
deny_deprecated: false,
|
with_sections: false
|
||||||
with_sections: false
|
|
||||||
if tag_names.any? { |n| n.downcase.start_with?('nico:') }
|
if tag_names.any? { |n| n.downcase.start_with?('nico:') }
|
||||||
raise NicoTagNormalisationError
|
raise NicoTagNormalisationError
|
||||||
end
|
end
|
||||||
@@ -135,9 +132,9 @@ class Tag < ApplicationRecord
|
|||||||
raise SectionLiteralParseError.new(raw_name, raw_name)
|
raise SectionLiteralParseError.new(raw_name, raw_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
name = TagName.canonicalise(locale, name).first
|
name = TagName.canonicalise(name).first
|
||||||
|
|
||||||
find_or_create_by_tag_name!(locale, name, category: (cat || :general)).tap do |tag|
|
find_or_create_by_tag_name!(name, category: (cat || :general)).tap do |tag|
|
||||||
if deny_deprecated && tag.deprecated?
|
if deny_deprecated && tag.deprecated?
|
||||||
raise DeprecatedTagNormalisationError, [tag.name]
|
raise DeprecatedTagNormalisationError, [tag.name]
|
||||||
end
|
end
|
||||||
@@ -225,22 +222,13 @@ class Tag < ApplicationRecord
|
|||||||
[left_end_ms, right_end_ms].max
|
[left_end_ms, right_end_ms].max
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_or_create_by_tag_name! locale, name, category:
|
def self.find_or_create_by_tag_name! name, category:
|
||||||
language_code = locale.language_code
|
tn = TagName.find_or_create_by!(name: name.to_s.strip)
|
||||||
name = name.to_s.strip
|
tn = tn.canonical if tn.canonical_id?
|
||||||
|
|
||||||
tn = TagName.find_or_create_by!(language_code:, name:) do
|
Tag.find_or_create_by!(tag_name_id: tn.id) do |t|
|
||||||
_1.script_code = locale.script_code
|
t.category = category
|
||||||
_1.primary_flg = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
tag = tn.tag
|
|
||||||
return tag if tag
|
|
||||||
|
|
||||||
tag = Tag.create!(tag_name: tn, category:)
|
|
||||||
tn.update!(tag:)
|
|
||||||
|
|
||||||
tag
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
@@ -276,8 +264,6 @@ 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,
|
||||||
|
|||||||
@@ -1,55 +1,43 @@
|
|||||||
class TagName < ApplicationRecord
|
class TagName < ApplicationRecord
|
||||||
belongs_to :language, foreign_key: :language_code, primary_key: :code
|
has_one :tag
|
||||||
belongs_to :tag, optional: true
|
|
||||||
has_one :wiki_page
|
has_one :wiki_page
|
||||||
|
|
||||||
validates :name, presence: true,
|
belongs_to :canonical, class_name: 'TagName', optional: true
|
||||||
length: { maximum: 255 },
|
has_many :aliases, class_name: 'TagName', foreign_key: :canonical_id
|
||||||
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 :alias_must_not_have_wiki_page
|
validate :canonical_must_not_be_present_with_tag_or_wiki_page
|
||||||
validate :name_must_be_sanitised
|
validate :name_must_be_sanitised
|
||||||
|
|
||||||
def primary? = primary_flg
|
def self.canonicalise names
|
||||||
|
|
||||||
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.where(language_code: locale.language_code, name: names).index_by(&:name)
|
tns = TagName.includes(:canonical).where(name: names).index_by(&:name)
|
||||||
|
|
||||||
names.map { |name|
|
names.map { |name| tns[name]&.canonical&.name || name }.uniq
|
||||||
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
|
|
||||||
|
|
||||||
def self.generate_name locale, tag, name
|
|
||||||
# TODO: 言語ごとの自動命名ロジック完成したら書く.
|
|
||||||
|
|
||||||
"Tag_##{ tag.id }"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
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 !(primary?) && name.to_s.include?(':')
|
if canonical_id? && name.to_s.include?(':')
|
||||||
errors.add :name, 'エーリアス名にプレフィクスを含むことはできません.'
|
errors.add :name, 'エーリアス名にプレフィクスを含むことはできません.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def alias_must_not_have_wiki_page
|
def canonical_must_not_be_present_with_tag_or_wiki_page
|
||||||
if !(primary?) && wiki_page
|
if canonical_id? && (tag || wiki_page)
|
||||||
errors.add :primary_flg, 'Wiki 参照がある名前はエーリアスになれません.'
|
errors.add :canonical, 'タグもしくは Wiki の参照がある名前はエーリアスになれません.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -15,38 +15,31 @@ class TagNameSanitisationRule < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def apply!
|
def apply!
|
||||||
Language.find_each do |language|
|
TagName.find_each do |tn|
|
||||||
TagName.where(language:).find_each do |tn|
|
name = sanitise(tn.name)
|
||||||
name = sanitise(tn.name)
|
next if name == tn.name
|
||||||
next if name == tn.name
|
|
||||||
|
|
||||||
TagName.transaction do
|
TagName.transaction do
|
||||||
existing_tn = TagName.find_by(language:, name:)
|
existing_tn = TagName.find_by(name:)
|
||||||
if existing_tn
|
if existing_tn
|
||||||
unless existing_tn.primary_flg
|
existing_tn = existing_tn.canonical || existing_tn
|
||||||
existing_tn = TagName.find_by!(language:,
|
next if existing_tn.id == tn.id
|
||||||
tag_id: existing_tn.tag_id,
|
|
||||||
primary_flg: true)
|
|
||||||
end
|
|
||||||
next if existing_tn.id == tn.id
|
|
||||||
|
|
||||||
existing_tag = existing_tn.tag
|
existing_tag = Tag.find_by(tag_name_id: existing_tn.id)
|
||||||
source_tag = tn.tag
|
source_tag = Tag.find_by(tag_name_id: tn.id)
|
||||||
|
|
||||||
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!
|
||||||
|
|
||||||
# TagName 側の自動サニタイズを回避
|
next
|
||||||
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
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class PostBulkCreator
|
|||||||
@host = host
|
@host = host
|
||||||
end
|
end
|
||||||
|
|
||||||
def run locale
|
def run
|
||||||
results = Array.new(@posts.length)
|
results = Array.new(@posts.length)
|
||||||
mutex = Mutex.new
|
mutex = Mutex.new
|
||||||
next_index = 0
|
next_index = 0
|
||||||
@@ -27,7 +27,7 @@ class PostBulkCreator
|
|||||||
break if index >= @posts.length
|
break if index >= @posts.length
|
||||||
|
|
||||||
attributes = @posts[index]
|
attributes = @posts[index]
|
||||||
results[index] = create_row(locale, actor, attributes, index)
|
results[index] = create_row(actor, attributes, index)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error(
|
Rails.logger.error(
|
||||||
"post_bulk_creator_worker_failure #{ { error: e.class.name,
|
"post_bulk_creator_worker_failure #{ { error: e.class.name,
|
||||||
@@ -61,7 +61,7 @@ class PostBulkCreator
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_row locale, actor, attributes, index
|
def create_row actor, attributes, index
|
||||||
preflight =
|
preflight =
|
||||||
PostCreatePreflight.new(
|
PostCreatePreflight.new(
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
@@ -76,7 +76,7 @@ class PostBulkCreator
|
|||||||
|
|
||||||
post = PostCreator.new(
|
post = PostCreator.new(
|
||||||
actor: actor,
|
actor: actor,
|
||||||
attributes: normalised_attributes(attributes, preflight, index)).create!(locale)
|
attributes: normalised_attributes(attributes, preflight, index)).create!
|
||||||
result = {
|
result = {
|
||||||
status: 'created',
|
status: 'created',
|
||||||
post: { id: post.id } }
|
post: { id: post.id } }
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ class PostCreatePlan
|
|||||||
@existing_tags_by_name = nil
|
@existing_tags_by_name = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def build! locale
|
def build!
|
||||||
direct_tag_specs, tag_sections = parse_direct_tag_specs(locale)
|
direct_tag_specs, tag_sections = parse_direct_tag_specs
|
||||||
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(locale, snapshot_tag_specs)
|
post_tag_specs = expand_parent_tag_specs(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 locale
|
def parse_direct_tag_specs
|
||||||
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(locale, raw_name)
|
tag_name, category, sections = parse_raw_tag_name(raw_name)
|
||||||
existing_tag = existing_tags_by_name(locale)[tag_name]
|
existing_tag = existing_tags_by_name[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 locale, raw_name
|
def parse_raw_tag_name 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(locale, name), category&.to_sym, sections]
|
[resolved_tag_name(name), category&.to_sym, sections]
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_default_tag_specs locale, direct_tag_specs
|
def build_default_tag_specs 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?(locale, _1) }
|
if direct_tag_specs.none? { deerjikist_tag_spec?(_1) }
|
||||||
default_tag_specs << {
|
default_tag_specs << {
|
||||||
name: NO_DEERJIKIST_TAG_NAME,
|
name: NO_DEERJIKIST_TAG_NAME,
|
||||||
category: :meta }
|
category: :meta }
|
||||||
@@ -131,10 +131,8 @@ class PostCreatePlan
|
|||||||
raise ActiveRecord::RecordInvalid, post
|
raise ActiveRecord::RecordInvalid, post
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_parent_tag_specs locale, snapshot_tag_specs
|
def expand_parent_tag_specs snapshot_tag_specs
|
||||||
existing_snapshot_tags = snapshot_tag_specs.filter_map do
|
existing_snapshot_tags = snapshot_tag_specs.filter_map { existing_tags_by_name[_1[:name]] }
|
||||||
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?)
|
||||||
@@ -158,33 +156,33 @@ class PostCreatePlan
|
|||||||
}.values.sort_by { _1[:name] }
|
}.values.sort_by { _1[:name] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def existing_tags_by_name locale
|
def existing_tags_by_name
|
||||||
@existing_tags_by_name ||= begin
|
@existing_tags_by_name ||= begin
|
||||||
names = tag_names.map { canonical_tag_name_without_sections(locale, _1) }.uniq
|
names = tag_names.map { canonical_tag_name_without_sections(_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! locale, names
|
def preload_existing_tags_by_name! 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(locale).keys
|
missing_names = wanted_names - existing_tags_by_name.keys
|
||||||
return if missing_names.empty?
|
return if missing_names.empty?
|
||||||
|
|
||||||
existing_tags_by_name(locale).merge!(
|
existing_tags_by_name.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 locale, raw_name
|
def canonical_tag_name_without_sections raw_name
|
||||||
name, = parse_raw_tag_name(locale, raw_name)
|
name, = parse_raw_tag_name(raw_name)
|
||||||
name
|
name
|
||||||
end
|
end
|
||||||
|
|
||||||
def deerjikist_tag_spec? locale, spec
|
def deerjikist_tag_spec? spec
|
||||||
return true if spec[:category] == :deerjikist
|
return true if spec[:category] == :deerjikist
|
||||||
|
|
||||||
existing_tags_by_name(locale)[spec[:name]]&.deerjikist?
|
existing_tags_by_name[spec[:name]]&.deerjikist?
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalise_parent_post_ids
|
def normalise_parent_post_ids
|
||||||
@@ -262,13 +260,10 @@ class PostCreatePlan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolved_tag_name locale, name
|
def resolved_tag_name name
|
||||||
tag_name = TagName.find_by!(language_code: locale.language_code, name:)
|
tag_name = TagName.includes(:canonical).find_by(name:)
|
||||||
unless tag_name.primary_flg
|
return name if tag_name.nil?
|
||||||
tag_name = TagName.find_by!(language_code: locale.language_code,
|
|
||||||
tag_id: tag_name.tag_id,
|
(tag_name.canonical || tag_name).name
|
||||||
primary_flg: true)
|
|
||||||
end
|
|
||||||
tag_name.name
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class PostCreator
|
|||||||
@field_warnings = { }
|
@field_warnings = { }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create! locale
|
def create!
|
||||||
thumbnail_attachment = prepare_thumbnail_attachment
|
thumbnail_attachment = prepare_thumbnail_attachment
|
||||||
post = Post.new(title: @attributes[:title].presence,
|
post = Post.new(title: @attributes[:title].presence,
|
||||||
url: @attributes[:url],
|
url: @attributes[:url],
|
||||||
@@ -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(locale)
|
snapshot_tags = planned_snapshot_tags
|
||||||
post_tags = planned_post_tags(locale)
|
post_tags = planned_post_tags
|
||||||
sections = planned_sections(locale)
|
sections = planned_sections
|
||||||
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
|
||||||
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(locale))
|
sync_parent_posts!(post, planned_parent_post_ids)
|
||||||
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,25 +45,23 @@ class PostCreator
|
|||||||
thumbnail_base: @attributes[:thumbnail_base].presence)
|
thumbnail_base: @attributes[:thumbnail_base].presence)
|
||||||
end
|
end
|
||||||
|
|
||||||
def planned_snapshot_tags(locale) = planned_create_attributes(locale)[:snapshot_tags]
|
def planned_snapshot_tags = planned_create_attributes[:snapshot_tags]
|
||||||
|
|
||||||
def planned_post_tags(locale) = planned_create_attributes(locale)[:post_tags]
|
def planned_post_tags = planned_create_attributes[:post_tags]
|
||||||
|
|
||||||
def planned_sections(locale) = planned_create_attributes(locale)[:tag_sections]
|
def planned_sections = planned_create_attributes[:tag_sections]
|
||||||
|
|
||||||
def planned_parent_post_ids locale
|
def planned_parent_post_ids = planned_create_attributes[:normalised_parent_post_ids]
|
||||||
planned_create_attributes(locale)[:normalised_parent_post_ids]
|
|
||||||
|
def planned_video_ms
|
||||||
|
planned_create_attributes[:video_ms]
|
||||||
end
|
end
|
||||||
|
|
||||||
def planned_video_ms locale
|
def planned_create_attributes
|
||||||
planned_create_attributes(locale)[:video_ms]
|
|
||||||
end
|
|
||||||
|
|
||||||
def planned_create_attributes locale
|
|
||||||
@planned_create_attributes ||= begin
|
@planned_create_attributes ||= begin
|
||||||
if @attributes.key?(:snapshot_tag_specs)
|
if @attributes.key?(:snapshot_tag_specs)
|
||||||
snapshot_tags = materialise_tags(locale, @attributes[:snapshot_tag_specs] || [])
|
snapshot_tags = materialise_tags(@attributes[:snapshot_tag_specs] || [])
|
||||||
post_tags = materialise_tags(locale, @attributes[:post_tag_specs] || [])
|
post_tags = materialise_tags(@attributes[:post_tag_specs] || [])
|
||||||
{
|
{
|
||||||
snapshot_tags: snapshot_tags,
|
snapshot_tags: snapshot_tags,
|
||||||
post_tags: post_tags,
|
post_tags: post_tags,
|
||||||
@@ -94,13 +92,13 @@ class PostCreator
|
|||||||
video_ms: plan[:video_ms] }
|
video_ms: plan[:video_ms] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def materialise_tags locale, specs
|
def materialise_tags specs
|
||||||
Array(specs).each_with_object({ }) do |spec, tags|
|
Array(specs).each_with_object({ }) do |spec, tags|
|
||||||
name = spec[:name] || spec['name']
|
name = spec[:name] || spec['name']
|
||||||
category = spec[:category] || spec['category']
|
category = spec[:category] || spec['category']
|
||||||
next if name.blank? || category.blank?
|
next if name.blank? || category.blank?
|
||||||
|
|
||||||
tag = Tag.find_or_create_by_tag_name!(locale, name, category:)
|
tag = Tag.find_or_create_by_tag_name!(name, category:)
|
||||||
tag.update!(category:) if tag.category.to_sym != category.to_sym
|
tag.update!(category:) if tag.category.to_sym != category.to_sym
|
||||||
tags[name] ||= tag
|
tags[name] ||= tag
|
||||||
end.values
|
end.values
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
class CreateLanguages < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
create_table :languages, id: { type: :string, limit: 16 }, primary_key: :code do |t|
|
|
||||||
t.string :name, null: false
|
|
||||||
t.datetime :deprecated_at, index: true
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
languages(code, name, created_at)
|
|
||||||
VALUES
|
|
||||||
('ja', '日本語', #{ connection.quote Time.current })
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
drop_table :languages
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
class CreateScripts < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
create_table :scripts, id: { type: 'CHAR(4)' }, primary_key: :code do |t|
|
|
||||||
t.string :name, null: false
|
|
||||||
t.datetime :deprecated_at, index: true
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
scripts(code, name, created_at)
|
|
||||||
VALUES
|
|
||||||
('Jpan', '漢字および仮名文字', #{ connection.quote Time.current })
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
drop_table :scripts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
class CreateLocales < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
create_table :locales, id: { type: :string, limit: 32 }, primary_key: :code do |t|
|
|
||||||
t.string :language_code, limit: 16, null: false, index: true
|
|
||||||
t.column :script_code, 'CHAR(4)', null: false
|
|
||||||
t.string :name, null: false
|
|
||||||
t.datetime :deprecated_at, index: true
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.foreign_key :languages, column: :language_code, primary_key: :code
|
|
||||||
t.foreign_key :scripts, column: :script_code, primary_key: :code
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
locales(code, language_code, script_code, name, created_at)
|
|
||||||
VALUES
|
|
||||||
('ja', 'ja', 'Jpan', '日本語', #{ connection.quote Time.current })
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
drop_table :locales
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
class AddColumnsToTagNames < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
add_reference :tag_names, :tag, after: :id, foreign_key: true
|
|
||||||
add_column :tag_names, :language_code, :string,
|
|
||||||
limit: 16, null: false, after: :tag_id, default: 'ja'
|
|
||||||
add_column :tag_names, :script_code, 'CHAR(4)',
|
|
||||||
null: false, after: :name, default: 'Jpan'
|
|
||||||
add_column :tag_names, :primary_flg, :boolean,
|
|
||||||
null: false, after: :script_code, default: true
|
|
||||||
|
|
||||||
add_foreign_key :tag_names, :languages, column: :language_code, primary_key: :code
|
|
||||||
add_foreign_key :tag_names, :scripts, column: :script_code, primary_key: :code
|
|
||||||
|
|
||||||
remove_index :tag_names, :name
|
|
||||||
add_index :tag_names, [:language_code, :name], unique: true
|
|
||||||
|
|
||||||
change_column_default :tag_names, :language_code, from: 'ja', to: nil
|
|
||||||
change_column_default :tag_names, :script_code, from: 'Jpan', to: nil
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
UPDATE
|
|
||||||
tag_names tn
|
|
||||||
LEFT JOIN
|
|
||||||
tags AS t
|
|
||||||
ON
|
|
||||||
t.tag_name_id = COALESCE(tn.canonical_id, tn.id)
|
|
||||||
SET
|
|
||||||
tn.tag_id = t.id
|
|
||||||
, tn.primary_flg = CASE
|
|
||||||
WHEN tn.canonical_id IS NULL THEN
|
|
||||||
1
|
|
||||||
ELSE
|
|
||||||
0
|
|
||||||
END
|
|
||||||
SQL
|
|
||||||
|
|
||||||
change_column_default :tag_names, :primary_flg, from: true, to: nil
|
|
||||||
|
|
||||||
add_column :tag_names, :primary_tag_id, :bigint,
|
|
||||||
as: 'CASE WHEN primary_flg THEN tag_id ELSE NULL END'
|
|
||||||
add_index :tag_names, [:primary_tag_id, :language_code], unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
remove_index :tag_names, [:primary_tag_id, :language_code]
|
|
||||||
remove_column :tag_names, :primary_tag_id
|
|
||||||
|
|
||||||
remove_foreign_key :tag_names, column: :language_code
|
|
||||||
remove_foreign_key :tag_names, column: :script_code
|
|
||||||
|
|
||||||
remove_index :tag_names, [:language_code, :name]
|
|
||||||
add_index :tag_names, :name, unique: true
|
|
||||||
|
|
||||||
remove_column :tag_names, :primary_flg
|
|
||||||
remove_column :tag_names, :script_code
|
|
||||||
remove_column :tag_names, :language_code
|
|
||||||
remove_reference :tag_names, :tag, foreign_key: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
生成ファイル
+2
-40
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
||||||
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@@ -138,24 +138,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
|||||||
t.index ["ip_address"], name: "index_ip_addresses_on_ip_address", unique: true
|
t.index ["ip_address"], name: "index_ip_addresses_on_ip_address", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "languages", primary_key: "code", id: { type: :string, limit: 16 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
|
||||||
t.string "name", null: false
|
|
||||||
t.datetime "deprecated_at"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["deprecated_at"], name: "index_languages_on_deprecated_at"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "locales", primary_key: "code", id: { type: :string, limit: 32 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
|
||||||
t.string "language_code", limit: 16, null: false
|
|
||||||
t.string "script_code", limit: 4, null: false
|
|
||||||
t.string "name", null: false
|
|
||||||
t.datetime "deprecated_at"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["deprecated_at"], name: "index_locales_on_deprecated_at"
|
|
||||||
t.index ["language_code"], name: "index_locales_on_language_code"
|
|
||||||
t.index ["script_code"], name: "fk_rails_0b74ce96a8"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "material_export_items", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "material_export_items", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "material_id", null: false
|
t.bigint "material_id", null: false
|
||||||
t.string "profile", default: "legacy_drive", null: false
|
t.string "profile", default: "legacy_drive", null: false
|
||||||
@@ -406,13 +388,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
|||||||
t.check_constraint "`version_no` > 0", name: "chk_posts_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "chk_posts_version_no_positive"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "scripts", primary_key: "code", id: { type: :string, limit: 4 }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
|
||||||
t.string "name", null: false
|
|
||||||
t.datetime "deprecated_at"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["deprecated_at"], name: "index_scripts_on_deprecated_at"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "settings", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "settings", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "user_id", null: false
|
t.bigint "user_id", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
@@ -445,20 +420,12 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "tag_names", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "tag_names", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "tag_id"
|
|
||||||
t.string "language_code", limit: 16, null: false
|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "script_code", limit: 4, null: false
|
|
||||||
t.boolean "primary_flg", null: false
|
|
||||||
t.bigint "canonical_id"
|
t.bigint "canonical_id"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.virtual "primary_tag_id", type: :bigint, as: "(case when `primary_flg` then `tag_id` else NULL end)"
|
|
||||||
t.index ["canonical_id"], name: "index_tag_names_on_canonical_id"
|
t.index ["canonical_id"], name: "index_tag_names_on_canonical_id"
|
||||||
t.index ["language_code", "name"], name: "index_tag_names_on_language_code_and_name", unique: true
|
t.index ["name"], name: "index_tag_names_on_name", unique: true
|
||||||
t.index ["primary_tag_id", "language_code"], name: "index_tag_names_on_primary_tag_id_and_language_code", unique: true
|
|
||||||
t.index ["script_code"], name: "fk_rails_dd783b3d1c"
|
|
||||||
t.index ["tag_id"], name: "index_tag_names_on_tag_id"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "tag_similarities", primary_key: ["tag_id", "target_tag_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "tag_similarities", primary_key: ["tag_id", "target_tag_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
@@ -734,8 +701,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
|||||||
add_foreign_key "gekanator_question_suggestions", "users"
|
add_foreign_key "gekanator_question_suggestions", "users"
|
||||||
add_foreign_key "gekanator_questions", "gekanator_question_suggestions"
|
add_foreign_key "gekanator_questions", "gekanator_question_suggestions"
|
||||||
add_foreign_key "gekanator_questions", "users", column: "created_by_id"
|
add_foreign_key "gekanator_questions", "users", column: "created_by_id"
|
||||||
add_foreign_key "locales", "languages", column: "language_code", primary_key: "code"
|
|
||||||
add_foreign_key "locales", "scripts", column: "script_code", primary_key: "code"
|
|
||||||
add_foreign_key "material_export_items", "materials"
|
add_foreign_key "material_export_items", "materials"
|
||||||
add_foreign_key "material_export_items", "users", column: "created_by_user_id"
|
add_foreign_key "material_export_items", "users", column: "created_by_user_id"
|
||||||
add_foreign_key "material_import_blocks", "users", column: "created_by_user_id"
|
add_foreign_key "material_import_blocks", "users", column: "created_by_user_id"
|
||||||
@@ -769,10 +734,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_030000) do
|
|||||||
add_foreign_key "settings", "users"
|
add_foreign_key "settings", "users"
|
||||||
add_foreign_key "tag_implications", "tags"
|
add_foreign_key "tag_implications", "tags"
|
||||||
add_foreign_key "tag_implications", "tags", column: "parent_tag_id"
|
add_foreign_key "tag_implications", "tags", column: "parent_tag_id"
|
||||||
add_foreign_key "tag_names", "languages", column: "language_code", primary_key: "code"
|
|
||||||
add_foreign_key "tag_names", "scripts", column: "script_code", primary_key: "code"
|
|
||||||
add_foreign_key "tag_names", "tag_names", column: "canonical_id"
|
add_foreign_key "tag_names", "tag_names", column: "canonical_id"
|
||||||
add_foreign_key "tag_names", "tags"
|
|
||||||
add_foreign_key "tag_similarities", "tags"
|
add_foreign_key "tag_similarities", "tags"
|
||||||
add_foreign_key "tag_similarities", "tags", column: "target_tag_id"
|
add_foreign_key "tag_similarities", "tags", column: "target_tag_id"
|
||||||
add_foreign_key "tag_versions", "users", column: "created_by_user_id"
|
add_foreign_key "tag_versions", "users", column: "created_by_user_id"
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :tag_name do
|
factory :tag_name do
|
||||||
language_code { 'ja' }
|
name { "tag-#{SecureRandom.hex(4)}" }
|
||||||
script_code { 'Jpan' }
|
|
||||||
primary_flg { true }
|
|
||||||
sequence(:name) { |number| "tag-#{ SecureRandom.hex(4) }-#{ number }" }
|
|
||||||
|
|
||||||
trait :alias do
|
|
||||||
primary_flg { false }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ FactoryBot.define do
|
|||||||
|
|
||||||
category { :general }
|
category { :general }
|
||||||
post_count { 0 }
|
post_count { 0 }
|
||||||
|
|
||||||
association :tag_name
|
association :tag_name
|
||||||
|
|
||||||
after(:build) do |tag, evaluator|
|
after(:build) do |tag, evaluator|
|
||||||
tag.name = evaluator.name if evaluator.name.present?
|
tag.name = evaluator.name if evaluator.name.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
after(:create) do |tag|
|
|
||||||
tag.tag_name.update!(tag:) if tag.tag_name.tag_id != tag.id
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Locale, type: :model do
|
|
||||||
def prepare_french_reference!
|
|
||||||
Language.find_or_create_by!(code: 'fr') { _1.name = 'French' }
|
|
||||||
Script.find_or_create_by!(code: 'Latn') { _1.name = 'Latin' }
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_french_locale!
|
|
||||||
prepare_french_reference!
|
|
||||||
described_class.create!(code: 'fr', language_code: 'fr',
|
|
||||||
script_code: 'Latn', name: 'French')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'generates a primary name for every existing tag using the name generator' do
|
|
||||||
first = create(:tag, name: 'first_existing_tag')
|
|
||||||
second = create(:tag, name: 'second_existing_tag')
|
|
||||||
allow(TagName).to receive(:generate_name).and_call_original
|
|
||||||
allow(TagName).to receive(:generate_name)
|
|
||||||
.with(kind_of(described_class), first, first.name)
|
|
||||||
.and_return('name_from_generator')
|
|
||||||
|
|
||||||
locale = create_french_locale!
|
|
||||||
|
|
||||||
expect(TagName).to have_received(:generate_name).with(locale, first, first.name)
|
|
||||||
expect(TagName).to have_received(:generate_name).with(locale, second, second.name)
|
|
||||||
expect(first.tag_names.find_by!(language_code: 'fr', primary_flg: true))
|
|
||||||
.to have_attributes(name: 'name_from_generator', tag_id: first.id,
|
|
||||||
language_code: 'fr', script_code: 'Latn',
|
|
||||||
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',
|
|
||||||
script_code: 'Latn', primary_flg: true)
|
|
||||||
expect(TagName.where(language_code: 'fr', primary_flg: true,
|
|
||||||
tag_id: [first.id, second.id]).count).to eq(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not duplicate an existing primary name in the new language' do
|
|
||||||
prepare_french_reference!
|
|
||||||
tag = create(:tag, name: 'already_named')
|
|
||||||
existing = create(:tag_name, name: 'nom_existant', tag:,
|
|
||||||
language_code: 'fr', script_code: 'Latn')
|
|
||||||
|
|
||||||
create_french_locale!
|
|
||||||
|
|
||||||
expect(TagName.where(tag_id: tag.id, language_code: 'fr', primary_flg: true))
|
|
||||||
.to contain_exactly(existing)
|
|
||||||
expect(existing.reload).to have_attributes(
|
|
||||||
tag_id: tag.id, language_code: 'fr', script_code: 'Latn', primary_flg: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a primary name when the language has only an alias' do
|
|
||||||
prepare_french_reference!
|
|
||||||
tag = create(:tag, name: 'alias_only_tag')
|
|
||||||
alias_name = create(:tag_name, :alias, name: 'alias_fr',
|
|
||||||
tag:,
|
|
||||||
language_code: 'fr', script_code: 'Latn')
|
|
||||||
|
|
||||||
create_french_locale!
|
|
||||||
|
|
||||||
expect(alias_name.reload).to have_attributes(
|
|
||||||
tag_id: tag.id,
|
|
||||||
language_code: 'fr', script_code: 'Latn', primary_flg: false)
|
|
||||||
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',
|
|
||||||
script_code: 'Latn', primary_flg: true)
|
|
||||||
expect(TagName.where(tag_id: tag.id, language_code: 'fr', primary_flg: true).count).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe MaterialExportItem, type: :model do
|
RSpec.describe MaterialExportItem, type: :model do
|
||||||
let(:user) { create(:user, :member) }
|
let(:user) { create(:user, :member) }
|
||||||
let(:tag) { create(:tag, name: 'export_item', category: :material) }
|
let(:tag) { Tag.create!(tag_name: TagName.create!(name: 'export_item'), category: :material) }
|
||||||
let(:material) do
|
let(:material) do
|
||||||
Material.create!(tag:, url: 'https://example.com/material',
|
Material.create!(tag:, url: 'https://example.com/material',
|
||||||
created_by_user: user, updated_by_user: user)
|
created_by_user: user, updated_by_user: user)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe PostVersion, type: :model do
|
RSpec.describe PostVersion, type: :model do
|
||||||
let!(:tag_name) { create(:tag_name, name: 'post_version_spec_tag') }
|
let!(:tag_name) { TagName.create!(name: 'post_version_spec_tag') }
|
||||||
let!(:tag) { create(:tag, tag_name: tag_name, category: :general) }
|
let!(:tag) { Tag.create!(tag_name: tag_name, category: :general) }
|
||||||
|
|
||||||
let!(:post_record) do
|
let!(:post_record) do
|
||||||
Post.create!(title: 'spec post', url: 'https://example.com/post-version-spec').tap do |post|
|
Post.create!(title: 'spec post', url: 'https://example.com/post-version-spec').tap do |post|
|
||||||
|
|||||||
@@ -34,41 +34,9 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
|||||||
described_class.create!(priority: 10, source_pattern: '_', replacement: '')
|
described_class.create!(priority: 10, source_pattern: '_', replacement: '')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when only another language has the sanitised name' do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
it 'keeps both names when sanitisation produces a cross-language match' do
|
|
||||||
japanese = create(:tag_name, name: 'foobar')
|
|
||||||
english = create(:tag_name, name: 'temporary',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english.update_columns(name: 'foo_bar')
|
|
||||||
|
|
||||||
expect { described_class.apply! }.not_to change(TagName, :count)
|
|
||||||
|
|
||||||
expect(english.reload).to have_attributes(name: 'foobar', language_code: 'en')
|
|
||||||
expect(japanese.reload).to have_attributes(name: 'foobar', language_code: 'ja')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renames without merging tags or deleting either language identity' do
|
|
||||||
japanese = create(:tag, name: 'foobar')
|
|
||||||
english_name = create(:tag_name, name: 'temporary',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english = create(:tag, tag_name: english_name)
|
|
||||||
english_name.update_columns(name: 'foo_bar')
|
|
||||||
|
|
||||||
described_class.apply!
|
|
||||||
|
|
||||||
expect(english_name.reload).to have_attributes(
|
|
||||||
name: 'foobar', language_code: 'en', tag_id: english.id)
|
|
||||||
expect(japanese.reload.tag_name).to have_attributes(
|
|
||||||
name: 'foobar', language_code: 'ja', tag_id: japanese.id)
|
|
||||||
expect(english.reload.tag_name_id).to eq(english_name.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when no conflicting tag_name exists' do
|
context 'when no conflicting tag_name exists' do
|
||||||
let!(:tag_name) do
|
let!(:tag_name) do
|
||||||
create(:tag_name, name: 'tmp').tap do |tn|
|
TagName.create!(name: 'tmp').tap do |tn|
|
||||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -79,10 +47,10 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a conflicting primary tag name exists' do
|
context 'when a conflicting canonical tag_name exists' do
|
||||||
let!(:existing) { create(:tag_name, name: 'foobar') }
|
let!(:existing) { TagName.create!(name: 'foobar') }
|
||||||
let!(:source) do
|
let!(:source) do
|
||||||
create(:tag_name, name: 'tmp').tap do |tn|
|
TagName.create!(name: 'tmp').tap do |tn|
|
||||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -95,7 +63,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when the source tag_name has a tag and the existing one has no tag' do
|
context 'when the source tag_name has a tag and the existing one has no tag' do
|
||||||
let!(:existing) { create(:tag_name, name: 'foobar') }
|
let!(:existing) { TagName.create!(name: 'foobar') }
|
||||||
let!(:source_tag) { create(:tag, name: 'tmp', category: :general) }
|
let!(:source_tag) { create(:tag, name: 'tmp', category: :general) }
|
||||||
let!(:source_tag_name_id) { source_tag.tag_name_id }
|
let!(:source_tag_name_id) { source_tag.tag_name_id }
|
||||||
|
|
||||||
@@ -105,7 +73,8 @@ 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!
|
||||||
expect(source_tag.reload.tag_name_id).to eq(existing.id)
|
expected_tag_name_id = existing.canonical_id || 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
|
||||||
@@ -113,33 +82,29 @@ 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', tag: existing_tag)
|
TagName.create!(name: 'foobar', canonical: existing_tag.tag_name)
|
||||||
end
|
end
|
||||||
let!(:source) do
|
let!(:source) do
|
||||||
create(:tag_name, name: 'tmp').tap do |tn|
|
TagName.create!(name: 'tmp').tap do |tn|
|
||||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes only the source and preserves the alias and its owning tag' do
|
it 'deletes only the source and preserves the alias and its canonical 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).to have_attributes(
|
expect(alias_name.reload.canonical).to eq(existing_tag.tag_name)
|
||||||
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
|
||||||
|
|
||||||
context 'when both source and existing tag_names have tags' do
|
context 'when both source and existing tag_names have tags' do
|
||||||
let!(:existing_tn) { create(:tag_name, name: 'foobar') }
|
let!(:existing_tn) { TagName.create!(name: 'foobar') }
|
||||||
let!(:existing_tag) { create(:tag, tag_name: existing_tn, category: :general) }
|
let!(:existing_tag) { Tag.create!(tag_name: existing_tn, category: :general) }
|
||||||
|
|
||||||
let!(:source_tn) { create(:tag_name, name: 'tmp') }
|
let!(:source_tn) { TagName.create!(name: 'tmp') }
|
||||||
let!(:source_tag) { create(:tag, tag_name: source_tn, category: :general) }
|
let!(:source_tag) { Tag.create!(tag_name: source_tn, category: :general) }
|
||||||
let!(:source_tag_name_id) { source_tn.id }
|
let!(:source_tag_name_id) { source_tn.id }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe TagName, type: :model do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
it 'allows the same name in different languages' do
|
|
||||||
create(:tag_name, name: 'shared_name')
|
|
||||||
english = build(:tag_name, name: 'shared_name',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
|
|
||||||
expect(english).to be_valid
|
|
||||||
expect { english.save! }.to change(described_class, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects a duplicate name within the same language' do
|
|
||||||
create(:tag_name, name: 'shared_name')
|
|
||||||
duplicate = build(:tag_name, name: 'shared_name')
|
|
||||||
|
|
||||||
expect(duplicate).to be_invalid
|
|
||||||
expect(duplicate.errors.of_kind?(:name, :taken)).to be(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates Japanese factory names without requiring a tag' do
|
|
||||||
name = create(:tag_name)
|
|
||||||
|
|
||||||
expect(name.reload).to have_attributes(
|
|
||||||
language_code: 'ja', script_code: 'Jpan', primary_flg: true, tag_id: nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'persists both sides of the representative tag factory association' do
|
|
||||||
tag = create(:tag)
|
|
||||||
|
|
||||||
expect(tag.reload.tag_name.reload.tag_id).to eq(tag.id)
|
|
||||||
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
|
|
||||||
|
|
||||||
it 'persists an alias owned by the same tag as its primary name' do
|
|
||||||
tag = create(:tag)
|
|
||||||
alias_name = create(:tag_name, :alias, name: 'valid_alias', tag:)
|
|
||||||
|
|
||||||
expect(alias_name.reload).to have_attributes(
|
|
||||||
tag_id: tag.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
japanese = create(:tag, name: 'japanese_canonical')
|
|
||||||
english_name = create(:tag_name, name: 'english_canonical',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english = create(:tag, tag_name: english_name)
|
|
||||||
japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: japanese)
|
|
||||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: english,
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
|
|
||||||
expect(japanese_alias.reload).to have_attributes(
|
|
||||||
tag_id: japanese.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
expect(english_alias.reload).to have_attributes(
|
|
||||||
tag_id: english.id,
|
|
||||||
primary_flg: false, language_code: 'en')
|
|
||||||
expect(described_class.canonicalise(locale, ['shared_alias']))
|
|
||||||
.to eq(['english_canonical'])
|
|
||||||
expect(described_class.canonicalise(Locale.nipponese, ['shared_alias']))
|
|
||||||
.to eq(['japanese_canonical'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
+65
-156
@@ -9,7 +9,7 @@ RSpec.describe Tag, type: :model do
|
|||||||
expect(tag).to be_invalid
|
expect(tag).to be_invalid
|
||||||
expect(tag.errors[:name]).to be_present
|
expect(tag.errors[:name]).to be_present
|
||||||
expect {
|
expect {
|
||||||
described_class.normalise_tags!(Locale.nipponese, [name])
|
described_class.normalise_tags!([name])
|
||||||
}.to raise_error(Tag::NicoTagNormalisationError)
|
}.to raise_error(Tag::NicoTagNormalisationError)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -25,48 +25,19 @@ RSpec.describe Tag, type: :model do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '.normalise_tags!' do
|
describe '.normalise_tags!' do
|
||||||
it 'canonicalises aliases in the supplied language only' do
|
|
||||||
Language.find_or_create_by!(code: 'en') { _1.name = 'English' }
|
|
||||||
Script.find_or_create_by!(code: 'Latn') { _1.name = 'Latin' }
|
|
||||||
Locale.insert_all!([
|
|
||||||
{ code: 'en', language_code: 'en', script_code: 'Latn',
|
|
||||||
name: 'English', created_at: Time.current }]) unless Locale.exists?(code: 'en')
|
|
||||||
english_locale = Locale.find('en')
|
|
||||||
japanese = create(:tag, name: 'japanese_canonical')
|
|
||||||
english_name = create(:tag_name, name: 'english_canonical',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english = create(:tag, tag_name: english_name)
|
|
||||||
japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: japanese)
|
|
||||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: english,
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
|
|
||||||
expect(japanese_alias.reload).to have_attributes(
|
|
||||||
tag_id: japanese.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
expect(english_alias.reload).to have_attributes(
|
|
||||||
tag_id: english.id,
|
|
||||||
primary_flg: false, language_code: 'en')
|
|
||||||
expect(described_class.normalise_tags!(
|
|
||||||
english_locale, ['shared_alias'],
|
|
||||||
with_tagme: false, with_no_deerjikist: false)).to eq([english])
|
|
||||||
expect(described_class.normalise_tags!(
|
|
||||||
Locale.nipponese, ['shared_alias'],
|
|
||||||
with_tagme: false, with_no_deerjikist: false)).to eq([japanese])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects deprecated tags when deny_deprecated is enabled' do
|
it 'rejects deprecated tags when deny_deprecated is enabled' do
|
||||||
tag_name = create(:tag_name, name: 'normalise deprecated tag')
|
tag_name = TagName.create!(name: 'normalise deprecated tag')
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
tag_name:,
|
tag_name:,
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: 1.day.from_now)
|
deprecated_at: 1.day.from_now
|
||||||
|
)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
described_class.normalise_tags!(Locale.nipponese,
|
described_class.normalise_tags!(
|
||||||
[deprecated_tag.name],
|
[deprecated_tag.name],
|
||||||
deny_deprecated: true)
|
deny_deprecated: true
|
||||||
|
)
|
||||||
}.to raise_error(Tag::DeprecatedTagNormalisationError) { |error|
|
}.to raise_error(Tag::DeprecatedTagNormalisationError) { |error|
|
||||||
expect(error.tag_names).to eq([deprecated_tag.name])
|
expect(error.tag_names).to eq([deprecated_tag.name])
|
||||||
}
|
}
|
||||||
@@ -74,79 +45,88 @@ RSpec.describe Tag, type: :model do
|
|||||||
|
|
||||||
it 'rejects invalid section literals instead of treating them as zero' do
|
it 'rejects invalid section literals instead of treating them as zero' do
|
||||||
expect {
|
expect {
|
||||||
described_class.normalise_tags!(Locale.nipponese,
|
described_class.normalise_tags!(
|
||||||
['normalise_invalid_section[1:aa-2:00]'],
|
['normalise_invalid_section[1:aa-2:00]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
}.to raise_error(Tag::SectionLiteralParseError)
|
}.to raise_error(Tag::SectionLiteralParseError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses open-ended section literals' do
|
it 'parses open-ended section literals' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[1:00-]'],
|
['伊地知ニジカ[1:00-]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses omitted begin as zero' do
|
it 'parses omitted begin as zero' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[-1:00]'],
|
['伊地知ニジカ[-1:00]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[0, 60_000]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[0, 60_000]])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'treats fully open section literals as plain tags' do
|
it 'treats fully open section literals as plain tags' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[-]'],
|
['伊地知ニジカ[-]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections)[tag.id]).to be_nil
|
expect(result.fetch(:sections)[tag.id]).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'treats [0:00-] as a plain tag' do
|
it 'treats [0:00-] as a plain tag' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[0:00-]'],
|
['伊地知ニジカ[0:00-]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections)[tag.id]).to be_nil
|
expect(result.fetch(:sections)[tag.id]).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'expands zero-width sections to one millisecond' do
|
it 'expands zero-width sections to one millisecond' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[1:00-1:00]'],
|
['伊地知ニジカ[1:00-1:00]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 60_001]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 60_001]])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'swaps reversed section boundaries' do
|
it 'swaps reversed section boundaries' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[2:00-1:00]'],
|
['伊地知ニジカ[2:00-1:00]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 120_000]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 120_000]])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'merges open-ended sections over later bounded sections' do
|
it 'merges open-ended sections over later bounded sections' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[1:00-][2:00-3:00]'],
|
['伊地知ニジカ[1:00-][2:00-3:00]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'merges adjacent bounded and open-ended sections' do
|
it 'merges adjacent bounded and open-ended sections' do
|
||||||
result = described_class.normalise_tags!(Locale.nipponese,
|
result = described_class.normalise_tags!(
|
||||||
['伊地知ニジカ[1:00-3:00][3:00-]'],
|
['伊地知ニジカ[1:00-3:00][3:00-]'],
|
||||||
with_sections: true)
|
with_sections: true
|
||||||
|
)
|
||||||
|
|
||||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||||
@@ -203,65 +183,11 @@ RSpec.describe Tag, type: :model do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '.find_or_create_by_tag_name!' do
|
describe '.find_or_create_by_tag_name!' do
|
||||||
context 'with an explicit locale' do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
it 'creates the representative name with locale attributes and ownership' do
|
|
||||||
tag = described_class.find_or_create_by_tag_name!(
|
|
||||||
locale, 'english_name', category: :character)
|
|
||||||
|
|
||||||
expect(tag.tag_name.reload).to have_attributes(
|
|
||||||
language_code: 'en', script_code: 'Latn',
|
|
||||||
primary_flg: true, tag_id: tag.id)
|
|
||||||
expect(tag.category).to eq('character')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates separate tag identities for the same name in different languages' do
|
|
||||||
japanese = described_class.find_or_create_by_tag_name!(
|
|
||||||
Locale.nipponese, 'same_name', category: :general)
|
|
||||||
english = described_class.find_or_create_by_tag_name!(
|
|
||||||
locale, 'same_name', category: :character)
|
|
||||||
|
|
||||||
expect(english).not_to eq(japanese)
|
|
||||||
expect(english.tag_name.language_code).to eq('en')
|
|
||||||
expect(japanese.tag_name.language_code).to eq('ja')
|
|
||||||
expect(described_class.find_or_create_by_tag_name!(
|
|
||||||
locale, 'same_name', category: :general)).to eq(english)
|
|
||||||
expect(described_class.find_or_create_by_tag_name!(
|
|
||||||
Locale.nipponese, 'same_name', category: :general)).to eq(japanese)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'preserves the V1 representative when looking up another primary language' do
|
|
||||||
tag = create(:tag, name: '日本語代表名')
|
|
||||||
representative_id = tag.tag_name_id
|
|
||||||
english = create(:tag_name, name: 'english_primary', tag:,
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
|
|
||||||
found = described_class.find_or_create_by_tag_name!(
|
|
||||||
locale, english.name, category: :general)
|
|
||||||
|
|
||||||
expect(found).to eq(tag)
|
|
||||||
expect(tag.reload.tag_name_id).to eq(representative_id)
|
|
||||||
expect(english.reload.tag_id).to eq(tag.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'normalises names using the supplied locale' do
|
|
||||||
tags = described_class.normalise_tags!(
|
|
||||||
locale, ['character:normalised_english'],
|
|
||||||
with_tagme: false, with_no_deerjikist: false)
|
|
||||||
|
|
||||||
expect(tags.length).to eq(1)
|
|
||||||
expect(tags.first.tag_name).to have_attributes(
|
|
||||||
name: 'normalised_english', language_code: 'en', script_code: 'Latn',
|
|
||||||
primary_flg: true, tag_id: tags.first.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a tag and name with the requested category after stripping whitespace' do
|
it 'creates a tag and name with the requested category after stripping whitespace' do
|
||||||
tag = nil
|
tag = nil
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
tag = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
tag = described_class.find_or_create_by_tag_name!(
|
||||||
' lookup_new ', category: :character)
|
' lookup_new ', category: :character)
|
||||||
}.to change(Tag, :count).by(1).and change(TagName, :count).by(1)
|
}.to change(Tag, :count).by(1).and change(TagName, :count).by(1)
|
||||||
|
|
||||||
@@ -269,42 +195,31 @@ RSpec.describe Tag, type: :model do
|
|||||||
expect(tag.category).to eq('character')
|
expect(tag.category).to eq('character')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'reuses the owning tag for an alias without changing its category' do
|
it 'reuses the canonical 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
|
alias_name = TagName.create!(name: 'lookup_alias', canonical: tag.tag_name)
|
||||||
alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
|
|
||||||
expect(alias_name.reload).to have_attributes(
|
|
||||||
tag_id: tag.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
found = nil
|
found = nil
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
found = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
found = described_class.find_or_create_by_tag_name!(
|
||||||
alias_name.name, category: :general)
|
alias_name.name, category: :general)
|
||||||
}.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(found).to eq(tag)
|
expect(found).to eq(tag)
|
||||||
expect(found.category).to eq('character')
|
expect(found.category).to eq('character')
|
||||||
expect(tag.reload.tag_name_id).to eq(representative_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'reuses the owning tag through another alias' do
|
it 'creates a tag for an existing canonical name reached through an alias' do
|
||||||
tag = create(:tag)
|
canonical = create(:tag_name)
|
||||||
primary = tag.tag_name
|
alias_name = TagName.create!(name: 'lookup_alias', canonical:)
|
||||||
alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
|
tag = nil
|
||||||
other_alias = create(:tag_name, :alias, name: 'lookup_other_alias', tag:)
|
|
||||||
|
|
||||||
expect([alias_name.reload, other_alias.reload]).to all(have_attributes(
|
|
||||||
tag_id: tag.id,
|
|
||||||
primary_flg: false, language_code: 'ja'))
|
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
found = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
tag = described_class.find_or_create_by_tag_name!(
|
||||||
alias_name.name, category: :general)
|
alias_name.name, category: :general)
|
||||||
expect(found).to eq(tag)
|
}.to change(Tag, :count).by(1).and change(TagName, :count).by(0)
|
||||||
}.to change(Tag, :count).by(0).and change(TagName, :count).by(0)
|
|
||||||
|
|
||||||
expect(tag.reload.tag_name).to eq(primary)
|
expect(tag.tag_name).to eq(canonical)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -328,8 +243,7 @@ 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).to have_attributes(
|
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
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,8 +269,7 @@ 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).to have_attributes(
|
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
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
|
||||||
@@ -364,7 +277,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', tag: source_tag)
|
source_alias = TagName.create!(name: 'merge_alias', canonical: source_tag_name)
|
||||||
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
|
||||||
|
|
||||||
@@ -434,8 +347,7 @@ 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).to have_attributes(
|
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
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
|
||||||
@@ -466,8 +378,7 @@ 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).to have_attributes(
|
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
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
|
||||||
@@ -496,8 +407,7 @@ 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).to have_attributes(
|
expect(earlier_name.reload.canonical_id).to be_nil
|
||||||
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)
|
||||||
@@ -506,8 +416,7 @@ 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).to have_attributes(
|
expect(source_tag_name.reload.canonical_id).to be_nil
|
||||||
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
|
||||||
|
|||||||
@@ -898,11 +898,12 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /gekanator/questions' do
|
describe 'GET /gekanator/questions' do
|
||||||
it 'omits questions for deprecated tags' do
|
it 'omits questions for deprecated tags' do
|
||||||
active_tag = create(:tag, name: 'active_question_tag', category: :general)
|
active_tag = Tag.create!(name: 'active_question_tag', category: :general)
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
name: 'deprecated_question_tag',
|
name: 'deprecated_question_tag',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
|
|
||||||
[active_tag, deprecated_tag].each do |question_tag|
|
[active_tag, deprecated_tag].each do |question_tag|
|
||||||
GekanatorQuestion.create!(
|
GekanatorQuestion.create!(
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ require 'rails_helper'
|
|||||||
RSpec.describe 'Gekanator posts API', type: :request do
|
RSpec.describe 'Gekanator posts API', type: :request do
|
||||||
describe 'GET /gekanator/posts' do
|
describe 'GET /gekanator/posts' do
|
||||||
it 'omits deprecated tags and returns the stored similarity cosine' do
|
it 'omits deprecated tags and returns the stored similarity cosine' do
|
||||||
active_tag = create(:tag, name: 'active tag', category: :general)
|
active_tag = Tag.create!(name: 'active tag', category: :general)
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
name: 'deprecated tag',
|
name: 'deprecated tag',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
post_record = Post.create!(title: 'source', url: 'https://example.com/source')
|
post_record = Post.create!(title: 'source', url: 'https://example.com/source')
|
||||||
target_post = Post.create!(title: 'target', url: 'https://example.com/target')
|
target_post = Post.create!(title: 'target', url: 'https://example.com/target')
|
||||||
|
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'Locale propagation on write paths', type: :request do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
let(:member) { create(:user, :member) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in_as(member)
|
|
||||||
end
|
|
||||||
|
|
||||||
def expect_localised_tag(name, expected_locale)
|
|
||||||
tag_name = TagName.find_by!(name:, language_code: expected_locale.language_code)
|
|
||||||
expect(tag_name).to have_attributes(
|
|
||||||
script_code: expected_locale.script_code, primary_flg: true)
|
|
||||||
expect(tag_name.tag_id).to be_present
|
|
||||||
expect(tag_name.tag.tag_name_id).to eq(tag_name.id)
|
|
||||||
tag_name.tag
|
|
||||||
end
|
|
||||||
|
|
||||||
['en', nil].each do |requested_locale|
|
|
||||||
context "with locale #{ requested_locale.inspect }" do
|
|
||||||
let(:locale_params) { requested_locale ? { locale: requested_locale } : { } }
|
|
||||||
let(:expected_locale) { requested_locale ? locale : Locale.nipponese }
|
|
||||||
|
|
||||||
it 'creates post tags in the requested language or Japanese fallback' do
|
|
||||||
post '/posts', params: locale_params.merge(
|
|
||||||
title: 'Locale post', url: 'https://example.com/locale-post',
|
|
||||||
tags: 'locale_post_tag', parent_post_ids: '')
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:created), response.body
|
|
||||||
tag = expect_localised_tag('locale_post_tag', expected_locale)
|
|
||||||
expect(Post.find(json.fetch('id')).tags).to include(tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates post tags in the requested language or Japanese fallback' do
|
|
||||||
record = create(:post)
|
|
||||||
PostVersionRecorder.record!(post: record, event_type: :create, created_by_user: member)
|
|
||||||
|
|
||||||
put "/posts/#{ record.id }", params: locale_params.merge(
|
|
||||||
title: record.title, tags: 'locale_updated_tag', parent_post_ids: '',
|
|
||||||
base_version_no: record.reload.version_no)
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
tag = expect_localised_tag('locale_updated_tag', expected_locale)
|
|
||||||
expect(record.reload.tags).to include(tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'passes the resolved locale to bulk creation' do
|
|
||||||
# Worker propagation is exercised in post_bulk_creator_spec.
|
|
||||||
expect_any_instance_of(PostBulkCreator).to receive(:run)
|
|
||||||
.with(expected_locale).and_return(results: [])
|
|
||||||
manifest = [{ title: 'Bulk locale', url: 'https://example.com/bulk-locale',
|
|
||||||
tags: 'bulk_locale_tag', parent_post_ids: '' }]
|
|
||||||
|
|
||||||
post '/posts/bulk', params: locale_params.merge(posts: JSON.generate(manifest)),
|
|
||||||
headers: { 'CONTENT_TYPE' => 'multipart/form-data' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates materials with tags in the resolved locale' do
|
|
||||||
post '/materials', params: locale_params.merge(
|
|
||||||
tag: 'locale_material', url: 'https://example.com/material')
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:created), response.body
|
|
||||||
tag = expect_localised_tag('locale_material', expected_locale)
|
|
||||||
expect(Material.find(json.fetch('id')).tag).to eq(tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates materials with tags in the resolved locale' do
|
|
||||||
tag = create(:tag, category: :material)
|
|
||||||
material = Material.create!(tag:, url: 'https://example.com/material')
|
|
||||||
|
|
||||||
put "/materials/#{ material.id }", params: locale_params.merge(
|
|
||||||
tag: 'locale_material_updated', url: material.url)
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
resolved_tag = expect_localised_tag('locale_material_updated', expected_locale)
|
|
||||||
expect(material.reload.tag).to eq(resolved_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'links Nico external tags to internal tags in the resolved locale' do
|
|
||||||
external = create(:external_tag)
|
|
||||||
NicoTagVersionRecorder.record!(
|
|
||||||
external_tag: external, event_type: :create, created_by_user: member)
|
|
||||||
|
|
||||||
put "/tags/nico/#{ external.id }", params: locale_params.merge(tags: 'locale_link')
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
tag = expect_localised_tag('locale_link', expected_locale)
|
|
||||||
expect(external.reload.linked_tags).to contain_exactly(tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates parent tags in the resolved locale' do
|
|
||||||
tag = create(:tag)
|
|
||||||
|
|
||||||
put "/tags/#{ tag.id }", params: locale_params.merge(
|
|
||||||
name: tag.name, category: tag.category, deprecated: false,
|
|
||||||
aliases: '', parent_tags: 'locale_parent')
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
parent = expect_localised_tag('locale_parent', expected_locale)
|
|
||||||
expect(TagImplication.where(tag:).pluck(:parent_tag_id)).to eq([parent.id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with existing names in both languages' do
|
|
||||||
let!(:english_name) do
|
|
||||||
create(:tag_name, name: 'shared_name', language_code: 'en', script_code: 'Latn')
|
|
||||||
end
|
|
||||||
let!(:english_tag) { create(:tag, tag_name: english_name, category: :material) }
|
|
||||||
let!(:japanese_tag) do
|
|
||||||
create(:tag, name: 'temporary_japanese', category: :general).tap do |tag|
|
|
||||||
# Isolate lookup from the separately tested uniqueness validation.
|
|
||||||
tag.tag_name.update_columns(name: 'shared_name')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'plans and creates the post using the English identity and category' do
|
|
||||||
post '/posts', params: {
|
|
||||||
locale: 'en', title: 'English identity',
|
|
||||||
url: 'https://example.com/english-identity', tags: 'shared_name', parent_post_ids: '' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:created), response.body
|
|
||||||
record = Post.find(json.fetch('id'))
|
|
||||||
expect(record.tags).to include(english_tag)
|
|
||||||
expect(record.tags).not_to include(japanese_tag)
|
|
||||||
expect(english_tag.reload.category).to eq('material')
|
|
||||||
expect(japanese_tag.reload.category).to eq('general')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates the post using the English identity' do
|
|
||||||
record = create(:post)
|
|
||||||
PostVersionRecorder.record!(post: record, event_type: :create, created_by_user: member)
|
|
||||||
|
|
||||||
put "/posts/#{ record.id }", params: {
|
|
||||||
locale: 'en', title: record.title, tags: 'shared_name', parent_post_ids: '',
|
|
||||||
base_version_no: record.reload.version_no }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(record.reload.tags).to include(english_tag)
|
|
||||||
expect(record.tags).not_to include(japanese_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'resolves the English material tag' do
|
|
||||||
post '/materials', params: {
|
|
||||||
locale: 'en', tag: 'shared_name', url: 'https://example.com/english-material' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:created), response.body
|
|
||||||
expect(Material.find(json.fetch('id')).tag).to eq(english_tag)
|
|
||||||
expect(Material.where(tag: japanese_tag)).to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'resolves the English parent tag' do
|
|
||||||
child = create(:tag)
|
|
||||||
|
|
||||||
put "/tags/#{ child.id }", params: {
|
|
||||||
locale: 'en', name: child.name, category: child.category, deprecated: false,
|
|
||||||
aliases: '', parent_tags: 'shared_name' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(TagImplication.where(tag: child).pluck(:parent_tag_id)).to eq([english_tag.id])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'resolves the English internal tag for a Nico link' do
|
|
||||||
external = create(:external_tag)
|
|
||||||
NicoTagVersionRecorder.record!(
|
|
||||||
external_tag: external, event_type: :create, created_by_user: member)
|
|
||||||
|
|
||||||
put "/tags/nico/#{ external.id }", params: { locale: 'en', tags: 'shared_name' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(external.reload.linked_tags).to contain_exactly(english_tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when renaming or updating aliases in English' do
|
|
||||||
let!(:english_name) do
|
|
||||||
create(:tag_name, name: 'english_original', language_code: 'en', script_code: 'Latn')
|
|
||||||
end
|
|
||||||
let!(:tag) { create(:tag, tag_name: english_name) }
|
|
||||||
|
|
||||||
it 'allows a rename to a name already used in Japanese' do
|
|
||||||
japanese = create(:tag, name: 'rename_target')
|
|
||||||
|
|
||||||
patch "/tags/#{ tag.id }", params: { locale: 'en', name: 'rename_target' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(tag.reload.tag_name).to have_attributes(
|
|
||||||
name: 'rename_target', language_code: 'en', tag_id: tag.id)
|
|
||||||
expect(japanese.reload.tag_name).to have_attributes(
|
|
||||||
name: 'rename_target', language_code: 'ja', tag_id: japanese.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates aliases in the requested language' do
|
|
||||||
put "/tags/#{ tag.id }", params: {
|
|
||||||
locale: 'en', name: tag.name, category: tag.category, deprecated: false,
|
|
||||||
aliases: 'english_alias', parent_tags: '' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(TagName.find_by!(language_code: 'en', name: 'english_alias'))
|
|
||||||
.to have_attributes(
|
|
||||||
script_code: 'Latn',
|
|
||||||
tag_id: tag.id, primary_flg: false, language_code: 'en')
|
|
||||||
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
|
|
||||||
|
|
||||||
it 'does not take an alias from another language' do
|
|
||||||
japanese = create(:tag, name: 'japanese_owner')
|
|
||||||
japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: japanese)
|
|
||||||
expect(japanese_alias.reload).to have_attributes(
|
|
||||||
tag_id: japanese.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
|
|
||||||
put "/tags/#{ tag.id }", params: {
|
|
||||||
locale: 'en', name: tag.name, category: tag.category, deprecated: false,
|
|
||||||
aliases: 'shared_alias', parent_tags: '' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
|
||||||
expect(japanese_alias.reload).to have_attributes(
|
|
||||||
tag_id: japanese.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
expect(TagName.find_by!(language_code: 'en', name: 'shared_alias'))
|
|
||||||
.to have_attributes(
|
|
||||||
tag_id: tag.id, script_code: 'Latn',
|
|
||||||
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
|
|
||||||
@@ -26,10 +26,10 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /materials' do
|
describe 'GET /materials' do
|
||||||
let!(:tag_a) do
|
let!(:tag_a) do
|
||||||
create(:tag, name: 'material_index_a', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_index_a'), category: :material)
|
||||||
end
|
end
|
||||||
let!(:tag_b) do
|
let!(:tag_b) do
|
||||||
create(:tag, name: 'material_index_b', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_index_b'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:material_a) do
|
let!(:material_a) do
|
||||||
@@ -116,20 +116,20 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
it 'filters by descendant tags and returns stable parent tag groups' do
|
it 'filters by descendant tags and returns stable parent tag groups' do
|
||||||
root =
|
root =
|
||||||
create(:tag, name: 'material_scope_root',
|
Tag.create!(tag_name: TagName.create!(name: 'material_scope_root'),
|
||||||
category: :material)
|
category: :material)
|
||||||
child_b =
|
child_b =
|
||||||
create(:tag, name: 'material_scope_b',
|
Tag.create!(tag_name: TagName.create!(name: 'material_scope_b'),
|
||||||
category: :material)
|
category: :material)
|
||||||
child_a =
|
child_a =
|
||||||
create(:tag, name: 'material_scope_a',
|
Tag.create!(tag_name: TagName.create!(name: 'material_scope_a'),
|
||||||
category: :material)
|
category: :material)
|
||||||
deprecated =
|
deprecated =
|
||||||
create(:tag, name: 'material_scope_old',
|
Tag.create!(tag_name: TagName.create!(name: 'material_scope_old'),
|
||||||
category: :material,
|
category: :material,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current)
|
||||||
grandchild =
|
grandchild =
|
||||||
create(:tag, name: 'material_scope_grandchild',
|
Tag.create!(tag_name: TagName.create!(name: 'material_scope_grandchild'),
|
||||||
category: :material)
|
category: :material)
|
||||||
root_material =
|
root_material =
|
||||||
build_material(tag: root, user: member_user,
|
build_material(tag: root, user: member_user,
|
||||||
@@ -179,7 +179,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /materials/:id' do
|
describe 'GET /materials/:id' do
|
||||||
let!(:tag) do
|
let!(:tag) do
|
||||||
create(:tag, name: 'material_show', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_show'), category: :material)
|
||||||
end
|
end
|
||||||
let!(:material) do
|
let!(:material) do
|
||||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'show.png'))
|
build_material(tag:, user: member_user, file: dummy_upload(filename: 'show.png'))
|
||||||
@@ -323,8 +323,8 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'returns 422 when the existing tag is not material/character' do
|
it 'returns 422 when the existing tag is not material/character' do
|
||||||
general_tag_name = create(:tag_name, name: 'material_create_general_tag')
|
general_tag_name = TagName.create!(name: 'material_create_general_tag')
|
||||||
create(:tag, tag_name: general_tag_name, category: :general)
|
Tag.create!(tag_name: general_tag_name, category: :general)
|
||||||
|
|
||||||
post '/materials', params: {
|
post '/materials', params: {
|
||||||
tag: 'material_create_general_tag',
|
tag: 'material_create_general_tag',
|
||||||
@@ -384,7 +384,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
describe 'PUT /materials/:id' do
|
describe 'PUT /materials/:id' do
|
||||||
let!(:tag) do
|
let!(:tag) do
|
||||||
create(:tag, name: 'material_update_old', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_update_old'), category: :material)
|
||||||
end
|
end
|
||||||
let!(:material) do
|
let!(:material) do
|
||||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'old.png'))
|
build_material(tag:, user: member_user, file: dummy_upload(filename: 'old.png'))
|
||||||
@@ -508,7 +508,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
it 'backfills a create tag_version for an existing material tag without history' do
|
it 'backfills a create tag_version for an existing material tag without history' do
|
||||||
existing_tag =
|
existing_tag =
|
||||||
create(:tag, name: 'material_update_existing_no_history',
|
Tag.create!(tag_name: TagName.create!(name: 'material_update_existing_no_history'),
|
||||||
category: :material)
|
category: :material)
|
||||||
|
|
||||||
expect(existing_tag.tag_versions).to be_empty
|
expect(existing_tag.tag_versions).to be_empty
|
||||||
@@ -531,7 +531,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
it 'backfills a create tag_version for an existing character tag without history' do
|
it 'backfills a create tag_version for an existing character tag without history' do
|
||||||
existing_tag =
|
existing_tag =
|
||||||
create(:tag, name: 'material_update_character_no_history',
|
Tag.create!(tag_name: TagName.create!(name: 'material_update_character_no_history'),
|
||||||
category: :character)
|
category: :character)
|
||||||
|
|
||||||
expect(existing_tag.tag_versions).to be_empty
|
expect(existing_tag.tag_versions).to be_empty
|
||||||
@@ -651,8 +651,8 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /materials/download.zip' do
|
describe 'GET /materials/download.zip' do
|
||||||
let!(:tag_a) { create(:tag, name: 'zip_a', category: :material) }
|
let!(:tag_a) { Tag.create!(tag_name: TagName.create!(name: 'zip_a'), category: :material) }
|
||||||
let!(:tag_b) { create(:tag, name: 'zip_b', category: :material) }
|
let!(:tag_b) { Tag.create!(tag_name: TagName.create!(name: 'zip_b'), category: :material) }
|
||||||
let!(:material_a) do
|
let!(:material_a) do
|
||||||
build_material(tag: tag_a, user: member_user,
|
build_material(tag: tag_a, user: member_user,
|
||||||
file: dummy_upload(filename: 'a.png', body: 'zip-a'))
|
file: dummy_upload(filename: 'a.png', body: 'zip-a'))
|
||||||
@@ -701,7 +701,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /materials/versions' do
|
describe 'GET /materials/versions' do
|
||||||
let!(:tag) do
|
let!(:tag) do
|
||||||
create(:tag, name: 'material_history', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_history'), category: :material)
|
||||||
end
|
end
|
||||||
let!(:material) do
|
let!(:material) do
|
||||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'history.png'))
|
build_material(tag:, user: member_user, file: dummy_upload(filename: 'history.png'))
|
||||||
@@ -776,7 +776,7 @@ RSpec.describe 'Materials API', type: :request do
|
|||||||
|
|
||||||
describe 'DELETE /materials/:id' do
|
describe 'DELETE /materials/:id' do
|
||||||
let!(:tag) do
|
let!(:tag) do
|
||||||
create(:tag, name: 'material_destroy', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'material_destroy'), category: :material)
|
||||||
end
|
end
|
||||||
let!(:material) do
|
let!(:material) do
|
||||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'destroy.png'))
|
build_material(tag:, user: member_user, file: dummy_upload(filename: 'destroy.png'))
|
||||||
|
|||||||
@@ -162,11 +162,11 @@ RSpec.describe 'NicoTags', type: :request do
|
|||||||
|
|
||||||
nico_tag = create(:external_tag, name: 'nico_tags_spec_source')
|
nico_tag = create(:external_tag, name: 'nico_tags_spec_source')
|
||||||
|
|
||||||
linked_a_name = create(:tag_name, name: 'nico_linked_a')
|
linked_a_name = TagName.create!(name: 'nico_linked_a')
|
||||||
linked_a = create(:tag, tag_name: linked_a_name, category: :general)
|
linked_a = Tag.create!(tag_name: linked_a_name, category: :general)
|
||||||
|
|
||||||
linked_b_name = create(:tag_name, name: 'nico_linked_b')
|
linked_b_name = TagName.create!(name: 'nico_linked_b')
|
||||||
linked_b = create(:tag, tag_name: linked_b_name, category: :general)
|
linked_b = Tag.create!(tag_name: linked_b_name, category: :general)
|
||||||
|
|
||||||
NicoTagVersionRecorder.record!(external_tag: nico_tag,
|
NicoTagVersionRecorder.record!(external_tag: nico_tag,
|
||||||
event_type: :create, created_by_user: admin)
|
event_type: :create, created_by_user: admin)
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
'sections' => [])
|
'sections' => [])
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
|
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
|
||||||
let!(:tag) { create(:tag, tag_name: tag_name, category: :general) }
|
let!(:tag) { Tag.create!(tag_name: tag_name, category: :general) }
|
||||||
|
|
||||||
let!(:post_record) do
|
let!(:post_record) do
|
||||||
Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
|
Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
|
||||||
@@ -125,11 +125,11 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
describe 'GET /posts' do
|
describe 'GET /posts' do
|
||||||
let!(:user) { create_member_user! }
|
let!(:user) { create_member_user! }
|
||||||
|
|
||||||
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
|
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
|
||||||
let!(:tag) { create(:tag, tag_name:, category: :general) }
|
let!(:tag) { Tag.create!(tag_name:, category: :general) }
|
||||||
let!(:tag_name2) { create(:tag_name, name: 'unko') }
|
let!(:tag_name2) { TagName.create!(name: 'unko') }
|
||||||
let!(:tag2) { create(:tag, tag_name: tag_name2, category: :deerjikist) }
|
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :deerjikist) }
|
||||||
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
|
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
|
||||||
|
|
||||||
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',
|
||||||
@@ -341,17 +341,17 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when tags contain not:' do
|
context 'when tags contain not:' do
|
||||||
let!(:foo_tag_name) { create(:tag_name, name: 'not_spec_foo') }
|
let!(:foo_tag_name) { TagName.create!(name: 'not_spec_foo') }
|
||||||
let!(:foo_tag) { create(:tag, tag_name: foo_tag_name, category: :general) }
|
let!(:foo_tag) { Tag.create!(tag_name: foo_tag_name, category: :general) }
|
||||||
|
|
||||||
let!(:bar_tag_name) { create(:tag_name, name: 'not_spec_bar') }
|
let!(:bar_tag_name) { TagName.create!(name: 'not_spec_bar') }
|
||||||
let!(:bar_tag) { create(:tag, tag_name: bar_tag_name, category: :general) }
|
let!(:bar_tag) { Tag.create!(tag_name: bar_tag_name, category: :general) }
|
||||||
|
|
||||||
let!(:baz_tag_name) { create(:tag_name, name: 'not_spec_baz') }
|
let!(:baz_tag_name) { TagName.create!(name: 'not_spec_baz') }
|
||||||
let!(:baz_tag) { create(:tag, tag_name: baz_tag_name, category: :general) }
|
let!(:baz_tag) { Tag.create!(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', tag: foo_tag)
|
TagName.create!(name: 'not_spec_foo_alias', canonical: foo_tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:foo_only_post) do
|
let!(:foo_only_post) do
|
||||||
@@ -772,10 +772,11 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'omits deprecated tags' do
|
it 'omits deprecated tags' do
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
name: 'deprecated_post_tag',
|
name: 'deprecated_post_tag',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
PostTag.create!(post: post_record, tag: deprecated_tag)
|
PostTag.create!(post: post_record, tag: deprecated_tag)
|
||||||
|
|
||||||
request
|
request
|
||||||
@@ -852,9 +853,9 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
|
|
||||||
tags =
|
tags =
|
||||||
15.times.map do |i|
|
15.times.map do |i|
|
||||||
tag_name = create(:tag_name, name: "show_query_tag_#{ i }")
|
tag_name = TagName.create!(name: "show_query_tag_#{ i }")
|
||||||
tag = create(:tag, tag_name:, category: :general)
|
tag = Tag.create!(tag_name:, category: :general)
|
||||||
create(:tag_name, :alias, name: "show_query_alias_#{ i }", tag:)
|
TagName.create!(name: "show_query_alias_#{ i }", canonical: tag_name)
|
||||||
PostTag.create!(post: post_record, tag:)
|
PostTag.create!(post: post_record, tag:)
|
||||||
tag
|
tag
|
||||||
end
|
end
|
||||||
@@ -999,7 +1000,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', tag:) }
|
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
|
||||||
|
|
||||||
it '401 when not logged in' do
|
it '401 when not logged in' do
|
||||||
sign_out
|
sign_out
|
||||||
@@ -1156,10 +1157,11 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects a deprecated tag specified directly' do
|
it 'rejects a deprecated tag specified directly' do
|
||||||
create(:tag,
|
Tag.create!(
|
||||||
name: 'deprecated_direct_tag',
|
name: 'deprecated_direct_tag',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
sign_in_as(member)
|
sign_in_as(member)
|
||||||
|
|
||||||
post '/posts', params: post_write_params(
|
post '/posts', params: post_write_params(
|
||||||
@@ -1176,16 +1178,18 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'expands through multiple deprecated parent tags and saves active ancestors' do
|
it 'expands through multiple deprecated parent tags and saves active ancestors' do
|
||||||
child = create(:tag, name: 'active_child', category: :general)
|
child = Tag.create!(name: 'active_child', category: :general)
|
||||||
deprecated_parent = create(:tag,
|
deprecated_parent = Tag.create!(
|
||||||
name: 'deprecated_parent',
|
name: 'deprecated_parent',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
deprecated_grandparent = create(:tag,
|
)
|
||||||
name: 'deprecated_grandparent',
|
deprecated_grandparent = Tag.create!(
|
||||||
category: :general,
|
name: 'deprecated_grandparent',
|
||||||
deprecated_at: Time.current)
|
category: :general,
|
||||||
active_grandparent = create(:tag, name: 'active_grandparent', category: :general)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
|
active_grandparent = Tag.create!(name: 'active_grandparent', category: :general)
|
||||||
TagImplication.create!(tag: child, parent_tag: deprecated_parent)
|
TagImplication.create!(tag: child, parent_tag: deprecated_parent)
|
||||||
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
||||||
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_grandparent)
|
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_grandparent)
|
||||||
@@ -1618,7 +1622,6 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
expect(arguments[:thumbnails].keys).to eq([0])
|
expect(arguments[:thumbnails].keys).to eq([0])
|
||||||
expect(arguments[:host]).to eq('http://www.example.com')
|
expect(arguments[:host]).to eq('http://www.example.com')
|
||||||
end
|
end
|
||||||
expect(creator).to have_received(:run).with(Locale.nipponese)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects malformed manifests as a request-level error' do
|
it 'rejects malformed manifests as a request-level error' do
|
||||||
@@ -1655,8 +1658,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
create(:post_tag_section, post: post_record, tag:,
|
create(:post_tag_section, post: post_record, tag:,
|
||||||
begin_ms: 1000, end_ms: 2000)
|
begin_ms: 1000, end_ms: 2000)
|
||||||
|
|
||||||
tn2 = create(:tag_name, name: 'spec_tag_2')
|
tn2 = TagName.create!(name: 'spec_tag_2')
|
||||||
replacement_tag = create(:tag, tag_name: tn2, category: :general)
|
replacement_tag = Tag.create!(tag_name: tn2, category: :general)
|
||||||
|
|
||||||
put "/posts/#{post_record.id}", params: post_update_params(
|
put "/posts/#{post_record.id}", params: post_update_params(
|
||||||
post_record,
|
post_record,
|
||||||
@@ -1704,10 +1707,11 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects a deprecated tag specified directly' do
|
it 'rejects a deprecated tag specified directly' do
|
||||||
create(:tag,
|
Tag.create!(
|
||||||
name: 'deprecated_update_tag',
|
name: 'deprecated_update_tag',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
sign_in_as(member)
|
sign_in_as(member)
|
||||||
|
|
||||||
put "/posts/#{ post_record.id }", params: post_update_params(
|
put "/posts/#{ post_record.id }", params: post_update_params(
|
||||||
@@ -1982,8 +1986,7 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
|
|
||||||
base_version = create_post_version_for!(post_record.reload)
|
base_version = create_post_version_for!(post_record.reload)
|
||||||
|
|
||||||
current_tag = Tag.find_or_create_by_tag_name!(
|
current_tag = Tag.find_or_create_by_tag_name!('current_added_tag', category: :general)
|
||||||
Locale.nipponese, 'current_added_tag', category: :general)
|
|
||||||
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
||||||
|
|
||||||
PostVersionRecorder.record!(
|
PostVersionRecorder.record!(
|
||||||
@@ -2012,8 +2015,7 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
|
|
||||||
base_version = create_post_version_for!(post_record.reload)
|
base_version = create_post_version_for!(post_record.reload)
|
||||||
|
|
||||||
current_tag = Tag.find_or_create_by_tag_name!(
|
current_tag = Tag.find_or_create_by_tag_name!('current_merge_tag', category: :general)
|
||||||
Locale.nipponese, 'current_merge_tag', category: :general)
|
|
||||||
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
||||||
|
|
||||||
PostVersionRecorder.record!(
|
PostVersionRecorder.record!(
|
||||||
@@ -2100,8 +2102,7 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
sign_in_as(member)
|
sign_in_as(member)
|
||||||
|
|
||||||
nico_tag = create_nico_tag!('nico:relation_source')
|
nico_tag = create_nico_tag!('nico:relation_source')
|
||||||
linked_tag = Tag.find_or_create_by_tag_name!(
|
linked_tag = Tag.find_or_create_by_tag_name!('relation_linked_tag', category: :general)
|
||||||
Locale.nipponese, 'relation_linked_tag', category: :general)
|
|
||||||
|
|
||||||
NicoTagRelation.create!(nico_tag:, tag: linked_tag)
|
NicoTagRelation.create!(nico_tag:, tag: linked_tag)
|
||||||
PostExternalTag.create!(post: post_record, external_tag: nico_tag)
|
PostExternalTag.create!(post: post_record, external_tag: nico_tag)
|
||||||
@@ -2190,8 +2191,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
let(:oc_from) { Time.zone.local(2019, 12, 31, 0, 0, 0) }
|
let(:oc_from) { Time.zone.local(2019, 12, 31, 0, 0, 0) }
|
||||||
let(:oc_before) { Time.zone.local(2020, 1, 1, 0, 0, 0) }
|
let(:oc_before) { Time.zone.local(2020, 1, 1, 0, 0, 0) }
|
||||||
|
|
||||||
let!(:tag_name2) { create(:tag_name, name: 'spec_tag_2') }
|
let!(:tag_name2) { TagName.create!(name: 'spec_tag_2') }
|
||||||
let!(:tag2) { create(:tag, tag_name: tag_name2, category: :general) }
|
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :general) }
|
||||||
|
|
||||||
def snapshot_tags(post)
|
def snapshot_tags(post)
|
||||||
post.snapshot_tag_names.join(' ')
|
post.snapshot_tag_names.join(' ')
|
||||||
@@ -2549,8 +2550,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
sign_in_as(member)
|
sign_in_as(member)
|
||||||
base_version = create_post_version_for!(post_record)
|
base_version = create_post_version_for!(post_record)
|
||||||
|
|
||||||
tag_name2 = create(:tag_name, name: 'spec_tag_2')
|
tag_name2 = TagName.create!(name: 'spec_tag_2')
|
||||||
create(:tag, tag_name: tag_name2, category: :general)
|
Tag.create!(tag_name: tag_name2, category: :general)
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
put "/posts/#{post_record.id}", params: post_write_params(
|
put "/posts/#{post_record.id}", params: post_write_params(
|
||||||
@@ -2714,8 +2715,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
|
|
||||||
base_version = create_post_version_for!(post_record.reload)
|
base_version = create_post_version_for!(post_record.reload)
|
||||||
|
|
||||||
tag_name2 = create(:tag_name, name: 'spec_tag_2')
|
tag_name2 = TagName.create!(name: 'spec_tag_2')
|
||||||
tag2 = create(:tag, tag_name: tag_name2, category: :general)
|
tag2 = Tag.create!(tag_name: tag_name2, category: :general)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
put "/posts/#{post_record.id}", params: post_write_params(
|
put "/posts/#{post_record.id}", params: post_write_params(
|
||||||
|
|||||||
@@ -232,8 +232,8 @@ RSpec.describe 'TagVersions API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create tag versions by wiki updates when tag has no versions yet' do
|
it 'does not create tag versions by wiki updates when tag has no versions yet' do
|
||||||
wiki_tag_name = create(:tag_name, name: 'tag_versions_from_wiki')
|
wiki_tag_name = TagName.create!(name: 'tag_versions_from_wiki')
|
||||||
wiki_tag = create(:tag, tag_name: wiki_tag_name, category: :general)
|
wiki_tag = Tag.create!(tag_name: wiki_tag_name, category: :general)
|
||||||
|
|
||||||
wiki_page =
|
wiki_page =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_tag! name:, category: :general
|
def create_tag! name:, category: :general
|
||||||
tag_name = create(:tag_name, name:)
|
tag_name = TagName.create!(name:)
|
||||||
create(:tag, tag_name:, category:)
|
Tag.create!(tag_name:, category:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_wiki_for_tag! tag:, body: 'wiki body', user: member_user
|
def create_wiki_for_tag! tag:, body: 'wiki body', user: member_user
|
||||||
|
|||||||
+173
-174
@@ -2,13 +2,13 @@ require 'cgi'
|
|||||||
require 'rails_helper'
|
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) { TagName.create!(name: 'spec_tag') }
|
||||||
let!(:tag) { create(:tag, tag_name: tn, category: :general) }
|
let!(:tag) { Tag.create!(tag_name: tn, category: :general) }
|
||||||
let!(:alias_tn) { create(:tag_name, :alias, name: 'unko', tag:) }
|
let!(:alias_tn) { TagName.create!(name: 'unko', canonical: tn) }
|
||||||
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) { TagName.create!(name: 'unknown') }
|
||||||
let!(:tag2) { create(:tag, tag_name: tn2, category: :general) }
|
let!(:tag2) { Tag.create!(tag_name: tn2, category: :general) }
|
||||||
|
|
||||||
def response_tags
|
def response_tags
|
||||||
json.fetch('tags')
|
json.fetch('tags')
|
||||||
@@ -91,7 +91,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filters tags by category' do
|
it 'filters tags by category' do
|
||||||
meme = create(:tag, name: 'meme_only', category: :meme)
|
meme = Tag.create!(tag_name: TagName.create!(name: 'meme_only'), category: :meme)
|
||||||
|
|
||||||
get '/tags', params: { category: 'meme' }
|
get '/tags', params: { category: 'meme' }
|
||||||
|
|
||||||
@@ -101,11 +101,12 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filters tags by deprecated state' do
|
it 'filters tags by deprecated state' do
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
name: 'deprecated_filter',
|
name: 'deprecated_filter',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: 1.day.from_now)
|
deprecated_at: 1.day.from_now
|
||||||
active_tag = create(:tag, name: 'active_filter', category: :general)
|
)
|
||||||
|
active_tag = Tag.create!(name: 'active_filter', category: :general)
|
||||||
|
|
||||||
get '/tags', params: { name: '_filter', deprecated: '1' }
|
get '/tags', params: { name: '_filter', deprecated: '1' }
|
||||||
|
|
||||||
@@ -121,9 +122,9 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filters tags by post_count range' do
|
it 'filters tags by post_count range' do
|
||||||
low = create(:tag, name: 'pc_low', category: :general)
|
low = Tag.create!(tag_name: TagName.create!(name: 'pc_low'), category: :general)
|
||||||
mid = create(:tag, name: 'pc_mid', category: :general)
|
mid = Tag.create!(tag_name: TagName.create!(name: 'pc_mid'), category: :general)
|
||||||
high = create(:tag, name: 'pc_high', category: :general)
|
high = Tag.create!(tag_name: TagName.create!(name: 'pc_high'), category: :general)
|
||||||
|
|
||||||
low.update_columns(post_count: 1)
|
low.update_columns(post_count: 1)
|
||||||
mid.update_columns(post_count: 3)
|
mid.update_columns(post_count: 3)
|
||||||
@@ -141,8 +142,8 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filters tags by created_at range' do
|
it 'filters tags by created_at range' do
|
||||||
old_tag = create(:tag, name: 'created_old', category: :general)
|
old_tag = Tag.create!(tag_name: TagName.create!(name: 'created_old'), category: :general)
|
||||||
new_tag = create(:tag, name: 'created_new', category: :general)
|
new_tag = Tag.create!(tag_name: TagName.create!(name: 'created_new'), category: :general)
|
||||||
|
|
||||||
old_time = Time.zone.local(2024, 1, 1, 0, 0, 0)
|
old_time = Time.zone.local(2024, 1, 1, 0, 0, 0)
|
||||||
new_time = Time.zone.local(2024, 2, 1, 0, 0, 0)
|
new_time = Time.zone.local(2024, 2, 1, 0, 0, 0)
|
||||||
@@ -161,8 +162,8 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filters tags by updated_at range' do
|
it 'filters tags by updated_at range' do
|
||||||
old_tag = create(:tag, name: 'updated_old', category: :general)
|
old_tag = Tag.create!(tag_name: TagName.create!(name: 'updated_old'), category: :general)
|
||||||
new_tag = create(:tag, name: 'updated_new', category: :general)
|
new_tag = Tag.create!(tag_name: TagName.create!(name: 'updated_new'), category: :general)
|
||||||
|
|
||||||
old_time = Time.zone.local(2024, 3, 1, 0, 0, 0)
|
old_time = Time.zone.local(2024, 3, 1, 0, 0, 0)
|
||||||
new_time = Time.zone.local(2024, 4, 1, 0, 0, 0)
|
new_time = Time.zone.local(2024, 4, 1, 0, 0, 0)
|
||||||
@@ -181,12 +182,12 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'orders tags by custom category order' do
|
it 'orders tags by custom category order' do
|
||||||
create(:tag, name: 'cat_deerjikist', category: :deerjikist)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_deerjikist'), category: :deerjikist)
|
||||||
create(:tag, name: 'cat_meme', category: :meme)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_meme'), category: :meme)
|
||||||
create(:tag, name: 'cat_character', category: :character)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_character'), category: :character)
|
||||||
create(:tag, name: 'cat_general', category: :general)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_general'), category: :general)
|
||||||
create(:tag, name: 'cat_material', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_material'), category: :material)
|
||||||
create(:tag, name: 'cat_meta', category: :meta)
|
Tag.create!(tag_name: TagName.create!(name: 'cat_meta'), category: :meta)
|
||||||
create(:external_tag, name: 'cat_nico')
|
create(:external_tag, name: 'cat_nico')
|
||||||
|
|
||||||
get '/tags', params: { name: 'cat_', order: 'category:asc', limit: 20 }
|
get '/tags', params: { name: 'cat_', order: 'category:asc', limit: 20 }
|
||||||
@@ -201,20 +202,20 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
context 'with mixed legacy pagination' do
|
context 'with mixed legacy pagination' do
|
||||||
let!(:first_tag) do
|
let!(:first_tag) do
|
||||||
create(:tag,
|
create(:tag,
|
||||||
tag_name: create(:tag_name, name: 'a_mixed_page'),
|
tag_name: create(:tag_name, name: 'a_mixed_page'),
|
||||||
category: :meme)
|
category: :meme)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:middle_tag) do
|
let!(:middle_tag) do
|
||||||
create(:tag,
|
create(:tag,
|
||||||
tag_name: create(:tag_name, name: 'm_mixed_page'),
|
tag_name: create(:tag_name, name: 'm_mixed_page'),
|
||||||
category: :meta)
|
category: :meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:last_tag) do
|
let!(:last_tag) do
|
||||||
create(:tag,
|
create(:tag,
|
||||||
tag_name: create(:tag_name, name: 'z_mixed_page'),
|
tag_name: create(:tag_name, name: 'z_mixed_page'),
|
||||||
category: :general)
|
category: :general)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:first_external) do
|
let!(:first_external) do
|
||||||
@@ -264,7 +265,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
|
|
||||||
it 'paginates and keeps total count' do
|
it 'paginates and keeps total count' do
|
||||||
%w[pag_a pag_b pag_c].each do |name|
|
%w[pag_a pag_b pag_c].each do |name|
|
||||||
create(:tag, tag_name: create(:tag_name, name:), category: :general)
|
Tag.create!(tag_name: TagName.create!(name:), category: :general)
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/tags', params: { name: 'pag_', order: 'name:asc', page: 2, limit: 2 }
|
get '/tags', params: { name: 'pag_', order: 'name:asc', page: 2, limit: 2 }
|
||||||
@@ -275,8 +276,8 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'falls back to default ordering when order is invalid' do
|
it 'falls back to default ordering when order is invalid' do
|
||||||
low = create(:tag, name: 'fallback_low', category: :general)
|
low = Tag.create!(tag_name: TagName.create!(name: 'fallback_low'), category: :general)
|
||||||
high = create(:tag, name: 'fallback_high', category: :general)
|
high = Tag.create!(tag_name: TagName.create!(name: 'fallback_high'), category: :general)
|
||||||
|
|
||||||
low.update_columns(post_count: 1)
|
low.update_columns(post_count: 1)
|
||||||
high.update_columns(post_count: 9)
|
high.update_columns(post_count: 9)
|
||||||
@@ -289,7 +290,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
|
|
||||||
it 'normalises invalid page and limit' do
|
it 'normalises invalid page and limit' do
|
||||||
%w[norm_a norm_b].each do |name|
|
%w[norm_a norm_b].each do |name|
|
||||||
create(:tag, tag_name: create(:tag_name, name:), category: :general)
|
Tag.create!(tag_name: TagName.create!(name:), category: :general)
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/tags', params: { name: 'norm_', order: 'name:asc', page: 0, limit: 0 }
|
get '/tags', params: { name: 'norm_', order: 'name:asc', page: 0, limit: 0 }
|
||||||
@@ -301,9 +302,10 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'returns aliases and parent tags' do
|
it 'returns aliases and parent tags' do
|
||||||
parent_tag = create(:tag,
|
parent_tag = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'index_parent_tag'),
|
tag_name: TagName.create!(name: 'index_parent_tag'),
|
||||||
category: :meme)
|
category: :meme
|
||||||
|
)
|
||||||
TagImplication.create!(tag:, parent_tag:)
|
TagImplication.create!(tag:, parent_tag:)
|
||||||
|
|
||||||
get '/tags', params: { name: 'spec_tag' }
|
get '/tags', params: { name: 'spec_tag' }
|
||||||
@@ -347,9 +349,10 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'returns aliases and parent tags' do
|
it 'returns aliases and parent tags' do
|
||||||
parent_tag = create(:tag,
|
parent_tag = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'show_parent_tag'),
|
tag_name: TagName.create!(name: 'show_parent_tag'),
|
||||||
category: :character)
|
category: :character
|
||||||
|
)
|
||||||
TagImplication.create!(tag:, parent_tag:)
|
TagImplication.create!(tag:, parent_tag:)
|
||||||
|
|
||||||
request
|
request
|
||||||
@@ -413,7 +416,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /tags/autocomplete' do
|
describe 'GET /tags/autocomplete' do
|
||||||
it 'combines internal and external matches without conflating equal ids' do
|
it 'combines internal and external matches without conflating equal ids' do
|
||||||
internal = create(:tag, category: :general, name: 'mixed_internal', post_count: 2)
|
internal = Tag.create!(category: :general, name: 'mixed_internal', post_count: 2)
|
||||||
external = create(:external_tag, id: internal.id,
|
external = create(:external_tag, id: internal.id,
|
||||||
name: 'mixed_external', post_count: 3)
|
name: 'mixed_external', post_count: 3)
|
||||||
|
|
||||||
@@ -452,7 +455,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
it 'limits the combined results to 20 and sorts ties by displayed name' do
|
it 'limits the combined results to 20 and sorts ties by displayed name' do
|
||||||
11.times do |i|
|
11.times do |i|
|
||||||
name = "combined_#{ i.to_s.rjust(2, '0') }"
|
name = "combined_#{ i.to_s.rjust(2, '0') }"
|
||||||
create(:tag, category: :general, name:, post_count: 1)
|
Tag.create!(category: :general, name:, post_count: 1)
|
||||||
create(:external_tag, name:, post_count: 1)
|
create(:external_tag, name:, post_count: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -464,9 +467,9 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
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 = Tag.create!(category: :general, name: 'switch_internal', post_count: 1)
|
||||||
alias_target = create(:tag, category: :general, name: 'alias_target', post_count: 1)
|
alias_target = Tag.create!(category: :general, name: 'alias_target', post_count: 1)
|
||||||
create(:tag_name, :alias, name: 'switch_alias', tag: alias_target)
|
TagName.create!(name: 'switch_alias', canonical: alias_target.tag_name)
|
||||||
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,15 +482,13 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
['%', '_'].each do |wildcard|
|
['%', '_'].each do |wildcard|
|
||||||
it "treats #{ wildcard } literally for primary, alias, and external names" do
|
it "treats #{ wildcard } literally for canonical, alias, and external names" do
|
||||||
literal = "literal#{ wildcard }match"
|
literal = "literal#{ wildcard }match"
|
||||||
create(:tag, category: :general, name: literal, post_count: 1)
|
Tag.create!(category: :general, name: literal, post_count: 1)
|
||||||
alias_target = create(
|
alias_target = Tag.create!(category: :general, name: 'literal_alias_target', post_count: 1)
|
||||||
:tag, category: :general, name: 'literal_alias_target', post_count: 1)
|
TagName.create!(name: "#{ literal }_alias", canonical: alias_target.tag_name)
|
||||||
create(:tag_name, :alias, name: "#{ literal }_alias",
|
|
||||||
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)
|
Tag.create!(category: :general, name: 'literalXmatch', post_count: 2)
|
||||||
create(:external_tag, name: 'literalXmatch', post_count: 2)
|
create(:external_tag, name: 'literalXmatch', post_count: 2)
|
||||||
|
|
||||||
get '/tags/autocomplete', params: { q: "literal#{ wildcard }" }
|
get '/tags/autocomplete', params: { q: "literal#{ wildcard }" }
|
||||||
@@ -509,7 +510,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 primary tag names by q with aliases' do
|
it 'returns matching canonical tags 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)
|
||||||
@@ -522,10 +523,11 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'omits deprecated tags' do
|
it 'omits deprecated tags' do
|
||||||
deprecated_tag = create(:tag,
|
deprecated_tag = Tag.create!(
|
||||||
name: 'spec_deprecated',
|
name: 'spec_deprecated',
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
deprecated_tag.update_columns(post_count: 1)
|
deprecated_tag.update_columns(post_count: 1)
|
||||||
|
|
||||||
get '/tags/autocomplete', params: { q: 'spec_', present: '0' }
|
get '/tags/autocomplete', params: { q: 'spec_', present: '0' }
|
||||||
@@ -772,8 +774,7 @@ 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!(language_code: 'ja', name: 'spec_tag'))
|
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.reload.tag_name)
|
||||||
.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' }
|
||||||
|
|
||||||
@@ -782,10 +783,8 @@ 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).to have_attributes(
|
expect(tag.tag_name.canonical_id).to be_nil
|
||||||
tag_id: tag.id, language_code: 'ja', primary_flg: true)
|
expect(TagName.find_by!(name: 'patch_roundtrip_target').canonical).to eq(tag.tag_name)
|
||||||
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
|
||||||
@@ -804,8 +803,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(tag.reload.name).to eq('spec_tag')
|
expect(tag.reload.name).to eq('spec_tag')
|
||||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('unko')
|
||||||
.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
|
||||||
|
|
||||||
@@ -822,27 +820,27 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
|
|
||||||
describe 'GET /tags/with-depth' do
|
describe 'GET /tags/with-depth' do
|
||||||
let!(:root_meme) do
|
let!(:root_meme) do
|
||||||
create(:tag, name: 'depth_a_root_meme', category: :meme)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_a_root_meme'), category: :meme)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:root_material) do
|
let!(:root_material) do
|
||||||
create(:tag, name: 'depth_b_root_material', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_b_root_material'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:hidden_general_root) do
|
let!(:hidden_general_root) do
|
||||||
create(:tag, name: 'depth_hidden_general_root', category: :general)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_hidden_general_root'), category: :general)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:child_character) do
|
let!(:child_character) do
|
||||||
create(:tag, name: 'depth_child_character', category: :character)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_child_character'), category: :character)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:grandchild_material) do
|
let!(:grandchild_material) do
|
||||||
create(:tag, name: 'depth_grandchild_material', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_grandchild_material'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:child_general) do
|
let!(:child_general) do
|
||||||
create(:tag, name: 'depth_child_general', category: :general)
|
Tag.create!(tag_name: TagName.create!(name: 'depth_child_general'), category: :general)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@@ -885,13 +883,15 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'passes through deprecated tags when finding children' do
|
it 'passes through deprecated tags when finding children' do
|
||||||
deprecated_middle = create(:tag,
|
deprecated_middle = Tag.create!(
|
||||||
name: 'depth_deprecated_middle',
|
name: 'depth_deprecated_middle',
|
||||||
category: :character,
|
category: :character,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
visible_descendant = create(:tag,
|
)
|
||||||
name: 'depth_visible_descendant',
|
visible_descendant = Tag.create!(
|
||||||
category: :material)
|
name: 'depth_visible_descendant',
|
||||||
|
category: :material
|
||||||
|
)
|
||||||
TagImplication.create!(parent_tag: root_material, tag: deprecated_middle)
|
TagImplication.create!(parent_tag: root_material, tag: deprecated_middle)
|
||||||
TagImplication.create!(parent_tag: deprecated_middle, tag: visible_descendant)
|
TagImplication.create!(parent_tag: deprecated_middle, tag: visible_descendant)
|
||||||
|
|
||||||
@@ -903,20 +903,24 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'passes through multiple deprecated tags for roots and has_children' do
|
it 'passes through multiple deprecated tags for roots and has_children' do
|
||||||
active_child = create(:tag,
|
active_child = Tag.create!(
|
||||||
name: 'depth_active_child_below_deprecated',
|
name: 'depth_active_child_below_deprecated',
|
||||||
category: :character)
|
category: :character
|
||||||
deprecated_parent = create(:tag,
|
)
|
||||||
name: 'depth_deprecated_parent',
|
deprecated_parent = Tag.create!(
|
||||||
category: :character,
|
name: 'depth_deprecated_parent',
|
||||||
deprecated_at: Time.current)
|
category: :character,
|
||||||
deprecated_grandparent = create(:tag,
|
deprecated_at: Time.current
|
||||||
name: 'depth_deprecated_grandparent',
|
)
|
||||||
category: :material,
|
deprecated_grandparent = Tag.create!(
|
||||||
deprecated_at: Time.current)
|
name: 'depth_deprecated_grandparent',
|
||||||
active_ancestor = create(:tag,
|
category: :material,
|
||||||
name: 'depth_active_ancestor',
|
deprecated_at: Time.current
|
||||||
category: :meme)
|
)
|
||||||
|
active_ancestor = Tag.create!(
|
||||||
|
name: 'depth_active_ancestor',
|
||||||
|
category: :meme
|
||||||
|
)
|
||||||
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
||||||
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
||||||
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_ancestor)
|
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_ancestor)
|
||||||
@@ -941,13 +945,15 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'treats an active tag with only deprecated ancestors as a root' do
|
it 'treats an active tag with only deprecated ancestors as a root' do
|
||||||
active_child = create(:tag,
|
active_child = Tag.create!(
|
||||||
name: 'depth_root_below_deprecated',
|
name: 'depth_root_below_deprecated',
|
||||||
category: :character)
|
category: :character
|
||||||
deprecated_parent = create(:tag,
|
)
|
||||||
name: 'depth_root_deprecated_parent',
|
deprecated_parent = Tag.create!(
|
||||||
category: :material,
|
name: 'depth_root_deprecated_parent',
|
||||||
deprecated_at: Time.current)
|
category: :material,
|
||||||
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
||||||
|
|
||||||
get '/tags/with-depth'
|
get '/tags/with-depth'
|
||||||
@@ -961,14 +967,16 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'terminates when deprecated implications contain a cycle' do
|
it 'terminates when deprecated implications contain a cycle' do
|
||||||
first = create(:tag,
|
first = Tag.create!(
|
||||||
name: 'depth_cycle_first',
|
name: 'depth_cycle_first',
|
||||||
category: :character,
|
category: :character,
|
||||||
deprecated_at: Time.current)
|
deprecated_at: Time.current
|
||||||
second = create(:tag,
|
)
|
||||||
name: 'depth_cycle_second',
|
second = Tag.create!(
|
||||||
category: :material,
|
name: 'depth_cycle_second',
|
||||||
deprecated_at: Time.current)
|
category: :material,
|
||||||
|
deprecated_at: Time.current
|
||||||
|
)
|
||||||
TagImplication.create!(tag: first, parent_tag: root_material)
|
TagImplication.create!(tag: first, parent_tag: root_material)
|
||||||
TagImplication.create!(tag: second, parent_tag: first)
|
TagImplication.create!(tag: second, parent_tag: first)
|
||||||
now = Time.current
|
now = Time.current
|
||||||
@@ -994,19 +1002,19 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
let!(:material_user) { create_member_user! }
|
let!(:material_user) { create_member_user! }
|
||||||
|
|
||||||
let!(:root_tag) do
|
let!(:root_tag) do
|
||||||
create(:tag, name: 'materials_root', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'materials_root'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:child_a_tag) do
|
let!(:child_a_tag) do
|
||||||
create(:tag, name: 'materials_child_a', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'materials_child_a'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:child_b_tag) do
|
let!(:child_b_tag) do
|
||||||
create(:tag, name: 'materials_child_b', category: :character)
|
Tag.create!(tag_name: TagName.create!(name: 'materials_child_b'), category: :character)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:grandchild_tag) do
|
let!(:grandchild_tag) do
|
||||||
create(:tag, name: 'materials_grandchild', category: :material)
|
Tag.create!(tag_name: TagName.create!(name: 'materials_grandchild'), category: :material)
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:root_material) do
|
let!(:root_material) do
|
||||||
@@ -1150,12 +1158,14 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'name, category, aliases, parent tags をまとめて更新できる' do
|
it 'name, category, aliases, parent tags をまとめて更新できる' do
|
||||||
old_parent = create(:tag,
|
old_parent = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'put_old_parent'),
|
tag_name: TagName.create!(name: 'put_old_parent'),
|
||||||
category: :general)
|
category: :general
|
||||||
kept_parent = create(:tag,
|
)
|
||||||
tag_name: create(:tag_name, name: 'put_kept_parent'),
|
kept_parent = Tag.create!(
|
||||||
category: :general)
|
tag_name: TagName.create!(name: 'put_kept_parent'),
|
||||||
|
category: :general
|
||||||
|
)
|
||||||
TagImplication.create!(tag:, parent_tag: old_parent)
|
TagImplication.create!(tag:, parent_tag: old_parent)
|
||||||
TagImplication.create!(tag:, parent_tag: kept_parent)
|
TagImplication.create!(tag:, parent_tag: kept_parent)
|
||||||
|
|
||||||
@@ -1174,17 +1184,14 @@ 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!(language_code: 'ja', name: 'put_alias_a'))
|
expect(TagName.find_by(name: 'put_alias_a').canonical).to eq(tag.tag_name)
|
||||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
expect(TagName.find_by(name: 'put_alias_b').canonical).to eq(tag.tag_name)
|
||||||
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).to have_attributes(
|
expect(old_name_alias.canonical).to eq(tag.tag_name)
|
||||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
|
||||||
|
|
||||||
expect(alias_tn.reload.tag_id).to be_nil
|
expect(alias_tn.reload.canonical).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',
|
||||||
@@ -1219,8 +1226,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
|
|
||||||
tag.reload
|
tag.reload
|
||||||
|
|
||||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_self_test'))
|
expect(TagName.find_by(name: 'put_alias_self_test').canonical).to eq(tag.tag_name)
|
||||||
.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
|
||||||
@@ -1291,14 +1297,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(TagName.find_by!(language_code: 'ja', name: 'put_roundtrip_b'))
|
expect(TagName.find_by!(name: 'put_roundtrip_b').canonical).to eq(tag.tag_name)
|
||||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('put_roundtrip_b', 'unko')
|
||||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
expect(tag.tag_name.aliases.map(&:name)).not_to include('spec_tag')
|
||||||
.to contain_exactly('put_roundtrip_b', 'unko')
|
expect(alias_tn.reload.canonical).to eq(tag.tag_name)
|
||||||
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
|
||||||
|
|
||||||
@@ -1308,12 +1310,14 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'parent tag の snapshot も作成する' do
|
it 'parent tag の snapshot も作成する' do
|
||||||
old_parent = create(:tag,
|
old_parent = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'put_snapshot_old_parent'),
|
tag_name: TagName.create!(name: 'put_snapshot_old_parent'),
|
||||||
category: :general)
|
category: :general
|
||||||
new_parent = create(:tag,
|
)
|
||||||
tag_name: create(:tag_name, name: 'put_snapshot_new_parent'),
|
new_parent = Tag.create!(
|
||||||
category: :general)
|
tag_name: TagName.create!(name: 'put_snapshot_new_parent'),
|
||||||
|
category: :general
|
||||||
|
)
|
||||||
TagImplication.create!(tag:, parent_tag: old_parent)
|
TagImplication.create!(tag:, parent_tag: old_parent)
|
||||||
|
|
||||||
put "/tags/#{ tag.id }", params: {
|
put "/tags/#{ tag.id }", params: {
|
||||||
@@ -1407,7 +1411,7 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新しい正本名へ移す' do
|
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新 canonical へ移す' do
|
||||||
wiki_page =
|
wiki_page =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: tag.tag_name,
|
tag_name: tag.tag_name,
|
||||||
@@ -1441,8 +1445,7 @@ 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!(language_code: 'ja', name: 'put_wiki_roundtrip_b'))
|
expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').canonical).to eq(tag.tag_name)
|
||||||
.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)
|
||||||
|
|
||||||
@@ -1451,16 +1454,16 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it '別名を他 tag から奪った場合、奪はれた側の tag version も作成する' do
|
it '別名を他 tag から奪った場合、奪はれた側の tag version も作成する' do
|
||||||
old_owner = create(:tag,
|
old_owner = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'put_alias_old_owner'),
|
tag_name: TagName.create!(name: 'put_alias_old_owner'),
|
||||||
category: :general)
|
category: :general
|
||||||
stolen_alias = create(:tag_name, :alias,
|
)
|
||||||
name: 'put_stolen_alias',
|
stolen_alias = TagName.create!(
|
||||||
tag: old_owner)
|
name: 'put_stolen_alias',
|
||||||
|
canonical: old_owner.tag_name
|
||||||
|
)
|
||||||
|
|
||||||
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
|
expect(old_owner.tag_name.aliases.map(&:name)).to include('put_stolen_alias')
|
||||||
primary_flg: false).pluck(:name))
|
|
||||||
.to include('put_stolen_alias')
|
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
put "/tags/#{ tag.id }", params: {
|
put "/tags/#{ tag.id }", params: {
|
||||||
@@ -1476,11 +1479,8 @@ 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).to have_attributes(
|
expect(stolen_alias.reload.canonical).to eq(tag.tag_name)
|
||||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
expect(old_owner.reload.tag_name.aliases.map(&:name)).not_to include('put_stolen_alias')
|
||||||
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)
|
||||||
|
|
||||||
@@ -1492,12 +1492,14 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it '別 tag の alias 名を rename で奪へる' do
|
it '別 tag の alias 名を rename で奪へる' do
|
||||||
old_owner = create(:tag,
|
old_owner = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'put_alias_collision_owner'),
|
tag_name: TagName.create!(name: 'put_alias_collision_owner'),
|
||||||
category: :general)
|
category: :general
|
||||||
stolen_alias = create(:tag_name, :alias,
|
)
|
||||||
name: 'put_alias_collision_name',
|
stolen_alias = TagName.create!(
|
||||||
tag: old_owner)
|
name: 'put_alias_collision_name',
|
||||||
|
canonical: old_owner.tag_name
|
||||||
|
)
|
||||||
wiki_page =
|
wiki_page =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: tag.tag_name,
|
tag_name: tag.tag_name,
|
||||||
@@ -1513,20 +1515,16 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
deprecated: '0',
|
deprecated: '0',
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok), response.body
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
tag.reload
|
tag.reload
|
||||||
old_owner.reload
|
old_owner.reload
|
||||||
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).to have_attributes(
|
expect(stolen_alias.canonical_id).to be_nil
|
||||||
tag_id: tag.id, language_code: 'ja', primary_flg: true)
|
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.tag_name)
|
||||||
expect(TagName.find_by!(language_code: 'ja', name: 'spec_tag'))
|
expect(old_owner.tag_name.aliases.map(&:name)).not_to include('put_alias_collision_name')
|
||||||
.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)
|
||||||
|
|
||||||
@@ -1536,9 +1534,10 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'parent_tags に指定すると循環する tag は 422 にする' do
|
it 'parent_tags に指定すると循環する tag は 422 にする' do
|
||||||
child = create(:tag,
|
child = Tag.create!(
|
||||||
tag_name: create(:tag_name, name: 'put_cycle_child'),
|
tag_name: TagName.create!(name: 'put_cycle_child'),
|
||||||
category: :general)
|
category: :general
|
||||||
|
)
|
||||||
|
|
||||||
TagImplication.create!(tag: child, parent_tag: tag)
|
TagImplication.create!(tag: child, parent_tag: tag)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki conflict handling', type: :request do
|
|||||||
it 'returns 409 when base_revision_id is stale' do
|
it 'returns 409 when base_revision_id is stale' do
|
||||||
page =
|
page =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: 'wiki_conflict_request'),
|
tag_name: TagName.create!(name: 'wiki_conflict_request'),
|
||||||
body: 'first',
|
body: 'first',
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki history integrity', type: :request do
|
|||||||
|
|
||||||
def create_wiki_page title:, body: 'body', message: 'init', user: self.user
|
def create_wiki_page title:, body: 'body', message: 'init', user: self.user
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: title),
|
tag_name: TagName.create!(name: title),
|
||||||
body:,
|
body:,
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message:)
|
message:)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
|
|
||||||
let!(:user) { create_member_user! }
|
let!(:user) { create_member_user! }
|
||||||
|
|
||||||
let!(:tn) { create(:tag_name, name: 'spec_wiki_title') }
|
let!(:tn) { TagName.create!(name: 'spec_wiki_title') }
|
||||||
let!(:page) do
|
let!(:page) do
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: tn,
|
tag_name: tn,
|
||||||
@@ -19,10 +19,11 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
message: 'init')
|
message: 'init')
|
||||||
end
|
end
|
||||||
let!(:tag) do
|
let!(:tag) do
|
||||||
create(:tag,
|
Tag.create!(
|
||||||
tag_name: tn,
|
tag_name: tn,
|
||||||
category: :general,
|
category: :general,
|
||||||
deprecated_at: Time.zone.local(2026, 6, 1))
|
deprecated_at: Time.zone.local(2026, 6, 1)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /wiki' do
|
describe 'GET /wiki' do
|
||||||
@@ -207,7 +208,7 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
{ 'X-Transfer-Code' => user.inheritance_code }
|
{ 'X-Transfer-Code' => user.inheritance_code }
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:test_tag_name) { create(:tag_name, name: 'TestPage') }
|
let!(:test_tag_name) { TagName.create!(name: 'TestPage') }
|
||||||
|
|
||||||
let!(:page) do
|
let!(:page) do
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
@@ -274,8 +275,8 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'wiki body だけを変更しても tag version は作成しない' do
|
it 'wiki body だけを変更しても tag version は作成しない' do
|
||||||
linked_tag_name = create(:tag_name, name: 'wiki_body_only_tag')
|
linked_tag_name = TagName.create!(name: 'wiki_body_only_tag')
|
||||||
linked_tag = create(:tag, tag_name: linked_tag_name, category: :general)
|
linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general)
|
||||||
|
|
||||||
TagVersionRecorder.record!(
|
TagVersionRecorder.record!(
|
||||||
tag: linked_tag,
|
tag: linked_tag,
|
||||||
@@ -358,13 +359,13 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
describe 'GET /wiki/search' do
|
describe 'GET /wiki/search' do
|
||||||
before do
|
before do
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: 'spec_wiki_title_2'),
|
tag_name: TagName.create!(name: 'spec_wiki_title_2'),
|
||||||
body: 'search body 2',
|
body: 'search body 2',
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
|
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: 'unrelated_title'),
|
tag_name: TagName.create!(name: 'unrelated_title'),
|
||||||
body: 'unrelated body',
|
body: 'unrelated body',
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
@@ -433,7 +434,7 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
|
|
||||||
it 'returns empty array when page has no revisions and filtered by id' do
|
it 'returns empty array when page has no revisions and filtered by id' do
|
||||||
# 別ページを作って revision 無し
|
# 別ページを作って revision 無し
|
||||||
tn2 = create(:tag_name, name: 'spec_no_rev')
|
tn2 = TagName.create!(name: 'spec_no_rev')
|
||||||
# 異常データ: revision 無し WikiPage を直接作る
|
# 異常データ: revision 無し WikiPage を直接作る
|
||||||
p2 = WikiPage.create!(
|
p2 = WikiPage.create!(
|
||||||
tag_name: tn2,
|
tag_name: tn2,
|
||||||
@@ -513,7 +514,7 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
|
|
||||||
describe 'Wiki::Commit.redirect!' do
|
describe 'Wiki::Commit.redirect!' do
|
||||||
it 'raises because redirect revisions are deprecated' do
|
it 'raises because redirect revisions are deprecated' do
|
||||||
target_tag_name = create(:tag_name, name: 'redirect_deprecated_target')
|
target_tag_name = TagName.create!(name: 'redirect_deprecated_target')
|
||||||
target =
|
target =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: target_tag_name,
|
tag_name: target_tag_name,
|
||||||
@@ -534,8 +535,8 @@ RSpec.describe 'Wiki API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'wiki title を変更すると対応する tag の version を作成する' do
|
it 'wiki title を変更すると対応する tag の version を作成する' do
|
||||||
linked_tag_name = create(:tag_name, name: 'wiki_linked_tag_for_version')
|
linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version')
|
||||||
linked_tag = create(:tag, tag_name: linked_tag_name, category: :general)
|
linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general)
|
||||||
|
|
||||||
linked_page =
|
linked_page =
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ RSpec.describe 'Wiki title collision', type: :request do
|
|||||||
|
|
||||||
def create_wiki_page title:, body:
|
def create_wiki_page title:, body:
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: title),
|
tag_name: TagName.create!(name: title),
|
||||||
body:,
|
body:,
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe MaterialSyncImporter do
|
RSpec.describe MaterialSyncImporter do
|
||||||
let(:user) { create(:user, :member) }
|
let(:user) { create(:user, :member) }
|
||||||
let(:tag) { create(:tag, name: 'sync_tag', category: :material) }
|
let(:tag) { Tag.create!(tag_name: TagName.create!(name: 'sync_tag'), category: :material) }
|
||||||
|
|
||||||
def tempfile_for body
|
def tempfile_for body
|
||||||
Tempfile.new(['material-sync-importer', '.png']).tap do |file|
|
Tempfile.new(['material-sync-importer', '.png']).tap do |file|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ RSpec.describe NicoTagVersionRecorder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'records sorted linked internal names and later link removal' do
|
it 'records sorted linked internal names and later link removal' do
|
||||||
tags = ['z_link', 'a_link'].map { |name| create(:tag, name:, category: :general) }
|
tags = ['z_link', 'a_link'].map { |name| Tag.create!(name:, category: :general) }
|
||||||
tags.each { |tag| NicoTagRelation.create!(nico_tag: external_tag, tag:) }
|
tags.each { |tag| NicoTagRelation.create!(nico_tag: external_tag, tag:) }
|
||||||
|
|
||||||
expect(record(:create).linked_tags).to eq('a_link z_link')
|
expect(record(:create).linked_tags).to eq('a_link z_link')
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe PostBulkCreator do
|
RSpec.describe PostBulkCreator do
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
it 'limits workers to two and keeps failures in their request slots' do
|
it 'limits workers to two and keeps failures in their request slots' do
|
||||||
actor = instance_double(User, id: 123)
|
actor = instance_double(User, id: 123)
|
||||||
allow(User).to receive(:find).with(123) {
|
allow(User).to receive(:find).with(123) {
|
||||||
@@ -11,7 +9,6 @@ RSpec.describe PostBulkCreator do
|
|||||||
mutex = Mutex.new
|
mutex = Mutex.new
|
||||||
active = 0
|
active = 0
|
||||||
maximum_active = 0
|
maximum_active = 0
|
||||||
creators = []
|
|
||||||
|
|
||||||
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
|
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
|
||||||
preflight = instance_double(PostCreatePreflight)
|
preflight = instance_double(PostCreatePreflight)
|
||||||
@@ -31,7 +28,6 @@ RSpec.describe PostBulkCreator do
|
|||||||
end
|
end
|
||||||
allow(PostCreator).to receive(:new) do |attributes:, **|
|
allow(PostCreator).to receive(:new) do |attributes:, **|
|
||||||
creator = instance_double(PostCreator)
|
creator = instance_double(PostCreator)
|
||||||
mutex.synchronize { creators << creator }
|
|
||||||
if attributes[:title] == 'broken'
|
if attributes[:title] == 'broken'
|
||||||
allow(creator).to receive(:create!).and_raise(StandardError, 'broken')
|
allow(creator).to receive(:create!).and_raise(StandardError, 'broken')
|
||||||
else
|
else
|
||||||
@@ -49,12 +45,7 @@ RSpec.describe PostBulkCreator do
|
|||||||
results = described_class.new(
|
results = described_class.new(
|
||||||
actor:,
|
actor:,
|
||||||
posts:,
|
posts:,
|
||||||
thumbnails: { }).run(locale).fetch(:results)
|
thumbnails: { }).run.fetch(:results)
|
||||||
|
|
||||||
expect(creators.length).to eq(posts.length)
|
|
||||||
creators.each do |creator|
|
|
||||||
expect(creator).to have_received(:create!).with(locale).once
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(maximum_active).to eq(2)
|
expect(maximum_active).to eq(2)
|
||||||
expect(results.length).to eq(posts.length)
|
expect(results.length).to eq(posts.length)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe PostCreatePlan do
|
RSpec.describe PostCreatePlan do
|
||||||
def create_tag! name, category
|
def create_tag! name, category
|
||||||
create(:tag, name:, category:)
|
Tag.create!(name:, category:)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@@ -10,55 +10,6 @@ RSpec.describe PostCreatePlan do
|
|||||||
create_tag!('ニジラー情報不詳', :meta)
|
create_tag!('ニジラー情報不詳', :meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an explicit locale' do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
it 'resolves the matching language identity and its parents' do
|
|
||||||
english_name = create(:tag_name, name: 'shared_name',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english = create(:tag, tag_name: english_name, category: :character)
|
|
||||||
japanese = create(:tag, name: 'temporary_japanese', category: :general)
|
|
||||||
# The DB permits this identity; validation has its own contract spec.
|
|
||||||
japanese.tag_name.update_columns(name: 'shared_name')
|
|
||||||
english_parent = create(:tag, name: 'english_parent', category: :material)
|
|
||||||
japanese_parent = create(:tag, name: 'japanese_parent', category: :general)
|
|
||||||
TagImplication.create!(tag: english, parent_tag: english_parent)
|
|
||||||
TagImplication.create!(tag: japanese, parent_tag: japanese_parent)
|
|
||||||
|
|
||||||
plan = described_class.new(attributes: { tags: 'shared_name' }).build!(locale)
|
|
||||||
|
|
||||||
expect(plan[:direct_tag_specs]).to eq(
|
|
||||||
[{ name: 'shared_name', category: :character }])
|
|
||||||
expect(plan[:post_tag_specs]).to include(
|
|
||||||
{ name: english_parent.name, category: :material })
|
|
||||||
expect(plan[:post_tag_specs].pluck(:name)).not_to include(japanese_parent.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'resolves aliases within the requested language' do
|
|
||||||
japanese = create(:tag, name: 'japanese_canonical')
|
|
||||||
english_name = create(:tag_name, name: 'english_canonical',
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
english = create(:tag, tag_name: english_name, category: :character)
|
|
||||||
japanese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: japanese)
|
|
||||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
|
||||||
tag: english,
|
|
||||||
language_code: 'en', script_code: 'Latn')
|
|
||||||
|
|
||||||
expect(japanese_alias.reload).to have_attributes(
|
|
||||||
tag_id: japanese.id,
|
|
||||||
primary_flg: false, language_code: 'ja')
|
|
||||||
expect(english_alias.reload).to have_attributes(
|
|
||||||
tag_id: english.id,
|
|
||||||
primary_flg: false, language_code: 'en')
|
|
||||||
|
|
||||||
plan = described_class.new(attributes: { tags: 'shared_alias' }).build!(locale)
|
|
||||||
|
|
||||||
expect(plan[:direct_tag_specs]).to eq(
|
|
||||||
[{ name: english.name, category: :character }])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'plans direct and existing default tags without persisting records' do
|
it 'plans direct and existing default tags without persisting records' do
|
||||||
counts = [TagName.count, Tag.count]
|
counts = [TagName.count, Tag.count]
|
||||||
|
|
||||||
@@ -67,7 +18,7 @@ RSpec.describe PostCreatePlan do
|
|||||||
url: 'https://example.com/post',
|
url: 'https://example.com/post',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
tags: 'character:new_character',
|
tags: 'character:new_character',
|
||||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
parent_post_ids: '' }).build!
|
||||||
|
|
||||||
expect(plan[:tags]).to eq('new_character')
|
expect(plan[:tags]).to eq('new_character')
|
||||||
expect(plan[:direct_tag_specs]).to eq(
|
expect(plan[:direct_tag_specs]).to eq(
|
||||||
@@ -78,9 +29,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 primary names' do
|
it 'resolves aliases and keeps tag sections separate from canonical names' do
|
||||||
tag = create_tag!('虹夏', :character)
|
canonical = create_tag!('虹夏', :character)
|
||||||
create(:tag_name, :alias, name: 'にじか', tag:)
|
TagName.create!(name: 'にじか', canonical: canonical.tag_name)
|
||||||
create_tag!('動画', :meta)
|
create_tag!('動画', :meta)
|
||||||
|
|
||||||
plan = described_class.new(
|
plan = described_class.new(
|
||||||
@@ -89,7 +40,7 @@ RSpec.describe PostCreatePlan do
|
|||||||
title: 'video',
|
title: 'video',
|
||||||
tags: '動画 にじか[0:10-0:20]',
|
tags: '動画 にじか[0:10-0:20]',
|
||||||
duration: '1:00',
|
duration: '1:00',
|
||||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
parent_post_ids: '' }).build!
|
||||||
|
|
||||||
expect(plan[:tags].split).to include('動画', '虹夏[0:10-0:20]')
|
expect(plan[:tags].split).to include('動画', '虹夏[0:10-0:20]')
|
||||||
expect(plan[:display_tags]).to include(
|
expect(plan[:display_tags]).to include(
|
||||||
@@ -104,7 +55,7 @@ RSpec.describe PostCreatePlan do
|
|||||||
counts = [Tag.count, TagName.count, ExternalTag.count]
|
counts = [Tag.count, TagName.count, ExternalTag.count]
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
described_class.new(attributes: { tags: 'NiCo:reserved' }).build!(Locale.nipponese)
|
described_class.new(attributes: { tags: 'NiCo:reserved' }).build!
|
||||||
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
||||||
expect(error.record.errors[:tags]).to be_present
|
expect(error.record.errors[:tags]).to be_present
|
||||||
}
|
}
|
||||||
@@ -122,7 +73,7 @@ RSpec.describe PostCreatePlan do
|
|||||||
url: 'https://example.com/post',
|
url: 'https://example.com/post',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
tags: long_name,
|
tags: long_name,
|
||||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
parent_post_ids: '' }).build!
|
||||||
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
||||||
expect(error.record.errors[:tags]).not_to be_empty
|
expect(error.record.errors[:tags]).not_to be_empty
|
||||||
}
|
}
|
||||||
@@ -136,7 +87,7 @@ RSpec.describe PostCreatePlan do
|
|||||||
title: 'title',
|
title: 'title',
|
||||||
tags: 'ordinary_tag',
|
tags: 'ordinary_tag',
|
||||||
duration: 'invalid',
|
duration: 'invalid',
|
||||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
parent_post_ids: '' }).build!
|
||||||
|
|
||||||
expect(plan[:duration]).to eq('invalid')
|
expect(plan[:duration]).to eq('invalid')
|
||||||
expect(plan[:video_ms]).to be_nil
|
expect(plan[:video_ms]).to be_nil
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ RSpec.describe PostCreator do
|
|||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
allow(Tag).to receive(:normalise_tags!).and_return({ tags: [], sections: {} })
|
||||||
allow(TagVersioning).to receive(:record_tag_snapshots!)
|
allow(TagVersioning).to receive(:record_tag_snapshots!)
|
||||||
allow(Tag).to receive(:expand_parent_tags).and_return([])
|
allow(Tag).to receive(:expand_parent_tags).and_return([])
|
||||||
allow(PostVersionRecorder).to receive(:record!)
|
allow(PostVersionRecorder).to receive(:record!)
|
||||||
@@ -33,7 +34,7 @@ RSpec.describe PostCreator do
|
|||||||
url: 'https://example.com/post',
|
url: 'https://example.com/post',
|
||||||
thumbnail: real_thumbnail_upload,
|
thumbnail: real_thumbnail_upload,
|
||||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||||
tags: '' }).create!(Locale.nipponese)
|
tags: '' }).create!
|
||||||
|
|
||||||
expect(post.thumbnail).to be_attached
|
expect(post.thumbnail).to be_attached
|
||||||
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
||||||
@@ -53,7 +54,7 @@ RSpec.describe PostCreator do
|
|||||||
title: 'title',
|
title: 'title',
|
||||||
url: 'https://example.com/post',
|
url: 'https://example.com/post',
|
||||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||||
tags: '' }).create!(Locale.nipponese)
|
tags: '' }).create!
|
||||||
|
|
||||||
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
||||||
expect(post.thumbnail).to be_attached
|
expect(post.thumbnail).to be_attached
|
||||||
@@ -71,34 +72,7 @@ RSpec.describe PostCreator do
|
|||||||
tags: '' })
|
tags: '' })
|
||||||
|
|
||||||
post_count = Post.count
|
post_count = Post.count
|
||||||
expect { creator.create!(Locale.nipponese) }
|
expect { creator.create! }.to raise_error(Post::RemoteThumbnailFetchFailed)
|
||||||
.to raise_error(Post::RemoteThumbnailFetchFailed)
|
|
||||||
expect(Post.count).to eq(post_count)
|
expect(Post.count).to eq(post_count)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an explicit locale' do
|
|
||||||
include_context 'English locale'
|
|
||||||
|
|
||||||
[false, true].each do |planned|
|
|
||||||
it "passes locale to tag creation with planned attributes: #{ planned }" do
|
|
||||||
attributes = {
|
|
||||||
title: 'Locale propagation',
|
|
||||||
url: 'https://example.com/locale-post',
|
|
||||||
tags: 'character:locale_character' }
|
|
||||||
if planned
|
|
||||||
specs = [{ name: 'locale_character', category: :character }]
|
|
||||||
attributes.merge!(snapshot_tag_specs: specs, post_tag_specs: specs)
|
|
||||||
end
|
|
||||||
allow(Tag).to receive(:find_or_create_by_tag_name!).and_call_original
|
|
||||||
|
|
||||||
post = described_class.new(actor:, attributes:).create!(locale)
|
|
||||||
|
|
||||||
expect(Tag).to have_received(:find_or_create_by_tag_name!)
|
|
||||||
.with(locale, 'locale_character', category: :character).at_least(:once)
|
|
||||||
tag_name = TagName.find_by!(language_code: 'en', name: 'locale_character')
|
|
||||||
expect(tag_name).to have_attributes(script_code: 'Latn', primary_flg: true)
|
|
||||||
expect(post.tags).to include(tag_name.tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ RSpec.describe PostImportPreviewer do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'applies metadata to automatic fields and recognises metadata tags' do
|
it 'applies metadata to automatic fields and recognises metadata tags' do
|
||||||
create(:tag, name: 'known-tag', category: :general)
|
Tag.create!(name: 'known-tag', category: :general)
|
||||||
allow(PostMetadataFetcher).to receive(:fetch).and_return(
|
allow(PostMetadataFetcher).to receive(:fetch).and_return(
|
||||||
title: 'metadata title',
|
title: 'metadata title',
|
||||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ RSpec.describe Wiki::Commit do
|
|||||||
|
|
||||||
def create_page title:, body: 'initial body'
|
def create_page title:, body: 'initial body'
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
tag_name: create(:tag_name, name: title),
|
tag_name: TagName.create!(name: title),
|
||||||
body:,
|
body:,
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
@@ -16,7 +16,7 @@ RSpec.describe Wiki::Commit do
|
|||||||
it 'creates page, revision, and version with normalised body' do
|
it 'creates page, revision, and version with normalised body' do
|
||||||
expect {
|
expect {
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
tag_name: create(:tag_name, name: 'commit_integrity_create'),
|
tag_name: TagName.create!(name: 'commit_integrity_create'),
|
||||||
body: "a\r\nb\r\n\r\n",
|
body: "a\r\nb\r\n\r\n",
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
@@ -36,7 +36,7 @@ RSpec.describe Wiki::Commit do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects body that becomes blank after normalisation' do
|
it 'rejects body that becomes blank after normalisation' do
|
||||||
tag_name = create(:tag_name, name: 'commit_integrity_blank')
|
tag_name = TagName.create!(name: 'commit_integrity_blank')
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
@@ -76,8 +76,8 @@ RSpec.describe Wiki::Commit do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not record tag_version on body-only wiki update' do
|
it 'does not record tag_version on body-only wiki update' do
|
||||||
tag_name = create(:tag_name, name: 'commit_integrity_linked_tag')
|
tag_name = TagName.create!(name: 'commit_integrity_linked_tag')
|
||||||
tag = create(:tag, tag_name:, category: :general)
|
tag = Tag.create!(tag_name:, category: :general)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ RSpec.describe Wiki::Commit do
|
|||||||
let(:user) { create_member_user! }
|
let(:user) { create_member_user! }
|
||||||
|
|
||||||
def create_page(title: 'commit_spec_page', body: 'initial body')
|
def create_page(title: 'commit_spec_page', body: 'initial body')
|
||||||
tag_name = create(:tag_name, name: title)
|
tag_name = TagName.create!(name: title)
|
||||||
|
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name:,
|
tag_name:,
|
||||||
@@ -80,8 +80,8 @@ RSpec.describe Wiki::Commit do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not record tag version when corresponding tag has no versions' do
|
it 'does not record tag version when corresponding tag has no versions' do
|
||||||
tag_name = create(:tag_name, name: 'commit_linked_tag_without_versions')
|
tag_name = TagName.create!(name: 'commit_linked_tag_without_versions')
|
||||||
tag = create(:tag, tag_name:, category: :general)
|
tag = Tag.create!(tag_name:, category: :general)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
@@ -107,8 +107,8 @@ RSpec.describe Wiki::Commit do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not record tag version when corresponding tag has no versions' do
|
it 'does not record tag version when corresponding tag has no versions' do
|
||||||
tag_name = create(:tag_name, name: 'commit_linked_tag_without_versions')
|
tag_name = TagName.create!(name: 'commit_linked_tag_without_versions')
|
||||||
tag = create(:tag, tag_name:, category: :general)
|
tag = Tag.create!(tag_name:, category: :general)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
described_class.create_content!(
|
described_class.create_content!(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ RSpec.describe WikiVersionRecorder do
|
|||||||
|
|
||||||
def create_page title:, body: 'body'
|
def create_page title:, body: 'body'
|
||||||
Wiki::Commit.create_content!(
|
Wiki::Commit.create_content!(
|
||||||
tag_name: create(:tag_name, name: title),
|
tag_name: TagName.create!(name: title),
|
||||||
body:,
|
body:,
|
||||||
created_by_user: user,
|
created_by_user: user,
|
||||||
message: 'init')
|
message: 'init')
|
||||||
|
|||||||
@@ -172,8 +172,7 @@ RSpec.describe Youtube::Sync do
|
|||||||
Tag.video
|
Tag.video
|
||||||
Tag.no_deerjikist
|
Tag.no_deerjikist
|
||||||
|
|
||||||
deerjikist_tag = Tag.find_or_create_by_tag_name!(
|
deerjikist_tag = Tag.find_or_create_by_tag_name!('テスト投稿者', category: :deerjikist)
|
||||||
Locale.nipponese, 'テスト投稿者', category: :deerjikist)
|
|
||||||
Deerjikist.create!(
|
Deerjikist.create!(
|
||||||
platform: 'youtube',
|
platform: 'youtube',
|
||||||
code: 'UC_MAPPED',
|
code: 'UC_MAPPED',
|
||||||
@@ -227,8 +226,7 @@ RSpec.describe Youtube::Sync do
|
|||||||
)
|
)
|
||||||
PostTag.create!(post:, tag: Tag.no_deerjikist)
|
PostTag.create!(post:, tag: Tag.no_deerjikist)
|
||||||
|
|
||||||
deerjikist_tag = Tag.find_or_create_by_tag_name!(
|
deerjikist_tag = Tag.find_or_create_by_tag_name!('後から判明した投稿者', category: :deerjikist)
|
||||||
Locale.nipponese, '後から判明した投稿者', category: :deerjikist)
|
|
||||||
Deerjikist.create!(
|
Deerjikist.create!(
|
||||||
platform: 'youtube',
|
platform: 'youtube',
|
||||||
code: 'UC_MAPPED_LATER',
|
code: 'UC_MAPPED_LATER',
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
RSpec.shared_context 'English locale' do
|
|
||||||
let!(:locale) do
|
|
||||||
Language.find_or_create_by!(code: 'en') { _1.name = 'English' }
|
|
||||||
Script.find_or_create_by!(code: 'Latn') { _1.name = 'Latin' }
|
|
||||||
unless Locale.exists?(code: 'en')
|
|
||||||
# Reference data only: do not generate names for unrelated existing tags.
|
|
||||||
Locale.insert_all!([
|
|
||||||
{ code: 'en', language_code: 'en', script_code: 'Latn',
|
|
||||||
name: 'English', created_at: Time.current }])
|
|
||||||
end
|
|
||||||
Locale.find('en')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -8,7 +8,7 @@ RSpec.describe 'nico:sync' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_tag!(name, category:)
|
def create_tag!(name, category:)
|
||||||
Tag.find_or_create_by_tag_name!(Locale.nipponese, name, category:)
|
Tag.find_or_create_by_tag_name!(name, category:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_nico_to_tag!(nico_tag, tag)
|
def link_nico_to_tag!(nico_tag, tag)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ RSpec.describe 'post_similarity:calc' do
|
|||||||
|
|
||||||
it 'calculates similarities from active tags only' do
|
it 'calculates similarities from active tags only' do
|
||||||
# 必要最低限のデータ
|
# 必要最低限のデータ
|
||||||
t1 = create(:tag, name: 't1')
|
t1 = Tag.create!(name: "t1")
|
||||||
t2 = create(:tag, name: 't2')
|
t2 = Tag.create!(name: "t2")
|
||||||
t3 = create(:tag, name: 't3')
|
t3 = Tag.create!(name: "t3")
|
||||||
deprecated_tag = create(:tag, name: 'deprecated', deprecated_at: Time.current)
|
deprecated_tag = Tag.create!(name: 'deprecated', deprecated_at: Time.current)
|
||||||
|
|
||||||
p1 = Post.create!(url: "https://example.com/1")
|
p1 = Post.create!(url: "https://example.com/1")
|
||||||
p2 = Post.create!(url: "https://example.com/2")
|
p2 = Post.create!(url: "https://example.com/2")
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ RSpec.describe 'tag_similarity:calc' do
|
|||||||
|
|
||||||
it 'calculates similarities for active tags only' do
|
it 'calculates similarities for active tags only' do
|
||||||
# 必要最低限のデータ
|
# 必要最低限のデータ
|
||||||
t1 = create(:tag, name: 't1')
|
t1 = Tag.create!(name: "t1")
|
||||||
t2 = create(:tag, name: 't2')
|
t2 = Tag.create!(name: "t2")
|
||||||
t3 = create(:tag, name: 't3')
|
t3 = Tag.create!(name: "t3")
|
||||||
deprecated_tag = create(:tag, name: 'deprecated', deprecated_at: Time.current)
|
deprecated_tag = Tag.create!(name: 'deprecated', deprecated_at: Time.current)
|
||||||
|
|
||||||
p1 = Post.create!(url: "https://example.com/1")
|
p1 = Post.create!(url: "https://example.com/1")
|
||||||
p2 = Post.create!(url: "https://example.com/2")
|
p2 = Post.create!(url: "https://example.com/2")
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする