このコミットが含まれているのは:
2026-10-15 23:49:38 +09:00
コミット 6ba87beef6
8個のファイルの変更、176行の追加、134行の削除
+12 -7
ファイルの表示
@@ -1,14 +1,18 @@
class TagDescriptionsController < ApplicationController class TagDescriptionsController < ApplicationController
def show def show
description = TagDescription.find_by(tag_id: params[:id], language_code: 'ja') locale = resolve_locale!
description = TagDescription.find_by(tag_id: params[:id], language_code: locale.language_code)
return head :not_found unless description return head :not_found unless description
render json: representation(description) render json: representation(description)
end end
def show_by_title def show_by_title
tag_name = TagName.find_by(language_code: 'ja', name: params[:name].to_s.strip) locale = resolve_locale!
description = tag_name&.tag && TagDescription.find_by(tag: tag_name.tag, language_code: 'ja') tag_name = TagName.find_by(
language_code: locale.language_code, name: params[:name].to_s.strip)
description = tag_name&.tag && TagDescription.find_by(
tag: tag_name.tag, language_code: locale.language_code)
return head :not_found unless description return head :not_found unless description
render json: representation(description) render json: representation(description)
@@ -21,18 +25,19 @@ class TagDescriptionsController < ApplicationController
body = params[:body].to_s body = params[:body].to_s
return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank? return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank?
locale = resolve_locale!
tag = Tag.find_by(id: params[:id]) tag = Tag.find_by(id: params[:id])
return head :not_found unless tag return head :not_found unless tag
description = TagDescription.find_by(tag:, language_code: 'ja') description = TagDescription.find_by(tag:, language_code: locale.language_code)
if description if description
TagDescriptionOperation.update!(tag:, body:, updated_by_user: current_user) TagDescriptionOperation.update!(locale, tag:, body:, updated_by_user: current_user)
description.reload description.reload
render json: representation(description) render json: representation(description)
else else
TagDescriptionOperation.create!(tag:, body:, created_by_user: current_user) TagDescriptionOperation.create!(locale, tag:, body:, created_by_user: current_user)
description = TagDescription.find_by!(tag:, language_code: 'ja') description = TagDescription.find_by!(tag:, language_code: locale.language_code)
render json: representation(description), status: :created render json: representation(description), status: :created
end end
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
+10 -6
ファイルの表示
@@ -1,33 +1,37 @@
class TagGroupsController < ApplicationController class TagGroupsController < ApplicationController
def index def index
locale = resolve_locale!
groups = TagGroup.includes(:created_by_user, :updated_by_user).order(:id) groups = TagGroup.includes(:created_by_user, :updated_by_user).order(:id)
render json: TagGroupRepr.many(groups.to_a) render json: TagGroupRepr.many(locale, groups.to_a)
end end
def show def show
locale = resolve_locale!
group = TagGroup.includes(:created_by_user, :updated_by_user).find_by(id: params[:id]) group = TagGroup.includes(:created_by_user, :updated_by_user).find_by(id: params[:id])
return head :not_found unless group return head :not_found unless group
render json: TagGroupRepr.one(group) render json: TagGroupRepr.one(locale, group)
end end
def create def create
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?
group = TagGroupOperation.create!(**write_attributes, actor: current_user) locale = resolve_locale!
render json: TagGroupRepr.one(group), status: :created group = TagGroupOperation.create!(locale, **write_attributes, actor: current_user)
render json: TagGroupRepr.one(locale, group), status: :created
end end
def update def update
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!
group = TagGroup.find_by(id: params[:id]) group = TagGroup.find_by(id: params[:id])
return head :not_found unless group return head :not_found unless group
TagGroupOperation.update!(group:, **write_attributes, actor: current_user) TagGroupOperation.update!(locale, group:, **write_attributes, actor: current_user)
render json: TagGroupRepr.one(group.reload) render json: TagGroupRepr.one(locale, group.reload)
end end
private private
+74 -53
ファイルの表示
@@ -4,9 +4,12 @@ class WikiPagesController < ApplicationController
rescue_from TagDescriptionOperation::Conflict, with: :render_wiki_conflict rescue_from TagDescriptionOperation::Conflict, with: :render_wiki_conflict
def index def index
locale = resolve_locale!
title = params[:title].to_s.strip title = params[:title].to_s.strip
descriptions = descriptions =
TagDescription.where(language_code: 'ja') TagDescription
.where(language_code: locale.language_code)
.includes(tag: :tag_names) .includes(tag: :tag_names)
descriptions = descriptions.order(updated_at: :desc, id: :asc) descriptions = descriptions.order(updated_at: :desc, id: :asc)
@@ -15,13 +18,14 @@ class WikiPagesController < ApplicationController
pattern = "%#{ ApplicationRecord.sanitize_sql_like(title) }%" pattern = "%#{ ApplicationRecord.sanitize_sql_like(title) }%"
descriptions = descriptions =
descriptions.joins(tag: :tag_names) descriptions.joins(tag: :tag_names)
.where(tag_names: { language_code: 'ja', primary_flg: true }) .where(tag_names: { language_code: locale.language_code, primary_flg: true })
.where('tag_names.name LIKE ?', pattern).limit(20) .where('tag_names.name LIKE ?', pattern).limit(20)
tag_groups = tag_groups.where( tag_groups = tag_groups.where(
'CONCAT(?, name) LIKE ?', TagGroupRepr::PREFIX, pattern).limit(20) 'CONCAT(?, name) LIKE ? OR CONCAT(?, name) LIKE ?',
TagGroupRepr::PREFIX, pattern, 'tag_group:', pattern).limit(20)
end end
json = descriptions.map { |description| WikiPageRepr.tag_description(description) } json = descriptions.map { |description| WikiPageRepr.tag_description(locale, description) }
json.concat(tag_groups.map { WikiPageRepr.tag_group(_1) }) json.concat(tag_groups.map { WikiPageRepr.tag_group(_1) })
json.sort_by! do |row| json.sort_by! do |row|
[-row['updated_at'].to_f, row.fetch('resource_type'), row.fetch('id')] [-row['updated_at'].to_f, row.fetch('resource_type'), row.fetch('id')]
@@ -31,47 +35,53 @@ class WikiPagesController < ApplicationController
end end
def show def show
tag_description = TagDescription.find_by(id: params[:id]) locale = resolve_locale!
tag_description = TagDescription.find_by(id: params[:id], language_code: locale.language_code)
return render_wiki_page_by_tag_description tag_description if tag_description return render_wiki_page_by_tag_description locale, tag_description if tag_description
tag_group = TagGroup.find_by(id: params[:id]) tag_group = TagGroup.find_by(id: params[:id])
return render_tag_group tag_group if tag_group return render_tag_group locale, tag_group if tag_group
head :not_found head :not_found
end end
def show_by_title def show_by_title
locale = resolve_locale!
title = params[:title].to_s.strip title = params[:title].to_s.strip
if title.start_with?(TagGroupRepr::PREFIX) if (name = TagGroupRepr.name_from_title(title))
name = title.delete_prefix(TagGroupRepr::PREFIX)
tag_group = TagGroup.find_by(name:) tag_group = TagGroup.find_by(name:)
return render_tag_group tag_group if tag_group return render_tag_group locale, tag_group if tag_group
return head :not_found return head :not_found
end end
tag_description = tag_description_by_title(title) tag_description = tag_description_by_title(locale, title)
return render_wiki_page_by_tag_description tag_description if tag_description return render_wiki_page_by_tag_description locale, tag_description if tag_description
head :not_found head :not_found
end end
def exists def exists
return head :no_content if TagDescription.exists?(id: params[:id]) locale = resolve_locale!
if TagDescription.exists?(id: params[:id], language_code: locale.language_code)
return head :no_content
end
return head :no_content if TagGroup.exists?(id: params[:id]) return head :no_content if TagGroup.exists?(id: params[:id])
head :not_found head :not_found
end end
def exists_by_title def exists_by_title
locale = resolve_locale!
title = params[:title].to_s.strip title = params[:title].to_s.strip
if title.start_with?(TagGroupRepr::PREFIX) if (name = TagGroupRepr.name_from_title(title))
exists = TagGroup.exists?(name: title.delete_prefix(TagGroupRepr::PREFIX)) exists = TagGroup.exists?(name:)
return head exists ? :no_content : :not_found return head exists ? :no_content : :not_found
end end
return head :no_content if tag_description_by_title(title)
return head :no_content if tag_description_by_title(locale, title)
head :not_found head :not_found
end end
@@ -80,15 +90,17 @@ class WikiPagesController < ApplicationController
id = params[:id] id = params[:id]
return render_bad_request('id は必須です.') if id.blank? return render_bad_request('id は必須です.') if id.blank?
locale = resolve_locale!
from = params[:from].presence from = params[:from].presence
to = params[:to].presence to = params[:to].presence
description = TagDescription.find_by(id:) description = TagDescription.find_by(id:, language_code: locale.language_code)
if description if description
versions = description_versions(description) versions = description_versions(description)
from_rev = from && versions.find(from) from_rev = from && versions.find(from)
to_rev = to ? versions.find(to) : versions.find_by!(version_no: description.version_no) to_rev = to ? versions.find(to) : versions.find_by!(version_no: description.version_no)
resource = WikiPageRepr.tag_description(description) resource = WikiPageRepr.tag_description(locale, description)
else else
tag_group = TagGroup.find_by(id:) tag_group = TagGroup.find_by(id:)
if tag_group if tag_group
@@ -96,8 +108,8 @@ class WikiPagesController < ApplicationController
to_rev = to ? group_version(tag_group, to) : current_group_version(tag_group) to_rev = to ? group_version(tag_group, to) : current_group_version(tag_group)
return head :not_found if (from && !from_rev) || !to_rev return head :not_found if (from && !from_rev) || !to_rev
from_body = from_rev && render_group_version(from_rev) from_body = from_rev && render_group_version(locale, from_rev)
to_body = render_group_version(to_rev) to_body = render_group_version(locale, to_rev)
resource = { 'id' => tag_group.id, resource = { 'id' => tag_group.id,
'title' => "#{ TagGroupRepr::PREFIX }#{ tag_group.name }", 'title' => "#{ TagGroupRepr::PREFIX }#{ tag_group.name }",
'deprecated_at' => nil } 'deprecated_at' => nil }
@@ -131,14 +143,15 @@ class WikiPagesController < ApplicationController
return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank? return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank?
return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank? return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank?
if title.start_with?(TagGroupRepr::PREFIX) locale = resolve_locale!
name = title.delete_prefix(TagGroupRepr::PREFIX)
parsed = TagGroupMarkdownParser.parse(body:, group: nil) if (name = TagGroupRepr.name_from_title(title))
parsed = TagGroupMarkdownParser.parse(locale, body:, group: nil)
group = TagGroupOperation.create!( group = TagGroupOperation.create!(
name:, **parsed.except(:compatibility_body), actor: current_user, locale, name:, **parsed.except(:compatibility_body), actor: current_user,
require_compatibility: true) require_compatibility: true)
version = current_group_version(group) version = current_group_version(group)
return render_group_representation(TagGroupRepr.one(group), version, return render_group_representation(TagGroupRepr.one(locale, group), version,
status: :created) status: :created)
end end
@@ -146,14 +159,15 @@ class WikiPagesController < ApplicationController
if title.include?(':') if title.include?(':')
return render_unprocessable_entity field: :title return render_unprocessable_entity field: :title
else else
tag_name = TagName.find_by(language_code: 'ja', name: title) tag_name = TagName.find_by(language_code: locale.language_code, name: title)
return render_unprocessable_entity field: :title unless tag_name&.tag return render_unprocessable_entity field: :title unless tag_name&.tag
TagDescriptionOperation.create!( TagDescriptionOperation.create!(
locale,
tag: tag_name.tag, body:, created_by_user: current_user, message:) tag: tag_name.tag, body:, created_by_user: current_user, message:)
end end
render json: WikiPageRepr.tag_description(description), status: :created render json: WikiPageRepr.tag_description(locale, description), status: :created
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
render_validation_error e.record render_validation_error e.record
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
@@ -172,26 +186,28 @@ class WikiPagesController < ApplicationController
return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank? return render_unprocessable_entity('タイトルは必須です.', field: :title) if title.blank?
return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank? return render_unprocessable_entity('本文は必須です.', field: :body) if body.blank?
locale = resolve_locale!
group = TagGroup.find_by(id: params[:id]) group = TagGroup.find_by(id: params[:id])
if title.start_with?(TagGroupRepr::PREFIX) if (name = TagGroupRepr.name_from_title(title))
return head :not_found unless group return head :not_found unless group
name = title.delete_prefix(TagGroupRepr::PREFIX) parsed = TagGroupMarkdownParser.parse(locale, body:, group:)
parsed = TagGroupMarkdownParser.parse(body:, group:)
TagGroupOperation.update!( TagGroupOperation.update!(
group:, name:, **parsed.except(:compatibility_body), actor: current_user, locale, group:, name:, **parsed.except(:compatibility_body), actor: current_user,
require_compatibility: true) require_compatibility: true)
return head :ok return head :ok
end end
return render_unprocessable_entity field: :title if title.include?(':') return render_unprocessable_entity field: :title if title.include?(':')
description = TagDescription.find_by(id: params[:id]) description = TagDescription.find_by(id: params[:id], language_code: locale.language_code)
unless description unless description
return render_unprocessable_entity field: :title if group return render_unprocessable_entity field: :title if group
return head :not_found return head :not_found
end end
tag = description.tag tag = description.tag
tag_name = tag.tag_name('ja') tag_name = tag.tag_name(locale.language_code)
return render_unprocessable_entity field: :title unless tag_name return render_unprocessable_entity field: :title unless tag_name
return render_unprocessable_entity field: :title if tag_name.name.include?(':') return render_unprocessable_entity field: :title if tag_name.name.include?(':')
@@ -207,7 +223,7 @@ class WikiPagesController < ApplicationController
tag_name.update!(name: title) if title != old_title tag_name.update!(name: title) if title != old_title
message = params[:message].presence message = params[:message].presence
TagDescriptionOperation.update!(tag:, body:, updated_by_user: current_user, TagDescriptionOperation.update!(locale, tag:, body:, updated_by_user: current_user,
message:, base_revision_id:) message:, base_revision_id:)
if title != old_title if title != old_title
@@ -226,10 +242,12 @@ class WikiPagesController < ApplicationController
end end
def changes def changes
locale = resolve_locale!
id = params[:id].presence id = params[:id].presence
description = id && TagDescription.find_by(id:) description = id && TagDescription.find_by(id:, language_code: locale.language_code)
if description if description
return render json: description_changes(TagDescription.where(id: description.id)) return render json: description_changes(locale, TagDescription.where(id: description.id))
end end
tag_group = id && TagGroup.find_by(id:) tag_group = id && TagGroup.find_by(id:)
@@ -240,7 +258,8 @@ class WikiPagesController < ApplicationController
json = [] json = []
unless id unless id
json.concat(tag_group_changes(TagGroupVersion.all)) json.concat(tag_group_changes(TagGroupVersion.all))
json.concat(description_changes(TagDescription.where(language_code: 'ja'))) json.concat(description_changes(locale,
TagDescription.where(language_code: locale.language_code)))
json = json.each_with_index.sort_by do |row, index| json = json.each_with_index.sort_by do |row, index|
[-row.fetch(:timestamp).to_f, index] [-row.fetch(:timestamp).to_f, index]
end.map(&:first) end.map(&:first)
@@ -250,7 +269,7 @@ class WikiPagesController < ApplicationController
private private
def render_tag_group group def render_tag_group locale, group
version = if params[:version].present? version = if params[:version].present?
group_version(group, params[:version]) group_version(group, params[:version])
else else
@@ -258,16 +277,17 @@ class WikiPagesController < ApplicationController
end end
return head :not_found unless version return head :not_found unless version
representation = if params[:version].present? representation =
TagGroupRepr.historical(version) if params[:version].present?
else TagGroupRepr.historical(locale, version)
TagGroupRepr.one(group) else
end TagGroupRepr.one(locale, group)
end
render_group_representation(representation, version) render_group_representation(representation, version)
end end
def render_group_version version def render_group_version locale, version
representation = TagGroupRepr.historical(version) representation = TagGroupRepr.historical(locale, version)
TagGroupMarkdownRenderer.render(representation) TagGroupMarkdownRenderer.render(representation)
end end
@@ -336,7 +356,7 @@ class WikiPagesController < ApplicationController
end end
end end
def render_wiki_page_by_tag_description description def render_wiki_page_by_tag_description locale, description
versions = description_versions(description) versions = description_versions(description)
historical = params[:version].present? historical = params[:version].present?
rev = historical ? versions.find_by(id: params[:version]) : rev = historical ? versions.find_by(id: params[:version]) :
@@ -347,7 +367,7 @@ class WikiPagesController < ApplicationController
succ = versions.find_by(version_no: rev.version_no + 1)&.id succ = versions.find_by(version_no: rev.version_no + 1)&.id
body = historical ? rev.body : description.body body = historical ? rev.body : description.body
updated_at = historical ? rev.created_at : description.updated_at updated_at = historical ? rev.created_at : description.updated_at
render json: WikiPageRepr.tag_description(description).merge( render json: WikiPageRepr.tag_description(locale, description).merge(
body:, revision_id: rev.id, pred:, succ:, updated_at:) body:, revision_id: rev.id, pred:, succ:, updated_at:)
end end
@@ -356,13 +376,14 @@ class WikiPagesController < ApplicationController
language_code: description.language_code) language_code: description.language_code)
end end
def tag_description_by_title title def tag_description_by_title locale, title
TagDescription.joins(tag: :tag_names) TagDescription.joins(tag: :tag_names)
.find_by(language_code: 'ja', tag_names: { language_code: 'ja', name: title }) .find_by(language_code: locale.language_code,
tag_names: { language_code: locale.language_code, name: title })
end end
def description_changes descriptions def description_changes locale, descriptions
versions = TagDescriptionVersion.where(language_code: 'ja', versions = TagDescriptionVersion.where(language_code: locale.language_code,
tag_id: descriptions.select(:tag_id)) tag_id: descriptions.select(:tag_id))
.order(id: :desc).limit(200).to_a .order(id: :desc).limit(200).to_a
return [] if versions.empty? return [] if versions.empty?
@@ -371,7 +392,7 @@ class WikiPagesController < ApplicationController
.includes(tag: :tag_names).index_by(&:tag_id) .includes(tag: :tag_names).index_by(&:tag_id)
users = User.where(id: versions.map(&:created_by_user_id)).index_by(&:id) users = User.where(id: versions.map(&:created_by_user_id)).index_by(&:id)
predecessors = TagDescriptionVersion.where( predecessors = TagDescriptionVersion.where(
language_code: 'ja', tag_id: by_tag.keys, language_code: locale.language_code, tag_id: by_tag.keys,
version_no: versions.map { |version| version.version_no - 1 }) version_no: versions.map { |version| version.version_no - 1 })
.pluck(:tag_id, :version_no, :id) .pluck(:tag_id, :version_no, :id)
.to_h { |tag_id, version_no, id| [[tag_id, version_no], id] } .to_h { |tag_id, version_no, id| [[tag_id, version_no], id] }
@@ -381,7 +402,7 @@ class WikiPagesController < ApplicationController
{ revision_id: version.id, { revision_id: version.id,
pred: predecessors[[version.tag_id, version.version_no - 1]], pred: predecessors[[version.tag_id, version.version_no - 1]],
succ: nil, succ: nil,
wiki_page: { id: description.id, title: description.tag.name('ja'), wiki_page: { id: description.id, title: description.tag.name(locale.language_code),
deprecated_at: description.tag.deprecated_at }, deprecated_at: description.tag.deprecated_at },
user: user && { id: user.id, name: user.name }, user: user && { id: user.id, name: user.name },
kind: 'content', kind: 'content',
+32 -21
ファイルの表示
@@ -3,7 +3,12 @@ module TagGroupRepr
module_function module_function
def many groups def name_from_title title
prefix = [PREFIX, 'tag_group:'].find { title.downcase.start_with?(_1) }
title[prefix.length..] if prefix
end
def many locale, groups
ids = groups.map(&:id) ids = groups.map(&:id)
return [] if ids.empty? return [] if ids.empty?
@@ -14,14 +19,20 @@ module TagGroupRepr
placements = TagGroupPlacement.where(tag_group_id: ids).order(:position) placements = TagGroupPlacement.where(tag_group_id: ids).order(:position)
.group_by(&:tag_group_id) .group_by(&:tag_group_id)
target_ids = tag_members.values.flatten.map(&:tag_id) + target_ids =
group_members.values.flatten.map(&:child_tag_group_id) + tag_members.values.flatten.map(&:tag_id) +
placements.values.flatten.filter_map(&:tag_id) placements.values.flatten.filter_map(&:tag_id)
child_ids = group_members.values.flatten.map(&:child_tag_group_id) + child_ids =
group_members.values.flatten.map(&:child_tag_group_id) +
placements.values.flatten.filter_map(&:child_tag_group_id) placements.values.flatten.filter_map(&:child_tag_group_id)
tags = Tag.select(:id, :category).where(id: target_ids.uniq).index_by(&:id) tags = Tag.select(:id, :category).where(id: target_ids.uniq).index_by(&:id)
names = TagName.where(tag_id: target_ids.uniq, language_code: 'ja', primary_flg: true) names =
.pluck(:tag_id, :name).to_h TagName
.where(tag_id: target_ids.uniq,
language_code: locale.language_code,
primary_flg: true)
.pluck(:tag_id, :name)
.to_h
child_groups = TagGroup.where(id: child_ids.uniq).index_by(&:id) child_groups = TagGroup.where(id: child_ids.uniq).index_by(&:id)
groups.map do |group| groups.map do |group|
@@ -32,8 +43,8 @@ module TagGroupRepr
end end
end end
def one group def one locale, group
many([group]).first many(locale, [group]).first
end end
def tag_group wiki_page def tag_group wiki_page
@@ -48,7 +59,7 @@ module TagGroupRepr
TagGroupVersion.find_by(tag_group_id: group.id, version_no: group.version_no) TagGroupVersion.find_by(tag_group_id: group.id, version_no: group.version_no)
end end
def historical version def historical locale, version
members = Array(version.members) members = Array(version.members)
placements = Array(version.placements) placements = Array(version.placements)
tag_references = members.filter_map { |member| member['tag_id'] } + tag_references = members.filter_map { |member| member['tag_id'] } +
@@ -68,10 +79,10 @@ module TagGroupRepr
[member.fetch('tag_group_id'), member.fetch('version_no')] [member.fetch('tag_group_id'), member.fetch('version_no')]
end.to_h end.to_h
historical_members = members.map do |member| historical_members = members.map do |member|
historical_member(member, tag_versions, group_versions) historical_member(locale, member, tag_versions, group_versions)
end end
historical_placements = placements.map do |node| historical_placements = placements.map do |node|
historical_placement(node, tag_versions, group_versions, historical_placement(locale, node, tag_versions, group_versions,
tag_version_numbers, group_version_numbers) tag_version_numbers, group_version_numbers)
end end
{ 'id' => version.tag_group_id, { 'id' => version.tag_group_id,
@@ -89,7 +100,7 @@ module TagGroupRepr
def group_json group, tag_members, group_members, rows, tags, names, child_groups def group_json group, tag_members, group_members, rows, tags, names, child_groups
tree = placement_tree(rows, tags, names, child_groups) tree = placement_tree(rows, tags, names, child_groups)
members = tag_members.sort_by(&:tag_id).map do |member| members = tag_members.sort_by(&:tag_id).map do |member|
{ 'tag' => tag_json(tags.fetch(member.tag_id), names.fetch(member.tag_id)) } { 'tag' => tag_json(tags.fetch(member.tag_id), names[member.tag_id]) }
end end
members.concat(group_members.sort_by(&:child_tag_group_id).map do |member| members.concat(group_members.sort_by(&:child_tag_group_id).map do |member|
{ 'tag_group' => group_json_reference(child_groups.fetch(member.child_tag_group_id)) } { 'tag_group' => group_json_reference(child_groups.fetch(member.child_tag_group_id)) }
@@ -115,7 +126,7 @@ module TagGroupRepr
build = lambda do |parent| build = lambda do |parent|
by_parent.fetch(parent, []).sort_by(&:position).map do |row| by_parent.fetch(parent, []).sort_by(&:position).map do |row|
node = if row.tag_id node = if row.tag_id
{ 'tag' => tag_json(tags.fetch(row.tag_id), names.fetch(row.tag_id)) } { 'tag' => tag_json(tags.fetch(row.tag_id), names[row.tag_id]) }
elsif row.child_tag_group_id elsif row.child_tag_group_id
{ 'tag_group' => group_json_reference(child_groups.fetch(row.child_tag_group_id)) } { 'tag_group' => group_json_reference(child_groups.fetch(row.child_tag_group_id)) }
else else
@@ -153,11 +164,11 @@ module TagGroupRepr
end end
end end
def historical_member member, tag_versions, group_versions def historical_member locale, member, tag_versions, group_versions
if member['tag_id'] if member['tag_id']
id = member.fetch('tag_id') id = member.fetch('tag_id')
version = tag_versions.fetch([id, member.fetch('version_no')]) version = tag_versions.fetch([id, member.fetch('version_no')])
{ 'tag' => { 'id' => id, 'name' => japanese_version_name(version), { 'tag' => { 'id' => id, 'name' => version_name(locale, version),
'category' => version.category } } 'category' => version.category } }
else else
id = member.fetch('tag_group_id') id = member.fetch('tag_group_id')
@@ -166,13 +177,13 @@ module TagGroupRepr
end end
end end
def historical_placement(node, tag_versions, group_versions, def historical_placement locale, node, tag_versions, group_versions,
tag_version_numbers, group_version_numbers) tag_version_numbers, group_version_numbers
result = if node['tag_id'] result = if node['tag_id']
id = node.fetch('tag_id') id = node.fetch('tag_id')
version_no = tag_version_numbers.fetch(id) version_no = tag_version_numbers.fetch(id)
version = tag_versions.fetch([id, version_no]) version = tag_versions.fetch([id, version_no])
{ 'tag' => { 'id' => id, 'name' => japanese_version_name(version), { 'tag' => { 'id' => id, 'name' => version_name(locale, version),
'category' => version.category } } 'category' => version.category } }
elsif node['tag_group_id'] elsif node['tag_group_id']
id = node.fetch('tag_group_id') id = node.fetch('tag_group_id')
@@ -183,12 +194,12 @@ module TagGroupRepr
{ 'section_name' => node.fetch('section_name'), 'description' => node['description'] } { 'section_name' => node.fetch('section_name'), 'description' => node['description'] }
end end
result.merge('children' => Array(node['children']).map do |child| result.merge('children' => Array(node['children']).map do |child|
historical_placement(child, tag_versions, group_versions, historical_placement(locale, child, tag_versions, group_versions,
tag_version_numbers, group_version_numbers) tag_version_numbers, group_version_numbers)
end) end)
end end
def japanese_version_name version def version_name locale, version
version.names.fetch('ja').fetch('name') version.names[locale.language_code]&.fetch('name')
end end
end end
+4 -3
ファイルの表示
@@ -4,11 +4,12 @@
module WikiPageRepr module WikiPageRepr
module_function module_function
def tag_description description def tag_description locale, description
tag = description.tag tag = description.tag
{ 'id' => description.id, { 'id' => description.id,
'resource_type' => 'tag_description', 'resource_type' => 'tag_description',
'tag_name_id' => tag.tag_name('ja')&.id, 'tag_name_id' => tag.tag_name(locale.language_code)&.id,
'body' => description.body, 'body' => description.body,
'created_user_id' => description.created_by_user_id, 'created_user_id' => description.created_by_user_id,
'updated_user_id' => description.updated_by_user_id, 'updated_user_id' => description.updated_by_user_id,
@@ -16,7 +17,7 @@ module WikiPageRepr
'updated_at' => description.updated_at, 'updated_at' => description.updated_at,
'discarded_at' => nil, 'discarded_at' => nil,
'version_no' => description.version_no, 'version_no' => description.version_no,
'title' => tag.name('ja'), 'title' => tag.name(locale.language_code),
'deprecated_at' => tag.deprecated_at } 'deprecated_at' => tag.deprecated_at }
end end
+7 -9
ファイルの表示
@@ -1,32 +1,30 @@
class TagDescriptionOperation class TagDescriptionOperation
LANGUAGE_CODE = 'ja'
class Conflict < StandardError class Conflict < StandardError
; ;
end end
def self.create!(tag:, body:, created_by_user:, message: nil) def self.create! locale, tag:, body:, created_by_user:, message: nil
ApplicationRecord.transaction do ApplicationRecord.transaction do
tag.lock! tag.lock!
raise ActiveRecord::RecordNotUnique if TagDescription.exists?( raise ActiveRecord::RecordNotUnique if TagDescription.exists?(
tag_id: tag.id, language_code: LANGUAGE_CODE) tag_id: tag.id, language_code: locale.language_code)
description = TagDescription.new(tag:, language_code: LANGUAGE_CODE) description = TagDescription.new(tag:, language_code: locale.language_code)
body = normalise_body!(description, body) body = normalise_body!(description, body)
record_version!(description:, body:, user: created_by_user) record_version!(description:, body:, user: created_by_user)
description description
end end
end end
def self.update!(tag:, body:, updated_by_user:, message: nil, def self.update! locale, tag:, body:, updated_by_user:, message: nil, base_revision_id: nil
base_revision_id: nil)
ApplicationRecord.transaction do ApplicationRecord.transaction do
tag.lock! tag.lock!
description = TagDescription.lock.find_by!( description = TagDescription.lock.find_by!(
tag_id: tag.id, language_code: LANGUAGE_CODE) tag_id: tag.id, language_code: locale.language_code)
body = normalise_body!(description, body) body = normalise_body!(description, body)
latest = TagDescriptionVersion.find_by( latest = TagDescriptionVersion.find_by(
tag_id: tag.id, language_code: LANGUAGE_CODE, tag_id: tag.id, language_code: locale.language_code,
version_no: description.version_no) version_no: description.version_no)
if base_revision_id.present? && latest&.id != base_revision_id.to_i if base_revision_id.present? && latest&.id != base_revision_id.to_i
raise Conflict, '競合が発生してゐます.' raise Conflict, '競合が発生してゐます.'
+22 -21
ファイルの表示
@@ -7,8 +7,8 @@ class TagGroupMarkdownParser
; ;
end end
def self.parse(body:, group: nil) def self.parse(locale, body:, group: nil)
new(body:, group:).parse new(body:, group:).parse(locale)
end end
def initialize(body:, group:) def initialize(body:, group:)
@@ -16,7 +16,7 @@ class TagGroupMarkdownParser
@group = group @group = group
end end
def parse def parse locale
parse_body = @body.gsub(/([^\n])\n([\x23]{1,6}[ \t]+\S)/, "\\1\n\n\\2") parse_body = @body.gsub(/([^\n])\n([\x23]{1,6}[ \t]+\S)/, "\\1\n\n\\2")
root = Kramdown::Document.new(parse_body, input: 'GFM', auto_ids: false).root root = Kramdown::Document.new(parse_body, input: 'GFM', auto_ids: false).root
placements = [] placements = []
@@ -51,7 +51,7 @@ class TagGroupMarkdownParser
when :ul when :ul
invalid!('未対応のリスト項目があります.') if node.attr.any? invalid!('未対応のリスト項目があります.') if node.attr.any?
target = headings.any? ? headings.last.fetch(:node).fetch('children') : placements target = headings.any? ? headings.last.fetch(:node).fetch('children') : placements
target.concat(node.children.map { parse_list_item(_1) }) target.concat(node.children.map { parse_list_item(locale, _1) })
else else
invalid!("未対応の Markdown 構文 #{ node.type.inspect } です.") invalid!("未対応の Markdown 構文 #{ node.type.inspect } です.")
end end
@@ -66,7 +66,7 @@ class TagGroupMarkdownParser
private private
def parse_list_item(item) def parse_list_item locale, item
invalid!('リスト項目ではありません.') unless item.type == :li invalid!('リスト項目ではありません.') unless item.type == :li
invalid!('未対応のリスト項目があります.') if item.attr.any? invalid!('未対応のリスト項目があります.') if item.attr.any?
@@ -81,29 +81,30 @@ class TagGroupMarkdownParser
label = own.map { inline_text(_1) }.join(' ').strip label = own.map { inline_text(_1) }.join(' ').strip
invalid!('空のリスト項目は使用できません.') if label.blank? invalid!('空のリスト項目は使用できません.') if label.blank?
node = if label.start_with?(PREFIX) node =
group_name = label.delete_prefix(PREFIX).strip if (group_name = TagGroupRepr.name_from_title(label))
child_group = TagGroup.find_by(name: group_name) group_name = group_name.strip
invalid!("Tag Group #{ group_name.inspect } を解決できません.") unless child_group child_group = TagGroup.find_by(name: group_name)
invalid!('自分自身を含めることはできません.') if child_group.id == @group&.id invalid!("Tag Group #{ group_name.inspect } を解決できません.") unless child_group
{ 'tag_group_id' => child_group.id } invalid!('自分自身を含めることはできません.') if child_group.id == @group&.id
elsif label.end_with?(':', ':') { 'tag_group_id' => child_group.id }
section_name = label.sub(/[::]\z/, '').strip elsif label.end_with?(':', ':')
invalid!('Section 名は必須です.') if section_name.blank? section_name = label.sub(/[::]\z/, '').strip
{ 'section_name' => section_name, 'description' => nil } invalid!('Section 名は必須です.') if section_name.blank?
else { 'section_name' => section_name, 'description' => nil }
{ 'tag_id' => resolve_tag(label) } else
end { 'tag_id' => resolve_tag(locale, label) }
end
node['children'] = nested.flat_map do |list| node['children'] = nested.flat_map do |list|
invalid!('未対応の入れ子リストです.') unless list.type == :ul invalid!('未対応の入れ子リストです.') unless list.type == :ul
list.children.map { parse_list_item(_1) } list.children.map { parse_list_item(locale, _1) }
end end
node node
end end
def resolve_tag(name) def resolve_tag locale, name
names = TagName.where(language_code: 'ja', name:).to_a names = TagName.where(language_code: locale.language_code, name:).to_a
tag_ids = names.filter_map(&:tag_id).uniq tag_ids = names.filter_map(&:tag_id).uniq
invalid!("Tag #{ name.inspect } を解決できません.") if tag_ids.empty? invalid!("Tag #{ name.inspect } を解決できません.") if tag_ids.empty?
invalid!("Tag 名 #{ name.inspect } が曖昧です.") if tag_ids.many? invalid!("Tag 名 #{ name.inspect } が曖昧です.") if tag_ids.many?
+15 -14
ファイルの表示
@@ -1,29 +1,29 @@
class TagGroupOperation class TagGroupOperation
MAX_PLACEMENT_DEPTH = 5 MAX_PLACEMENT_DEPTH = 5
def self.create!(name:, description:, members:, placements:, actor:, def self.create!(locale, name:, description:, members:, placements:, actor:,
require_compatibility: false) require_compatibility: false)
ApplicationRecord.transaction do ApplicationRecord.transaction do
group = TagGroup.new(version_no: 1, created_by_user: actor, group = TagGroup.new(version_no: 1, created_by_user: actor,
updated_by_user: actor) updated_by_user: actor)
attributes = prepare!(group:, name:, description:, members:, placements:) attributes = prepare!(locale:, group:, name:, description:, members:, placements:)
group.assign_attributes(name: attributes.fetch(:name), group.assign_attributes(name: attributes.fetch(:name),
description: attributes.fetch(:description)) description: attributes.fetch(:description))
group.save! group.save!
persist_structure!(group:, attributes:, actor:) persist_structure!(group:, attributes:, actor:)
validate_compatibility!(group) if require_compatibility validate_compatibility!(locale, group) if require_compatibility
persist_version!(group:, attributes:, actor:, event_type: 'create') persist_version!(group:, attributes:, actor:, event_type: 'create')
group group
end end
end end
def self.update!(group:, name:, description:, members:, placements:, actor:, def self.update!(locale, group:, name:, description:, members:, placements:, actor:,
require_compatibility: false) require_compatibility: false)
ApplicationRecord.transaction do ApplicationRecord.transaction do
group.lock! group.lock!
attributes = prepare!(group:, name:, description:, members:, placements:) attributes = prepare!(locale:, group:, name:, description:, members:, placements:)
if unchanged?(group, attributes) if unchanged?(group, attributes)
validate_compatibility!(group) if require_compatibility validate_compatibility!(locale, group) if require_compatibility
next group next group
end end
@@ -32,20 +32,20 @@ class TagGroupOperation
version_no: group.version_no + 1, version_no: group.version_no + 1,
updated_by_user: actor) updated_by_user: actor)
persist_structure!(group:, attributes:, actor:) persist_structure!(group:, attributes:, actor:)
validate_compatibility!(group) if require_compatibility validate_compatibility!(locale, group) if require_compatibility
persist_version!(group:, attributes:, actor:, event_type: 'update') persist_version!(group:, attributes:, actor:, event_type: 'update')
group group
end end
end end
def self.validate_compatibility! group def self.validate_compatibility! locale, group
TagGroupMarkdownRenderer.render(TagGroupRepr.one(group)) TagGroupMarkdownRenderer.render(TagGroupRepr.one(locale, group))
rescue TagGroupMarkdownRenderer::UnsupportedStructure => error rescue TagGroupMarkdownRenderer::UnsupportedStructure => error
invalid!(group, :base, error.message) invalid!(group, :base, error.message)
end end
private_class_method :validate_compatibility! private_class_method :validate_compatibility!
def self.prepare!(group:, name:, description:, members:, placements:) def self.prepare!(locale:, group:, name:, description:, members:, placements:)
record = group record = group
normalized_name = name.to_s.strip normalized_name = name.to_s.strip
invalid!(record, :name, 'グループ名は必須です.') if normalized_name.blank? invalid!(record, :name, 'グループ名は必須です.') if normalized_name.blank?
@@ -63,7 +63,7 @@ class TagGroupOperation
member_rows = normalize_members(record, members) member_rows = normalize_members(record, members)
placement_tree = normalize_placements(record, placements) placement_tree = normalize_placements(record, placements)
validate_references!(record, member_rows, placement_tree) validate_references!(locale, record, member_rows, placement_tree)
validate_containment!(record, member_rows) validate_containment!(record, member_rows)
{ name: normalized_name, description:, members: member_rows, { name: normalized_name, description:, members: member_rows,
placements: placement_tree } placements: placement_tree }
@@ -187,7 +187,7 @@ class TagGroupOperation
end end
private_class_method :normalize_placement_nodes private_class_method :normalize_placement_nodes
def self.validate_references!(record, members, placements) def self.validate_references!(locale, record, members, placements)
membership = members.to_set membership = members.to_set
placed = placement_references(placements).uniq placed = placement_references(placements).uniq
missing = placed - membership.to_a missing = placed - membership.to_a
@@ -206,13 +206,14 @@ class TagGroupOperation
tag_versions = TagVersion.where(tag_id: tag_ids).pluck(:tag_id, :version_no).to_set tag_versions = TagVersion.where(tag_id: tag_ids).pluck(:tag_id, :version_no).to_set
group_versions = TagGroupVersion.where(tag_group_id: group_ids) group_versions = TagGroupVersion.where(tag_group_id: group_ids)
.pluck(:tag_group_id, :version_no).to_set .pluck(:tag_group_id, :version_no).to_set
tag_names = TagName.where(tag_id: tag_ids, language_code: 'ja', primary_flg: true) tag_names = TagName.where(
tag_id: tag_ids, language_code: locale.language_code, primary_flg: true)
.pluck(:tag_id).uniq .pluck(:tag_id).uniq
members.each do |member| members.each do |member|
if member.key?('tag_id') if member.key?('tag_id')
id = member.fetch('tag_id') id = member.fetch('tag_id')
invalid!(record, :members, "Tag ##{ id } に日本語の正規名がありません.") unless invalid!(record, :members, :invalid) unless
tag_names.include?(id) tag_names.include?(id)
version_no = tags.fetch(id).version_no version_no = tags.fetch(id).version_no
unless tag_versions.include?([id, version_no]) unless tag_versions.include?([id, version_no])