アイヌ語追加 (#428) (#433)

Reviewed-on: #433
Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
2026-10-20 20:44:14 +09:00
committed by みてるぞ
コミット 084a090cab
294個のファイルの変更、13978行の追加、3461行の削除
+116
ファイルの表示
@@ -0,0 +1,116 @@
require 'rails_helper'
require 'tempfile'
RSpec.describe CertifiedTagNameTranslations do
let!(:language) { Language.create!(code: 'ain', name: 'ain') }
let!(:script) { Script.create!(code: 'Kana', name: 'Kana') }
let!(:tag) { create(:tag, primary_name: 'source') }
let(:entries) { [{ 'ja' => 'source', 'ain-Kana' => 'レ_ハイタ' }] }
let(:document) do
{ 'format' => described_class::FORMAT,
'source_locale' => 'ja', 'target_locale' => 'ain-Kana', 'entries' => entries }
end
let(:locale) do
Locale.new(code: 'ain-Kana', language_code: 'ain', script_code: 'Kana',
region_code: 392, variants: '', name: 'Ainu')
end
around do |example|
Tempfile.create(['certified-names', '.json']) do |file|
@path = file.path
example.run
end
end
def write_document
File.write(@path, JSON.generate(document))
locale.certified_tag_names_path = @path
end
it 'registers certified names before automatic names and records their provenance' do
other = create(:tag, primary_name: 'レ_ハイタ')
write_document
locale.save!
expect(tag.reload.tag_name('ain')).to have_attributes(
name: 'レ_ハイタ', primary_flg: true, auto_generated: false)
expect(other.reload.tag_name('ain')).to have_attributes(
name: "Tag_##{ other.id }", auto_generated: true)
version = tag.tag_versions.order(:version_no).last
expect(version.created_by_user_id).to be_nil
expect(version.names.fetch('ain')).to include(
'name' => 'レ_ハイタ', 'auto_generated' => false)
end
it 'skips missing sources without rolling back the locale or matching entries' do
entries.unshift({ 'ja' => 'missing', 'ain-Kana' => 'unused' })
write_document
locale.save!
expect(locale).to be_persisted
expect(tag.reload.name('ain')).to eq('レ_ハイタ')
expect(TagName.exists?(language_code: 'ain', name: 'unused')).to be(false)
end
it 'matches source names exactly rather than using a case-insensitive collation' do
entries.first['ja'] = 'SOURCE'
write_document
expect(described_class.load!(@path, locale:)).to eq({})
end
it 'rejects ambiguous primary matches' do
write_document
relation = instance_double(ActiveRecord::Relation)
allow(TagName).to receive(:where).with(
language_code: 'ja', primary_flg: true, name: ['source']).and_return(relation)
allow(relation).to receive(:pluck).with(:name, :tag_id)
.and_return([['source', tag.id], ['source', tag.id + 1]])
expect { described_class.load!(@path, locale:) }
.to raise_error(described_class::Error, /source.*exactly one tag/)
end
it 'rejects duplicate sources and identifies the offending name' do
entries.push({ 'ja' => 'source', 'ain-Kana' => 'another' })
write_document
expect { locale.save! }.to raise_error(described_class::Error, /duplicate source.*source/)
expect(Locale.exists?(code: 'ain-Kana')).to be(false)
end
it 'rejects duplicate targets even when a source is missing' do
entries.push({ 'ja' => 'missing', 'ain-Kana' => 'レ_ハイタ' })
write_document
expect { locale.save! }.to raise_error(described_class::Error, /duplicate target.*レ_ハイタ/)
expect(Locale.exists?(code: 'ain-Kana')).to be(false)
end
it 'rolls back names and histories if a later certified name is invalid' do
create(:tag, primary_name: 'second')
entries.push({ 'ja' => 'second', 'ain-Kana' => 'nico:invalid' })
write_document
original_versions = TagVersion.order(:id).map(&:attributes)
expect { locale.save! }.to raise_error(described_class::Error, /nico:invalid/)
expect(Locale.exists?(code: 'ain-Kana')).to be(false)
expect(TagName.where(language_code: 'ain')).to be_empty
expect(TagVersion.order(:id).map(&:attributes)).to eq(original_versions)
end
it 'does not add names or versions on repeated completion' do
write_document
locale.save!
original_names = TagName.order(:id).map(&:attributes)
original_versions = TagVersion.order(:id).map(&:attributes)
TagNameCompletion.for_locale!(locale)
expect(TagName.order(:id).map(&:attributes)).to eq(original_names)
expect(TagVersion.order(:id).map(&:attributes)).to eq(original_versions)
end
end
+2 -3
ファイルの表示
@@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe PostBulkCreator do
include_context 'English locale'
include_context 'Ainu locale'
it 'limits workers to two and keeps failures in their request slots' do
locale = Locale.nipponese
@@ -67,8 +67,7 @@ RSpec.describe PostBulkCreator do
expect(results[3].dig(:post, :id)).to eq(4)
end
it 'passes a future English locale to preflight and creation in every worker' do
pending '日本語以外の Locale 有効化は別課題で実装する'
it 'passes a future Ainu locale to preflight and creation in every worker' do
locale
actor = instance_double(User, id: 123)
allow(User).to receive(:find).with(123).and_return(actor)
+21 -23
ファイルの表示
@@ -11,23 +11,22 @@ RSpec.describe PostCreatePlan do
end
context 'with an explicit locale' do
include_context 'English locale'
include_context 'Ainu locale'
it 'resolves the matching language identity and its parents' do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
english_name = create(:tag_name, name: 'shared_name',
language_code: 'en', script_code: 'Latn')
english = create(:tag, primary_tag_name: english_name, category: :character)
ainu_name = create(:tag_name, name: 'shared_name',
language_code: 'ain', script_code: 'Kana')
ainu = create(:tag, primary_tag_name: ainu_name, category: :character)
primary_tag_name_for(ainu, 'ja').update!(name: 'ainu_in_japanese', auto_generated: false)
nipponese = create(:tag, primary_name: 'temporary_nipponese', category: :general)
# The DB permits this identity; validation has its own contract spec.
primary_tag_name_for(nipponese, 'ja').update_columns(name: 'shared_name')
english_parent_name = create(:tag_name, name: 'english_parent',
language_code: 'en', script_code: 'Latn')
english_parent = create(:tag, primary_tag_name: english_parent_name,
primary_tag_name_for(nipponese, 'ja').update!(name: 'shared_name')
ainu_parent_name = create(:tag_name, name: 'ainu_parent',
language_code: 'ain', script_code: 'Kana')
ainu_parent = create(:tag, primary_tag_name: ainu_parent_name,
category: :material)
nipponese_parent = create(:tag, primary_name: 'nipponese_parent', category: :general)
TagImplication.create!(tag: english, parent_tag: english_parent)
TagImplication.create!(tag: ainu, parent_tag: ainu_parent)
TagImplication.create!(tag: nipponese, parent_tag: nipponese_parent)
plan = described_class.new(attributes: { tags: 'shared_name' }).build!(locale)
@@ -35,35 +34,34 @@ RSpec.describe PostCreatePlan do
expect(plan[:direct_tag_specs]).to eq(
[{ name: 'shared_name', category: :character }])
expect(plan[:post_tag_specs]).to include(
{ name: primary_tag_name_for(english_parent, 'en').name, category: :material })
{ name: primary_tag_name_for(ainu_parent, 'ain').name, category: :material })
expect(plan[:post_tag_specs].pluck(:name))
.not_to include(primary_tag_name_for(nipponese_parent, 'ja').name)
end
it 'resolves aliases within the requested language' do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
nipponese = create(:tag, primary_name: 'nipponese_canonical')
english_name = create(:tag_name, name: 'english_canonical',
language_code: 'en', script_code: 'Latn')
english = create(:tag, primary_tag_name: english_name, category: :character)
ainu_name = create(:tag_name, name: 'ainu_canonical',
language_code: 'ain', script_code: 'Kana')
ainu = create(:tag, primary_tag_name: ainu_name, category: :character)
nipponese_alias = create(:tag_name, :alias, name: 'shared_alias',
tag: nipponese)
english_alias = create(:tag_name, :alias, name: 'shared_alias',
tag: english,
language_code: 'en', script_code: 'Latn')
ainu_alias = create(:tag_name, :alias, name: 'shared_alias',
tag: ainu,
language_code: 'ain', script_code: 'Kana')
expect(nipponese_alias.reload).to have_attributes(
tag_id: nipponese.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(ainu_alias.reload).to have_attributes(
tag_id: ainu.id,
primary_flg: false, language_code: 'ain')
plan = described_class.new(attributes: { tags: 'shared_alias' }).build!(locale)
expect(plan[:direct_tag_specs]).to eq(
[{ name: english_name.name, category: :character }])
[{ name: ainu_name.name, category: :character }])
end
end
+5 -5
ファイルの表示
@@ -77,11 +77,10 @@ RSpec.describe PostCreator do
end
context 'with an explicit locale' do
include_context 'English locale'
include_context 'Ainu locale'
[false, true].each do |planned|
it "passes locale to tag creation with planned attributes: #{ planned }" do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
attributes = {
title: 'Locale propagation',
@@ -96,9 +95,10 @@ RSpec.describe PostCreator do
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)
.with(locale, 'locale_character', category: :character,
created_by_user: actor).at_least(:once)
tag_name = TagName.find_by!(language_code: 'ain', name: 'locale_character')
expect(tag_name).to have_attributes(script_code: 'Kana', primary_flg: true)
expect(post.tags).to include(tag_name.tag)
end
end
+34
ファイルの表示
@@ -141,6 +141,40 @@ RSpec.describe 'Shared resource IDs across MySQL connections', type: :model do
result(first)
end
[false, true].each do |inner_rollback|
[false, true].each do |outer_rollback|
it "keeps savepoint=#{ inner_rollback } lock through outer rollback=#{ outer_rollback }" do
connection = ApplicationRecord.connection
database = connection.pool.db_config.database
lock_name = "btrc:ids:#{ Digest::SHA256.hexdigest(database)[0, 16] }:tags"
quoted_lock = connection.quote(lock_name)
owner = connection.select_value('SELECT CONNECTION_ID()').to_i
connection.transaction do
connection.transaction(requires_new: true) do
create_resource(:external)
raise ActiveRecord::Rollback if inner_rollback
end
expect(connection.select_value("SELECT IS_USED_LOCK(#{ quoted_lock })").to_i)
.to eq(owner)
contender = worker do |other|
other.select_value("SELECT GET_LOCK(#{ quoted_lock }, 0)").to_i
end
expect(result(contender)).to eq(0)
SharedResourceIdAllocator.lock_space!(ExternalTag, 'tags')
raise ActiveRecord::Rollback if outer_rollback
end
expect(connection.select_value("SELECT IS_FREE_LOCK(#{ quoted_lock })").to_i)
.to eq(1)
expect(ExternalTag.exists?(name: "#{ @prefix }_external"))
.to eq(!inner_rollback && !outer_rollback)
end
end
end
it 'raises on a real lock acquisition failure without inserting a record' do
first, (_id, _connection_id, lock_name) = hold_creation(:external, rollback: true)
stub_const('SharedResourceIdAllocator::LOCK_TIMEOUT', 0)
+30 -12
ファイルの表示
@@ -101,7 +101,7 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
let(:record) { build(:external_tag) }
let(:connection) { double('connection') }
let(:transaction) { double('transaction', open?: true) }
let(:callbacks) { { } }
let(:callbacks) { [] }
let(:sql) { [] }
before do
@@ -128,19 +128,24 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
end
end
allow(ExternalTag).to receive(:connection).and_return(connection)
allow(ExternalTag).to receive(:current_transaction).and_return(transaction)
allow(connection).to receive(:current_transaction).and_return(transaction)
allow(connection).to receive(:add_transaction_record) { callbacks << _1 }
allow(connection).to receive(:disconnect!)
allow(ExternalTag).to receive(:uncached).and_yield
[:after_commit, :after_rollback].each do |event|
allow(transaction).to receive(event) { |&callback| callbacks[event] = callback }
end
end
[:after_commit, :after_rollback].each do |event|
def finish_transaction event
allow(transaction).to receive(:open?).and_return(false)
callbacks.shift.public_send(event)
allow(transaction).to receive(:open?).and_return(true)
end
[:committed!, :rolledback!].each do |event|
it "holds the shared lock until #{ event }" do
described_class.assign!(record)
expect(record.id).to eq(9)
expect(sql.grep(/RELEASE_LOCK/)).to be_empty
callbacks.fetch(event).call
finish_transaction(event)
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
end
end
@@ -155,7 +160,7 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
it 'releases a successfully acquired lock when allocation fails and rolls back' do
allow(connection).to receive(:select_all).and_raise('query failed')
expect { described_class.assign!(record) }.to raise_error(RuntimeError, 'query failed')
callbacks.fetch(:after_rollback).call
finish_transaction(:rolledback!)
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
end
@@ -164,18 +169,17 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
described_class.assign!(record)
expect(record.id).to eq(50)
expect(sql.grep(/FOR UPDATE/)).to be_empty
callbacks.fetch(:after_commit).call
finish_transaction(:committed!)
end
it 'uses the same named lock within each space and different locks between spaces' do
locks = [Tag, ExternalTag, TagDescription, TagGroup, TagDescriptionVersion, TagGroupVersion].map do |model|
allow(model).to receive(:connection).and_return(connection)
allow(model).to receive(:current_transaction).and_return(transaction)
allow(model).to receive(:uncached).and_yield
record = model.new(id: 50)
described_class.assign!(record)
statement = sql.grep(/GET_LOCK/).last
callbacks.fetch(:after_commit).call
finish_transaction(:committed!)
statement
end
expect(locks[0]).to eq(locks[1])
@@ -187,7 +191,21 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
it 'reports lock release failures' do
described_class.assign!(record)
allow(connection).to receive(:select_value).with(/RELEASE_LOCK/).and_return(nil)
expect { callbacks.fetch(:after_commit).call }
expect { finish_transaction(:committed!) }
.to raise_error(ActiveRecord::ActiveRecordError, /lock release failed/)
expect(connection).to have_received(:disconnect!)
end
it 'keeps the lock and transfers cleanup when a savepoint rolls back' do
described_class.assign!(record)
callbacks.shift.rolledback!
expect(callbacks.size).to eq(1)
expect(sql.grep(/RELEASE_LOCK/)).to be_empty
described_class.assign!(record)
expect(sql.grep(/GET_LOCK/).size).to eq(1)
finish_transaction(:committed!)
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
end
end
+127
ファイルの表示
@@ -0,0 +1,127 @@
require 'rails_helper'
RSpec.describe TagNameCompletion do
let(:author) { create(:user, :member) }
let!(:language) { Language.create!(code: 'ain', name: 'ain') }
let!(:script) { Script.create!(code: 'Kana', name: 'Kana') }
let(:locale) do
Locale.create!(code: 'ain-Kana', language_code: 'ain', script_code: 'Kana',
region_code: 392, variants: '', name: 'ain-Kana')
end
it 'generates a phonetic primary with the required identity and provenance' do
tag = create(:tag, primary_name: '漢字あカ')
locale
expect(tag.reload.tag_name('ain')).to have_attributes(
name: 'カンチ_ア_カ', language_code: 'ain', script_code: 'Kana',
primary_flg: true, auto_generated: true, tag_id: tag.id)
end
it 'uses the exact fallback when the input cannot be converted' do
tag = create(:tag, primary_name: '😀')
locale
expect(tag.reload.name('ain')).to eq("Tag_##{ tag.id }")
end
it 'uses the exact fallback when a generated name collides with a human name' do
other = create(:tag, primary_name: 'other')
create(:tag_name, tag: other, name: 'AbC', language_code: 'ain', script_code: 'Kana')
tag = create(:tag, primary_name: 'AbC')
locale
expect(tag.reload.name('ain')).to eq("Tag_##{ tag.id }")
expect(other.reload.name('ain')).to eq('AbC')
end
it 'uses the exact fallback for a transcription that fails name validation' do
tag = create(:tag, primary_name: 'base')
source = TagName.new(name: 'nico:invalid', language_code: 'ja')
expect(TagName.generate_name(locale, tag, source)).to eq("Tag_##{ tag.id }")
end
it 'preserves human confirmation and is idempotent across repeated completion' do
locale
tag = Tag.find_or_create_by_tag_name!(Locale.nipponese, 'あいぬ', category: :general)
primary = tag.tag_name('ain')
primary.update!(name: 'human_confirmed', auto_generated: false)
snapshot = tag.tag_names.order(:id).map(&:attributes)
2.times { described_class.for_tag!(tag) }
expect(tag.tag_names.order(:id).map(&:attributes)).to eq(snapshot)
end
it 'creates one primary per active language through the production creation path' do
locale
tag = Tag.find_or_create_by_tag_name!(Locale.nipponese, 'アイヌ', category: :general)
expect(tag.tag_names.where(primary_flg: true).pluck(:language_code, :name))
.to contain_exactly(['ja', 'アイヌ'], ['ain', 'アイヌ'])
expect(Tag.find_or_create_by_tag_name!(locale, 'アイヌ', category: :general)).to eq(tag)
end
['ja', 'ain'].each do |language|
it "records one authored create with all names for a #{ language } origin" do
ainu_locale = locale
origin = language == 'ja' ? Locale.nipponese : ainu_locale
tag = nil
expect {
tag = Tag.find_or_create_by_tag_name!(origin, 'アイヌ', category: :general,
created_by_user: author)
}.to change(TagVersion, :count).by(1)
version = tag.tag_versions.sole
expect(version).to have_attributes(
event_type: 'create', version_no: 1, created_by_user_id: author.id)
expect(tag.reload.version_no).to eq(1)
expect(version.names).to eq(
'ja' => { 'name' => 'アイヌ', 'script_code' => 'Jpan',
'auto_generated' => language != 'ja', 'aliases' => [] },
'ain' => { 'name' => 'アイヌ', 'script_code' => 'Kana',
'auto_generated' => language != 'ain', 'aliases' => [] })
expect {
described_class.for_tag!(tag, created_by_user: author)
found = Tag.find_or_create_by_tag_name!(origin, 'アイヌ', category: :general,
created_by_user: author)
expect(found).to eq(tag)
TagVersioning.record_tag_snapshot!(tag, created_by_user: author)
}.not_to change(TagVersion, :count)
expect(version.reload.created_by_user_id).to eq(author.id)
end
end
it 'records the author even when no additional language needs completion' do
tag = build(:tag, primary_name: 'single_language_creation')
described_class.for_tag!(tag, created_by_user: author)
expect(tag).to be_persisted
expect(tag.tag_versions.sole).to have_attributes(
event_type: 'create', version_no: 1, created_by_user_id: author.id)
expect(tag.tag_versions.sole.names.keys).to eq(['ja'])
end
it 'rolls back a new tag and all names when its create history fails' do
locale
author
before_counts = [Tag.count, TagName.count, TagVersion.count]
allow(TagVersioning).to receive(:record!).and_raise('history failed')
expect {
Tag.find_or_create_by_tag_name!(Locale.nipponese, 'atomic_new_history',
category: :general, created_by_user: author)
}.to raise_error('history failed')
expect([Tag.count, TagName.count, TagVersion.count]).to eq(before_counts)
expect(TagName.where(name: 'atomic_new_history')).to be_empty
end
end
+37
ファイルの表示
@@ -0,0 +1,37 @@
require 'rails_helper'
RSpec.describe TagNameTranscription do
{
'AbCdXYZ' => 'AbCdXYZ',
'あいぬ' => 'アイヌ',
'アイヌ' => 'アイヌ',
'日本語' => 'ニチホンコ',
'漢字' => 'カンチ',
'漢あカ' => 'カン_ア_カ',
'漢字あいうカナ' => 'カンチ_アイウ_カナ',
'Ab漢あカZ' => 'Abカン_ア_カZ',
'りゃリュリョ' => 'リヤ_リユリヨ',
'キャット' => 'キヤット',
'コーヒー' => 'コヒ',
'コウコオケイケエカア' => 'コウコオケイケエカア',
'ガギグゲゴダヂヅデド' => 'カキクケコタチチュテト',
'バビブベボパピプペポ' => 'パピプペポパピプペポ',
'ザジズゼゾ' => 'チャチチュチェチョ',
'ジャジュジョ' => 'チャチュチョ',
'アㇰアㇱアッアンアㇺアㇷ゚' => 'アㇰアㇱアッアンアㇺアㇷ゚',
'アㇻイㇼウㇽエㇾオㇿ' => 'アㇻイㇼウㇽエㇾオㇿ',
'漢Aあ' => 'カンAア' }.each do |source, expected|
it "transcribes #{ source } as #{ expected }" do
expect(described_class.ainu(source)).to eq(expected)
end
end
{ '😀' => '', 'ヴ' => '', 'ティ' => 'テ', 'ㇵ' => '',
'魑' => '', '𠮷' => '', 'ー' => '', '々' => '',
'ア😀😀イ' => 'ア_イ', '😀アイ😀' => 'アイ',
'_😀_' => '__', 'ア_😀_イ' => 'ア___イ', '漢魑字' => 'カン_チ' }.each do |source, expected|
it "preserves convertible portions of #{ source }" do
expect(described_class.ainu(source)).to eq(expected)
end
end
end
+20 -23
ファイルの表示
@@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe TagVersionRecorder do
include_context 'English locale'
include_context 'Ainu locale'
let(:user) { create_member_user! }
let!(:tag) { create(:tag, primary_name: '日本語名') }
@@ -17,11 +17,11 @@ RSpec.describe TagVersionRecorder do
{ 'name' => 'alias_z', 'script_code' => 'Jpan' }] }
end
def prepare_english_names!
def prepare_ainu_names!
locale
primary_tag_name_for(tag, 'en').update!(name: 'English name', auto_generated: false)
create(:tag_name, :alias, tag:, language_code: 'en', script_code: 'Latn',
name: 'English alias')
primary_tag_name_for(tag, 'ain').update!(name: 'Ainu name', auto_generated: false)
create(:tag_name, :alias, tag:, language_code: 'ain', script_code: 'Kana',
name: 'Ainu alias')
end
it 'snapshots the Nipponese primary and sorted aliases in grouped names' do
@@ -62,40 +62,37 @@ RSpec.describe TagVersionRecorder do
end
it 'snapshots future languages together without a request locale argument' do
pending '日本語以外の Locale 有効化は別課題で実装する'
prepare_english_names!
prepare_ainu_names!
version = described_class.record!(tag:, event_type: :create, created_by_user: user)
version = described_class.record!(tag:, event_type: :update, created_by_user: user)
expect(version.names).to eq(
'ja' => nipponese_snapshot,
'en' => { 'name' => 'English name', 'script_code' => 'Latn',
'ain' => { 'name' => 'Ainu name', 'script_code' => 'Kana',
'auto_generated' => false,
'aliases' => [{ 'name' => 'English alias', 'script_code' => 'Latn' }] })
'aliases' => [{ 'name' => 'Ainu alias', 'script_code' => 'Kana' }] })
end
it 'creates a version for an English-only change and preserves prior names' do
pending '日本語以外の Locale 有効化は別課題で実装する'
prepare_english_names!
original = described_class.record!(tag:, event_type: :create, created_by_user: user)
primary_tag_name_for(tag, 'en').update!(name: 'Renamed')
it 'creates a version for an Ainu-only change and preserves prior names' do
prepare_ainu_names!
original = described_class.record!(tag:, event_type: :update, created_by_user: user)
primary_tag_name_for(tag, 'ain').update!(name: 'Renamed')
updated = described_class.record!(tag:, event_type: :update, created_by_user: user)
expect(updated.version_no).to eq(2)
expect(original.reload.names.fetch('en')).to include('name' => 'English name')
expect(updated.names.fetch('en')).to include('name' => 'Renamed')
expect(updated.version_no).to eq(original.version_no + 1)
expect(original.reload.names.fetch('ain')).to include('name' => 'Ainu name')
expect(updated.names.fetch('ain')).to include('name' => 'Renamed')
expect(updated.names.fetch('ja')).to eq(nipponese_snapshot)
end
it 'does not create a version for an unchanged multilingual snapshot' do
pending '日本語以外の Locale 有効化は別課題で実装する'
prepare_english_names!
described_class.record!(tag:, event_type: :create, created_by_user: user)
prepare_ainu_names!
original = described_class.record!(tag:, event_type: :update, created_by_user: user)
unchanged = described_class.record!(tag:, event_type: :update, created_by_user: user)
expect(unchanged.version_no).to eq(1)
expect(tag.reload.tag_versions.count).to eq(1)
expect(unchanged.version_no).to eq(original.version_no)
expect(tag.reload.tag_versions.count).to eq(original.version_no)
end
end