コミットを比較

..
14 コミット
作成者 SHA1 メッセージ 日付
みてるぞ a0f1d0c4c3 #419 2026-09-24 12:48:29 +09:00
みてるぞ b04f3e698d #419 2026-09-24 08:46:22 +09:00
みてるぞ 5d2e85a690 #419 2026-09-24 08:14:17 +09:00
みてるぞ 3b2acb95be #419 2026-09-24 07:16:00 +09:00
みてるぞ 1de00191ea #419 2026-09-24 07:01:36 +09:00
みてるぞ d1bab299ab #419 フロントのテスト追加 2026-09-23 18:22:24 +09:00
みてるぞ 089727c153 #419 2026-09-23 14:40:59 +09:00
みてるぞ 894f9f9571 #419 2026-09-23 10:59:03 +09:00
みてるぞ 2490ed91c4 #419 2026-09-23 09:41:41 +09:00
みてるぞ 2dbd5260ea #419 2026-09-23 02:46:56 +09:00
みてるぞ 2e25c6e0ba #419 2026-09-22 20:33:46 +09:00
みてるぞ a0c2788e00 #419 2026-09-22 20:16:30 +09:00
みてるぞ 31fc32b377 #419 2026-09-22 19:22:16 +09:00
みてるぞ 068355720d #419 2026-09-22 15:13:14 +09:00
55個のファイルの変更、542行の追加、1430行の削除
-2
ファイルの表示
@@ -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
+6 -7
ファイルの表示
@@ -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
+2 -5
ファイルの表示
@@ -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)
+10 -22
ファイルの表示
@@ -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: }
+10 -14
ファイルの表示
@@ -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
-3
ファイルの表示
@@ -1,3 +0,0 @@
class Language < ApplicationRecord
has_many :languages, class_name: 'Locale', foreign_key: :language_code
end
-24
ファイルの表示
@@ -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
-3
ファイルの表示
@@ -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,
+20 -32
ファイルの表示
@@ -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
+19 -26
ファイルの表示
@@ -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
+4 -4
ファイルの表示
@@ -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 } }
+27 -32
ファイルの表示
@@ -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
+18 -20
ファイルの表示
@@ -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
-20
ファイルの表示
@@ -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
-20
ファイルの表示
@@ -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
-25
ファイルの表示
@@ -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
-59
ファイルの表示
@@ -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"
-1
ファイルの表示
@@ -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 -8
ファイルの表示
@@ -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
-5
ファイルの表示
@@ -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
-69
ファイルの表示
@@ -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
+1 -1
ファイルの表示
@@ -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)
+2 -2
ファイルの表示
@@ -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|
+15 -50
ファイルの表示
@@ -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
-94
ファイルの表示
@@ -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
+6 -5
ファイルの表示
@@ -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!(
+6 -5
ファイルの表示
@@ -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')
-236
ファイルの表示
@@ -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
+17 -17
ファイルの表示
@@ -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'))
+4 -4
ファイルの表示
@@ -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)
+56 -55
ファイルの表示
@@ -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(
+2 -2
ファイルの表示
@@ -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!(
+2 -2
ファイルの表示
@@ -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)
+1 -1
ファイルの表示
@@ -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')
+1 -1
ファイルの表示
@@ -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:)
+15 -14
ファイルの表示
@@ -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!(
+1 -1
ファイルの表示
@@ -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')
+1 -1
ファイルの表示
@@ -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|
+1 -1
ファイルの表示
@@ -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 -10
ファイルの表示
@@ -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)
+9 -58
ファイルの表示
@@ -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
+4 -30
ファイルの表示
@@ -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
+1 -1
ファイルの表示
@@ -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',
+5 -5
ファイルの表示
@@ -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!(
+5 -5
ファイルの表示
@@ -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!(
+1 -1
ファイルの表示
@@ -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')
+2 -4
ファイルの表示
@@ -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',
-13
ファイルの表示
@@ -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
+1 -1
ファイルの表示
@@ -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)
+4 -4
ファイルの表示
@@ -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")
+4 -4
ファイルの表示
@@ -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")