このコミットが含まれているのは:
@@ -1,32 +1,30 @@
|
||||
class TagDescriptionOperation
|
||||
LANGUAGE_CODE = 'ja'
|
||||
|
||||
class Conflict < StandardError
|
||||
;
|
||||
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
|
||||
tag.lock!
|
||||
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)
|
||||
record_version!(description:, body:, user: created_by_user)
|
||||
|
||||
description
|
||||
end
|
||||
end
|
||||
|
||||
def self.update!(tag:, body:, updated_by_user:, message: nil,
|
||||
base_revision_id: nil)
|
||||
def self.update! locale, tag:, body:, updated_by_user:, message: nil, base_revision_id: nil
|
||||
ApplicationRecord.transaction do
|
||||
tag.lock!
|
||||
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)
|
||||
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)
|
||||
if base_revision_id.present? && latest&.id != base_revision_id.to_i
|
||||
raise Conflict, '競合が発生してゐます.'
|
||||
|
||||
@@ -7,8 +7,8 @@ class TagGroupMarkdownParser
|
||||
;
|
||||
end
|
||||
|
||||
def self.parse(body:, group: nil)
|
||||
new(body:, group:).parse
|
||||
def self.parse(locale, body:, group: nil)
|
||||
new(body:, group:).parse(locale)
|
||||
end
|
||||
|
||||
def initialize(body:, group:)
|
||||
@@ -16,7 +16,7 @@ class TagGroupMarkdownParser
|
||||
@group = group
|
||||
end
|
||||
|
||||
def parse
|
||||
def parse locale
|
||||
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
|
||||
placements = []
|
||||
@@ -51,7 +51,7 @@ class TagGroupMarkdownParser
|
||||
when :ul
|
||||
invalid!('未対応のリスト項目があります.') if node.attr.any?
|
||||
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
|
||||
invalid!("未対応の Markdown 構文 #{ node.type.inspect } です.")
|
||||
end
|
||||
@@ -66,7 +66,7 @@ class TagGroupMarkdownParser
|
||||
|
||||
private
|
||||
|
||||
def parse_list_item(item)
|
||||
def parse_list_item locale, item
|
||||
invalid!('リスト項目ではありません.') unless item.type == :li
|
||||
invalid!('未対応のリスト項目があります.') if item.attr.any?
|
||||
|
||||
@@ -81,29 +81,30 @@ class TagGroupMarkdownParser
|
||||
label = own.map { inline_text(_1) }.join(' ').strip
|
||||
invalid!('空のリスト項目は使用できません.') if label.blank?
|
||||
|
||||
node = if label.start_with?(PREFIX)
|
||||
group_name = label.delete_prefix(PREFIX).strip
|
||||
child_group = TagGroup.find_by(name: group_name)
|
||||
invalid!("Tag Group #{ group_name.inspect } を解決できません.") unless child_group
|
||||
invalid!('自分自身を含めることはできません.') if child_group.id == @group&.id
|
||||
{ 'tag_group_id' => child_group.id }
|
||||
elsif label.end_with?(':', ':')
|
||||
section_name = label.sub(/[::]\z/, '').strip
|
||||
invalid!('Section 名は必須です.') if section_name.blank?
|
||||
{ 'section_name' => section_name, 'description' => nil }
|
||||
else
|
||||
{ 'tag_id' => resolve_tag(label) }
|
||||
end
|
||||
node =
|
||||
if (group_name = TagGroupRepr.name_from_title(label))
|
||||
group_name = group_name.strip
|
||||
child_group = TagGroup.find_by(name: group_name)
|
||||
invalid!("Tag Group #{ group_name.inspect } を解決できません.") unless child_group
|
||||
invalid!('自分自身を含めることはできません.') if child_group.id == @group&.id
|
||||
{ 'tag_group_id' => child_group.id }
|
||||
elsif label.end_with?(':', ':')
|
||||
section_name = label.sub(/[::]\z/, '').strip
|
||||
invalid!('Section 名は必須です.') if section_name.blank?
|
||||
{ 'section_name' => section_name, 'description' => nil }
|
||||
else
|
||||
{ 'tag_id' => resolve_tag(locale, label) }
|
||||
end
|
||||
|
||||
node['children'] = nested.flat_map do |list|
|
||||
invalid!('未対応の入れ子リストです.') unless list.type == :ul
|
||||
list.children.map { parse_list_item(_1) }
|
||||
list.children.map { parse_list_item(locale, _1) }
|
||||
end
|
||||
node
|
||||
end
|
||||
|
||||
def resolve_tag(name)
|
||||
names = TagName.where(language_code: 'ja', name:).to_a
|
||||
def resolve_tag locale, name
|
||||
names = TagName.where(language_code: locale.language_code, name:).to_a
|
||||
tag_ids = names.filter_map(&:tag_id).uniq
|
||||
invalid!("Tag #{ name.inspect } を解決できません.") if tag_ids.empty?
|
||||
invalid!("Tag 名 #{ name.inspect } が曖昧です.") if tag_ids.many?
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
class TagGroupOperation
|
||||
MAX_PLACEMENT_DEPTH = 5
|
||||
|
||||
def self.create!(name:, description:, members:, placements:, actor:,
|
||||
def self.create!(locale, name:, description:, members:, placements:, actor:,
|
||||
require_compatibility: false)
|
||||
ApplicationRecord.transaction do
|
||||
group = TagGroup.new(version_no: 1, created_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),
|
||||
description: attributes.fetch(:description))
|
||||
group.save!
|
||||
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')
|
||||
group
|
||||
end
|
||||
end
|
||||
|
||||
def self.update!(group:, name:, description:, members:, placements:, actor:,
|
||||
def self.update!(locale, group:, name:, description:, members:, placements:, actor:,
|
||||
require_compatibility: false)
|
||||
ApplicationRecord.transaction do
|
||||
group.lock!
|
||||
attributes = prepare!(group:, name:, description:, members:, placements:)
|
||||
attributes = prepare!(locale:, group:, name:, description:, members:, placements:)
|
||||
if unchanged?(group, attributes)
|
||||
validate_compatibility!(group) if require_compatibility
|
||||
validate_compatibility!(locale, group) if require_compatibility
|
||||
next group
|
||||
end
|
||||
|
||||
@@ -32,20 +32,20 @@ class TagGroupOperation
|
||||
version_no: group.version_no + 1,
|
||||
updated_by_user: 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')
|
||||
group
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_compatibility! group
|
||||
TagGroupMarkdownRenderer.render(TagGroupRepr.one(group))
|
||||
def self.validate_compatibility! locale, group
|
||||
TagGroupMarkdownRenderer.render(TagGroupRepr.one(locale, group))
|
||||
rescue TagGroupMarkdownRenderer::UnsupportedStructure => error
|
||||
invalid!(group, :base, error.message)
|
||||
end
|
||||
private_class_method :validate_compatibility!
|
||||
|
||||
def self.prepare!(group:, name:, description:, members:, placements:)
|
||||
def self.prepare!(locale:, group:, name:, description:, members:, placements:)
|
||||
record = group
|
||||
normalized_name = name.to_s.strip
|
||||
invalid!(record, :name, 'グループ名は必須です.') if normalized_name.blank?
|
||||
@@ -63,7 +63,7 @@ class TagGroupOperation
|
||||
|
||||
member_rows = normalize_members(record, members)
|
||||
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)
|
||||
{ name: normalized_name, description:, members: member_rows,
|
||||
placements: placement_tree }
|
||||
@@ -187,7 +187,7 @@ class TagGroupOperation
|
||||
end
|
||||
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
|
||||
placed = placement_references(placements).uniq
|
||||
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
|
||||
group_versions = TagGroupVersion.where(tag_group_id: group_ids)
|
||||
.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
|
||||
|
||||
members.each do |member|
|
||||
if member.key?('tag_id')
|
||||
id = member.fetch('tag_id')
|
||||
invalid!(record, :members, "Tag ##{ id } に日本語の正規名がありません.") unless
|
||||
invalid!(record, :members, :invalid) unless
|
||||
tag_names.include?(id)
|
||||
version_no = tags.fetch(id).version_no
|
||||
unless tag_versions.include?([id, version_no])
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする