このコミットが含まれているのは:
@@ -15,23 +15,22 @@ class NicoTagsController < ApplicationController
|
|||||||
limit = 1 if limit < 1
|
limit = 1 if limit < 1
|
||||||
|
|
||||||
post_tag_max_sql =
|
post_tag_max_sql =
|
||||||
PostTag
|
PostExternalTag
|
||||||
.select('tag_id, MAX(created_at) AS max_created_at')
|
.select('external_tag_id, MAX(created_at) AS max_created_at')
|
||||||
.group('tag_id')
|
.group('external_tag_id')
|
||||||
.to_sql
|
.to_sql
|
||||||
|
|
||||||
q = Tag.nico_tags
|
q =
|
||||||
.joins(:tag_name)
|
ExternalTag
|
||||||
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
||||||
'ON post_tag_max.tag_id = tags.id')
|
'ON post_tag_max.external_tag_id = external_tags.id')
|
||||||
.includes(:tag_name, tag_name: :wiki_page, linked_tags: { tag_name: :wiki_page })
|
q = q.where('external_tags.name LIKE ?', "%#{ name }%") if name
|
||||||
q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name
|
|
||||||
if linked_tag
|
if linked_tag
|
||||||
linked_tag_ids =
|
linked_tag_ids =
|
||||||
Tag
|
Tag
|
||||||
.joins(:tag_name)
|
.joins(:tag_name)
|
||||||
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
||||||
.pluck(:id)
|
.pluck(:id)
|
||||||
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
||||||
q = q.where(id: linked_nico_tag_ids)
|
q = q.where(id: linked_nico_tag_ids)
|
||||||
end
|
end
|
||||||
@@ -72,8 +71,7 @@ class NicoTagsController < ApplicationController
|
|||||||
|
|
||||||
id = params[:id].to_i
|
id = params[:id].to_i
|
||||||
|
|
||||||
tag = Tag.find(id)
|
tag = ExternalTag.find(id)
|
||||||
return render_bad_request('ニコニコ・タグを指定してください.') unless tag.nico?
|
|
||||||
|
|
||||||
linked_tag_names = params[:tags].to_s.split
|
linked_tag_names = params[:tags].to_s.split
|
||||||
linked_tags = nil
|
linked_tags = nil
|
||||||
@@ -81,9 +79,6 @@ class NicoTagsController < ApplicationController
|
|||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
linked_tags = Tag.normalise_tags!(linked_tag_names, with_tagme: false,
|
linked_tags = Tag.normalise_tags!(linked_tag_names, with_tagme: false,
|
||||||
with_no_deerjikist: false)
|
with_no_deerjikist: false)
|
||||||
if linked_tags.any? { |t| t.nico? }
|
|
||||||
raise Tag::NicoTagNormalisationError
|
|
||||||
end
|
|
||||||
|
|
||||||
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class PostVersionsController < ApplicationController
|
|||||||
'prev.original_created_before AS prev_original_created_before')
|
'prev.original_created_before AS prev_original_created_before')
|
||||||
q = q.where('post_versions.post_id = ?', post_id) if post_id
|
q = q.where('post_versions.post_id = ?', post_id) if post_id
|
||||||
if tag_id
|
if tag_id
|
||||||
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('id', #{ tag_id })) " +
|
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('tag_id', #{ tag_id })) " +
|
||||||
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('id', #{ tag_id }))")
|
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('tag_id', #{ tag_id }))")
|
||||||
end
|
end
|
||||||
|
|
||||||
count = q.except(:select, :order, :limit, :offset).count
|
count = q.except(:select, :order, :limit, :offset).count
|
||||||
@@ -113,21 +113,18 @@ class PostVersionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_version_tags(cur_tags, prev_tags)
|
def build_version_tags cur_tags, prev_tags
|
||||||
(cur_tags | prev_tags).map do |name|
|
(cur_tags | prev_tags).map do |name|
|
||||||
type =
|
type =
|
||||||
if cur_tags.include?(name) && prev_tags.include?(name)
|
if cur_tags.include?(name) && prev_tags.include?(name)
|
||||||
'context'
|
'context'
|
||||||
elsif cur_tags.include?(name)
|
elsif cur_tags.include?(name)
|
||||||
'added'
|
'added'
|
||||||
else
|
else
|
||||||
'removed'
|
'removed'
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
{ name:, type: }
|
||||||
name:,
|
|
||||||
type:
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -671,7 +671,6 @@ class PostsController < ApplicationController
|
|||||||
post
|
post
|
||||||
.post_tags
|
.post_tags
|
||||||
.joins(tag: :tag_name)
|
.joins(tag: :tag_name)
|
||||||
.merge(Tag.not_nico)
|
|
||||||
.merge(Tag.where(deprecated_at: nil))
|
.merge(Tag.where(deprecated_at: nil))
|
||||||
.includes(:sections, tag: :tag_name)
|
.includes(:sections, tag: :tag_name)
|
||||||
.order('tag_names.name')
|
.order('tag_names.name')
|
||||||
@@ -689,8 +688,7 @@ class PostsController < ApplicationController
|
|||||||
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!(tag_names, with_tagme: false, deny_deprecated: true,
|
||||||
with_sections: true) =>
|
with_sections: true) => { tags:, sections: }
|
||||||
{ 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:)
|
||||||
@@ -833,15 +831,12 @@ 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!(snapshot[:tag_names], with_tagme: false,
|
Tag.normalise_tags!(snapshot[:tag_names],
|
||||||
deny_deprecated: true,
|
with_tagme: false,
|
||||||
with_sections: true) =>
|
deny_deprecated: true,
|
||||||
{ tags: editable_tags, sections: }
|
with_sections: true) => { tags:, sections: }
|
||||||
TagVersioning.record_tag_snapshots!(editable_tags, created_by_user: current_user)
|
TagVersioning.record_tag_snapshots!(tags, created_by_user: current_user)
|
||||||
|
|
||||||
readonly_tags = post.tags.nico.to_a
|
|
||||||
|
|
||||||
tags = readonly_tags + editable_tags
|
|
||||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||||
|
|
||||||
post.video_ms = tags.any? { _1.id == Tag.video.id } ? snapshot[:video_ms] : nil
|
post.video_ms = tags.any? { _1.id == Tag.video.id } ? snapshot[:video_ms] : nil
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class TagChildrenController < ApplicationController
|
|||||||
|
|
||||||
parent = Tag.find(parent_id)
|
parent = Tag.find(parent_id)
|
||||||
child = Tag.find(child_id)
|
child = Tag.find(child_id)
|
||||||
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||||
@@ -33,7 +32,6 @@ class TagChildrenController < ApplicationController
|
|||||||
|
|
||||||
parent = Tag.find(parent_id)
|
parent = Tag.find(parent_id)
|
||||||
child = Tag.find(child_id)
|
child = Tag.find(child_id)
|
||||||
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||||
|
|||||||
@@ -100,13 +100,14 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
def autocomplete
|
def autocomplete
|
||||||
q = params[:q].to_s.strip.sub(/\Anot:/i, '')
|
q = params[:q].to_s.strip.sub(/\Anot:/i, '')
|
||||||
|
prefix = "#{ ActiveRecord::Base.sanitize_sql_like(q) }%"
|
||||||
|
|
||||||
with_nico = bool?(:nico, default: true)
|
with_nico = bool?(:nico, default: true)
|
||||||
present_only = bool?(:present, default: true)
|
present_only = bool?(:present, default: true)
|
||||||
|
|
||||||
alias_rows =
|
alias_rows =
|
||||||
TagName
|
TagName
|
||||||
.where('name LIKE ?', "#{ q }%")
|
.where('name LIKE ?', prefix)
|
||||||
.where.not(canonical_id: nil)
|
.where.not(canonical_id: nil)
|
||||||
.pluck(:canonical_id, :name)
|
.pluck(:canonical_id, :name)
|
||||||
|
|
||||||
@@ -118,29 +119,57 @@ class TagsController < ApplicationController
|
|||||||
matched_alias_by_tag_name_id[canonical_id] ||= alias_name
|
matched_alias_by_tag_name_id[canonical_id] ||= alias_name
|
||||||
end
|
end
|
||||||
|
|
||||||
base = Tag.joins(:tag_name)
|
base =
|
||||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
Tag
|
||||||
.where(deprecated_at: nil)
|
.joins(:tag_name)
|
||||||
|
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||||
|
.where(deprecated_at: nil)
|
||||||
base = base.where('tags.post_count > 0') if present_only
|
base = base.where('tags.post_count > 0') if present_only
|
||||||
|
|
||||||
canonical_hit =
|
canonical_hit = base.where('tag_names.name LIKE ?', prefix)
|
||||||
base
|
|
||||||
.where(((with_nico ? '(tags.category = ? AND tag_names.name LIKE ?) OR ' : '') +
|
|
||||||
'tag_names.name LIKE ?'),
|
|
||||||
*(with_nico ? ['nico', "nico:#{ q }%"] : []), "#{ q }%")
|
|
||||||
|
|
||||||
tags =
|
internal_tags =
|
||||||
if canonical_ids.present?
|
if with_nico
|
||||||
canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
||||||
else
|
else
|
||||||
canonical_hit
|
canonical_hit
|
||||||
end
|
end
|
||||||
|
|
||||||
tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a
|
internal_rows =
|
||||||
|
internal_tags
|
||||||
|
.order(Arel.sql('tags.post_count DESC, tag_names.name'))
|
||||||
|
.limit(20)
|
||||||
|
.map { |tag|
|
||||||
|
TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
|
||||||
|
}
|
||||||
|
|
||||||
render json: tags.map { |tag|
|
return render json: internal_rows unless with_nico
|
||||||
TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
|
|
||||||
}
|
external_base = ExternalTag.all
|
||||||
|
external_base = external_base.where('post_count > 0') if present_only
|
||||||
|
|
||||||
|
external_rows =
|
||||||
|
external_base
|
||||||
|
.where("CONCAT(platform, ':', name) LIKE ? OR name LIKE ?", prefix, prefix)
|
||||||
|
.order(post_count: :desc, name: :asc)
|
||||||
|
.limit(20)
|
||||||
|
.map { |tag|
|
||||||
|
{ 'id' => tag.id,
|
||||||
|
'name' => "#{ tag.platform }:#{ tag.name }",
|
||||||
|
'category' => 'nico',
|
||||||
|
'deprecated_at' => nil,
|
||||||
|
'created_at' => tag.created_at,
|
||||||
|
'updated_at' => tag.created_at,
|
||||||
|
'post_count' => tag.post_count,
|
||||||
|
'matched_alias' => nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
rows =
|
||||||
|
(internal_rows + external_rows)
|
||||||
|
.sort_by { |row| [-row[:post_count], row[:name]] }
|
||||||
|
.first(20)
|
||||||
|
|
||||||
|
render json: rows
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -282,11 +311,7 @@ class TagsController < ApplicationController
|
|||||||
parent_names = params[:parent_tags].to_s.split.uniq
|
parent_names = params[:parent_tags].to_s.split.uniq
|
||||||
deprecated = bool?(:deprecated)
|
deprecated = bool?(:deprecated)
|
||||||
|
|
||||||
if tag.nico? && deprecated
|
if category == 'nico'
|
||||||
return render_unprocessable_entity 'ニコタグは廃止できません.', field: :deprecated
|
|
||||||
end
|
|
||||||
|
|
||||||
if tag.nico? || category == 'nico'
|
|
||||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -334,13 +359,9 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
tag = Tag.find(params[:id])
|
tag = Tag.find(params[:id])
|
||||||
|
|
||||||
if tag.nico? && deprecated_given && deprecated
|
|
||||||
return render_unprocessable_entity 'ニコタグは廃止できません.', field: :deprecated
|
|
||||||
end
|
|
||||||
|
|
||||||
return unless validate_tag_rename(tag, name)
|
return unless validate_tag_rename(tag, name)
|
||||||
|
|
||||||
if tag.nico? || (category.present? && category == 'nico')
|
if category.present? && category == 'nico'
|
||||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -547,11 +568,6 @@ class TagsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def record_tag_version! tag, event_type:, created_by_user:, name_changed: false, wiki_page: nil
|
def record_tag_version! tag, event_type:, created_by_user:, name_changed: false, wiki_page: nil
|
||||||
if tag.nico?
|
|
||||||
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
|
|
||||||
return unless name_changed
|
return unless name_changed
|
||||||
@@ -661,8 +677,7 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
def update_parent_tags! tag, parent_names
|
def update_parent_tags! tag, parent_names
|
||||||
parent_tags = Tag.normalise_tags!(parent_names, with_tagme: false,
|
parent_tags = Tag.normalise_tags!(parent_names, with_tagme: false,
|
||||||
with_no_deerjikist: false,
|
with_no_deerjikist: false)
|
||||||
deny_nico: true)
|
|
||||||
|
|
||||||
old_parent_tags = tag.parents.to_a
|
old_parent_tags = tag.parents.to_a
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
class ExternalTag < ApplicationRecord
|
||||||
|
enum :platform, nico: 'nico'
|
||||||
|
validates :platform, presence: true, inclusion: { in: ExternalTag.platforms.keys }
|
||||||
|
|
||||||
|
has_many :post_external_tags, dependent: :delete_all
|
||||||
|
has_many :posts, through: :post_external_tags
|
||||||
|
|
||||||
|
has_many :nico_tag_versions, foreign_key: :tag_id, inverse_of: :external_tag
|
||||||
|
has_many :nico_tag_relations, foreign_key: :tag_id, dependent: :destroy
|
||||||
|
has_many :linked_tags, through: :nico_tag_relations, source: :nico_tag
|
||||||
|
|
||||||
|
def snapshot_linked_tag_names
|
||||||
|
linked_tags.joins(:tag_name).order('tag_names.name').pluck('tag_names.name')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class NicoTagVersion < ApplicationRecord
|
class NicoTagVersion < ApplicationRecord
|
||||||
include VersionRecord
|
include VersionRecord
|
||||||
|
|
||||||
belongs_to :tag
|
belongs_to :external_tag, foreign_key: :tag_id, inverse_of: :nico_tag_versions
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
end
|
end
|
||||||
|
|||||||
+24
-13
@@ -92,6 +92,9 @@ class Post < ApplicationRecord
|
|||||||
inverse_of: :parent_post
|
inverse_of: :parent_post
|
||||||
has_many :children, through: :child_post_implications, source: :post
|
has_many :children, through: :child_post_implications, source: :post
|
||||||
|
|
||||||
|
has_many :post_external_tags, dependent: :delete_all
|
||||||
|
has_many :external_tags, through: :post_external_tags
|
||||||
|
|
||||||
has_one_attached :thumbnail
|
has_one_attached :thumbnail
|
||||||
|
|
||||||
attribute :version_no, :integer, default: 1
|
attribute :version_no, :integer, default: 1
|
||||||
@@ -150,19 +153,27 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def snapshot_tags_json
|
def snapshot_tags_json
|
||||||
post_tags
|
tag_snapshots =
|
||||||
.joins(tag: :tag_name)
|
post_tags
|
||||||
.includes(:sections, tag: :tag_name)
|
.joins(tag: :tag_name)
|
||||||
.order('tags.id')
|
.includes(:sections, tag: :tag_name)
|
||||||
.map do |pt|
|
.order('tags.id')
|
||||||
{ 'id' => pt.tag.id,
|
.map { |pt|
|
||||||
'version_no' => pt.tag.version_no,
|
{ 'tag_id' => pt.tag.id,
|
||||||
'name' => pt.tag.name,
|
'version_no' => pt.tag.version_no,
|
||||||
'category' => pt.tag.category,
|
'name' => pt.tag.name,
|
||||||
'sections' => pt.sections.sort_by(&:begin_ms).map {
|
'category' => pt.tag.category,
|
||||||
{ 'begin_ms' => _1.begin_ms, 'end_ms' => _1.end_ms }
|
'sections' => pt.sections.sort_by(&:begin_ms).map {
|
||||||
} }
|
{ 'begin_ms' => _1.begin_ms, 'end_ms' => _1.end_ms }
|
||||||
end
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
external_tag_snapshots =
|
||||||
|
post_external_tags.order(:external_tag_id).map {
|
||||||
|
{ 'external_tag_id' => _1.external_tag_id }
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_snapshots + external_tag_snapshots
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.section_literal section
|
def self.section_literal section
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
class PostExternalTag < ApplicationRecord
|
||||||
|
belongs_to :post
|
||||||
|
belongs_to :external_tag
|
||||||
|
end
|
||||||
+8
-28
@@ -31,10 +31,6 @@ class Tag < ApplicationRecord
|
|||||||
has_many :nico_tag_relations, foreign_key: :nico_tag_id, dependent: :destroy
|
has_many :nico_tag_relations, foreign_key: :nico_tag_id, dependent: :destroy
|
||||||
has_many :linked_tags, through: :nico_tag_relations, source: :tag
|
has_many :linked_tags, through: :nico_tag_relations, source: :tag
|
||||||
|
|
||||||
has_many :reversed_nico_tag_relations,
|
|
||||||
class_name: 'NicoTagRelation', foreign_key: :tag_id, dependent: :destroy
|
|
||||||
has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag
|
|
||||||
|
|
||||||
has_many :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
|
has_many :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
|
||||||
has_many :children, through: :tag_implications, source: :tag
|
has_many :children, through: :tag_implications, source: :tag
|
||||||
|
|
||||||
@@ -50,7 +46,6 @@ class Tag < ApplicationRecord
|
|||||||
has_many :materials
|
has_many :materials
|
||||||
|
|
||||||
has_many :tag_versions
|
has_many :tag_versions
|
||||||
has_many :nico_tag_versions
|
|
||||||
|
|
||||||
belongs_to :tag_name
|
belongs_to :tag_name
|
||||||
delegate :wiki_page, to: :tag_name
|
delegate :wiki_page, to: :tag_name
|
||||||
@@ -65,17 +60,15 @@ class Tag < ApplicationRecord
|
|||||||
character: 'character',
|
character: 'character',
|
||||||
general: 'general',
|
general: 'general',
|
||||||
material: 'material',
|
material: 'material',
|
||||||
nico: 'nico',
|
|
||||||
meta: 'meta'
|
meta: 'meta'
|
||||||
|
|
||||||
validates :category, presence: true, inclusion: { in: Tag.categories.keys }
|
validates :category, presence: true, inclusion: { in: Tag.categories.keys }
|
||||||
|
|
||||||
validate :nico_tag_name_must_start_with_nico
|
validate :tag_name_mustnt_start_with_nico
|
||||||
validate :tag_name_must_be_canonical
|
validate :tag_name_must_be_canonical
|
||||||
validate :category_must_be_deerjikist_with_deerjikists
|
validate :category_must_be_deerjikist_with_deerjikists
|
||||||
validate :nico_tags_cannot_be_deprecated
|
|
||||||
|
|
||||||
scope :nico_tags, -> { nico }
|
def self.nico_tags = ExternalTag.where(platform: :nico)
|
||||||
|
|
||||||
CATEGORY_PREFIXES = {
|
CATEGORY_PREFIXES = {
|
||||||
'general:' => :general,
|
'general:' => :general,
|
||||||
@@ -110,10 +103,9 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
def self.normalise_tags! tag_names, with_tagme: true,
|
def self.normalise_tags! tag_names, with_tagme: true,
|
||||||
with_no_deerjikist: true,
|
with_no_deerjikist: true,
|
||||||
deny_nico: true,
|
|
||||||
deny_deprecated: false,
|
deny_deprecated: false,
|
||||||
with_sections: false
|
with_sections: false
|
||||||
if deny_nico && 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
|
||||||
|
|
||||||
@@ -273,12 +265,8 @@ class Tag < ApplicationRecord
|
|||||||
TagVersioning.record!(source_tag, event_type: :discard, created_by_user:)
|
TagVersioning.record!(source_tag, event_type: :discard, created_by_user:)
|
||||||
source_tag.destroy!
|
source_tag.destroy!
|
||||||
|
|
||||||
if source_tag.nico?
|
source_tag_name.update_columns(canonical_id: target_tag.tag_name_id,
|
||||||
source_tag_name.destroy!
|
updated_at: Time.current)
|
||||||
else
|
|
||||||
source_tag_name.update_columns(canonical_id: target_tag.tag_name_id,
|
|
||||||
updated_at: Time.current)
|
|
||||||
end
|
|
||||||
|
|
||||||
TagVersioning.record!(target_tag, event_type: :update, created_by_user:)
|
TagVersioning.record!(target_tag, event_type: :update, created_by_user:)
|
||||||
end
|
end
|
||||||
@@ -305,11 +293,9 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def nico_tag_name_must_start_with_nico
|
def tag_name_mustnt_start_with_nico
|
||||||
n = name.to_s
|
if name.to_s.downcase.start_with?('nico:')
|
||||||
if ((nico? && !(n.downcase.start_with?('nico:'))) ||
|
errors.add :name, 'タグの命名規則に反してゐます.'
|
||||||
(!(nico?) && n.downcase.start_with?('nico:')))
|
|
||||||
errors.add :name, 'ニコニコ・タグの命名規則に反してゐます.'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -350,10 +336,4 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
total_s * 1_000 + match[:ms].to_s.ljust(3, '0')[0, 3].to_i
|
total_s * 1_000 + match[:ms].to_s.ljust(3, '0')[0, 3].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def nico_tags_cannot_be_deprecated
|
|
||||||
if nico? && deprecated_at.present?
|
|
||||||
errors.add :deprecated_at, 'ニコタグは廃止できません.'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class NicoTagVersionRecorder < VersionRecorder
|
|||||||
def record_key = :tag
|
def record_key = :tag
|
||||||
|
|
||||||
def snapshot_attributes
|
def snapshot_attributes
|
||||||
{ name: @record.name, linked_tags: @record.snapshot_linked_tag_names.join(' ') }
|
{ name: "#{ @record.platform }:#{ @record.name }",
|
||||||
|
linked_tags: @record.snapshot_linked_tag_names.join(' ') }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,23 +20,22 @@ class PostCreatePlan
|
|||||||
parent_post_ids = normalise_parent_post_ids
|
parent_post_ids = normalise_parent_post_ids
|
||||||
validate_parent_post_ids!(parent_post_ids)
|
validate_parent_post_ids!(parent_post_ids)
|
||||||
|
|
||||||
{
|
{ url: @attributes[:url],
|
||||||
url: @attributes[:url],
|
title: @attributes[:title].to_s,
|
||||||
title: @attributes[:title].to_s,
|
thumbnail_base: @attributes[:thumbnail_base].presence,
|
||||||
thumbnail_base: @attributes[:thumbnail_base].presence,
|
original_created_from: @attributes[:original_created_from].presence,
|
||||||
original_created_from: @attributes[:original_created_from].presence,
|
original_created_before: @attributes[:original_created_before].presence,
|
||||||
original_created_before: @attributes[:original_created_before].presence,
|
tags: serialised_tags(direct_tag_specs, tag_sections),
|
||||||
tags: serialised_tags(direct_tag_specs, tag_sections),
|
display_tags: display_tags(direct_tag_specs, tag_sections),
|
||||||
display_tags: display_tags(direct_tag_specs, tag_sections),
|
duration: @attributes[:duration].to_s,
|
||||||
duration: @attributes[:duration].to_s,
|
video_ms: video_ms,
|
||||||
video_ms: video_ms,
|
parent_post_ids: parent_post_ids.join(' '),
|
||||||
parent_post_ids: parent_post_ids.join(' '),
|
direct_tag_specs: direct_tag_specs,
|
||||||
direct_tag_specs: direct_tag_specs,
|
default_tag_specs: default_tag_specs,
|
||||||
default_tag_specs: default_tag_specs,
|
snapshot_tag_specs: snapshot_tag_specs,
|
||||||
snapshot_tag_specs: snapshot_tag_specs,
|
post_tag_specs: post_tag_specs,
|
||||||
post_tag_specs: post_tag_specs,
|
tag_sections: tag_sections,
|
||||||
tag_sections: tag_sections,
|
normalised_parent_post_ids: parent_post_ids }
|
||||||
normalised_parent_post_ids: parent_post_ids }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -50,7 +49,6 @@ class PostCreatePlan
|
|||||||
tag_names.each do |raw_name|
|
tag_names.each do |raw_name|
|
||||||
tag_name, category, sections = parse_raw_tag_name(raw_name)
|
tag_name, category, sections = parse_raw_tag_name(raw_name)
|
||||||
existing_tag = existing_tags_by_name[tag_name]
|
existing_tag = existing_tags_by_name[tag_name]
|
||||||
raise Tag::NicoTagNormalisationError if existing_tag&.nico?
|
|
||||||
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 << {
|
||||||
@@ -139,14 +137,14 @@ class PostCreatePlan
|
|||||||
Tag.expand_parent_tags(existing_snapshot_tags)
|
Tag.expand_parent_tags(existing_snapshot_tags)
|
||||||
.reject(&:deprecated?)
|
.reject(&:deprecated?)
|
||||||
.map { |tag|
|
.map { |tag|
|
||||||
{
|
{ name: tag.name,
|
||||||
name: tag.name,
|
category: tag.category.to_sym }
|
||||||
category: tag.category.to_sym } }
|
}
|
||||||
merge_tag_specs(snapshot_tag_specs + expanded_parent_specs)
|
merge_tag_specs(snapshot_tag_specs + expanded_parent_specs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_tag_specs specs
|
def merge_tag_specs specs
|
||||||
specs.each_with_object({ }) do |spec, merged|
|
specs.each_with_object({ }) { |spec, merged|
|
||||||
merged[spec[:name]] =
|
merged[spec[:name]] =
|
||||||
if merged.key?(spec[:name]) && merged[spec[:name]][:category] != :general
|
if merged.key?(spec[:name]) && merged[spec[:name]][:category] != :general
|
||||||
merged[spec[:name]]
|
merged[spec[:name]]
|
||||||
@@ -155,7 +153,7 @@ class PostCreatePlan
|
|||||||
name: spec[:name],
|
name: spec[:name],
|
||||||
category: spec[:category] }
|
category: spec[:category] }
|
||||||
end
|
end
|
||||||
end.values.sort_by { _1[:name] }
|
}.values.sort_by { _1[:name] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def existing_tags_by_name
|
def existing_tags_by_name
|
||||||
|
|||||||
@@ -1,32 +1,16 @@
|
|||||||
class TagVersioning
|
class TagVersioning
|
||||||
def self.record! tag, event_type:, created_by_user:
|
def self.record! tag, event_type:, created_by_user:
|
||||||
if tag.nico?
|
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
|
||||||
else
|
|
||||||
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ensure_snapshot! tag, created_by_user:
|
def self.ensure_snapshot! tag, created_by_user:
|
||||||
if tag.nico?
|
return if tag.tag_versions.exists?
|
||||||
return if tag.nico_tag_versions.exists?
|
|
||||||
|
|
||||||
NicoTagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
TagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
||||||
else
|
|
||||||
return if tag.tag_versions.exists?
|
|
||||||
|
|
||||||
TagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.record_tag_snapshot! tag, created_by_user:
|
def self.record_tag_snapshot! tag, created_by_user:
|
||||||
event_type =
|
event_type = tag.tag_versions.exists? ? :update : :create
|
||||||
if tag.nico?
|
|
||||||
tag.nico_tag_versions.exists? ? :update : :create
|
|
||||||
else
|
|
||||||
tag.tag_versions.exists? ? :update : :create
|
|
||||||
end
|
|
||||||
|
|
||||||
record!(tag, event_type:, created_by_user:)
|
record!(tag, event_type:, created_by_user:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
生成ファイル
+20
-3
@@ -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_21_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
|
||||||
@@ -48,6 +48,14 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_030000) do
|
|||||||
t.index ["tag_id"], name: "index_deerjikists_on_tag_id"
|
t.index ["tag_id"], name: "index_deerjikists_on_tag_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "external_tags", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
|
t.string "platform", limit: 16, null: false
|
||||||
|
t.string "name", null: false
|
||||||
|
t.integer "post_count", default: 0, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.index ["platform", "name"], name: "index_external_tags_on_platform_and_name", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "gekanator_ai_runs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "gekanator_ai_runs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "model", null: false
|
t.string "model", null: false
|
||||||
t.integer "input_tokens", default: 0, null: false
|
t.integer "input_tokens", default: 0, null: false
|
||||||
@@ -281,6 +289,13 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_030000) do
|
|||||||
t.check_constraint "`version_no` > 0", name: "nico_tag_versions_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "nico_tag_versions_version_no_positive"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "post_external_tags", primary_key: ["post_id", "external_tag_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
|
t.bigint "post_id", null: false
|
||||||
|
t.bigint "external_tag_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.index ["external_tag_id"], name: "index_post_external_tags_on_external_tag_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "post_implications", primary_key: ["post_id", "parent_post_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "post_implications", primary_key: ["post_id", "parent_post_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "post_id", null: false
|
t.bigint "post_id", null: false
|
||||||
t.bigint "parent_post_id", null: false
|
t.bigint "parent_post_id", null: false
|
||||||
@@ -352,7 +367,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_030000) do
|
|||||||
t.check_constraint "(`video_ms` is null) or (`video_ms` > 0)", name: "chk_post_versions_video_ms_positive"
|
t.check_constraint "(`video_ms` is null) or (`video_ms` > 0)", name: "chk_post_versions_video_ms_positive"
|
||||||
t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')", name: "post_versions_event_type_valid"
|
t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')", name: "post_versions_event_type_valid"
|
||||||
t.check_constraint "`version_no` > 0", name: "post_versions_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "post_versions_version_no_positive"
|
||||||
t.check_constraint "json_schema_valid(_utf8mb4'{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\",\"minimum\":1},\"version_no\":{\"type\":\"integer\",\"minimum\":1},\"name\":{\"type\":\"string\",\"minLength\":1},\"category\":{\"type\":\"string\",\"enum\":[\"deerjikist\",\"meme\",\"character\",\"general\",\"material\",\"meta\",\"nico\"]},\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"begin_ms\":{\"type\":\"integer\",\"minimum\":0},\"end_ms\":{\"type\":[\"integer\",\"null\"],\"minimum\":0}},\"required\":[\"begin_ms\",\"end_ms\"],\"additionalProperties\":false}}},\"required\":[\"id\",\"version_no\",\"name\",\"category\",\"sections\"],\"additionalProperties\":false}}',`tags_json`)", name: "chk_post_versions_tags_json_schema"
|
t.check_constraint "json_schema_valid(_utf8mb4'{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"object\",\"properties\":{\"tag_id\":{\"type\":\"integer\",\"minimum\":1},\"version_no\":{\"type\":\"integer\",\"minimum\":1},\"name\":{\"type\":\"string\",\"minLength\":1},\"category\":{\"type\":\"string\",\"enum\":[\"deerjikist\",\"meme\",\"character\",\"general\",\"material\",\"meta\"]},\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"begin_ms\":{\"type\":\"integer\",\"minimum\":0},\"end_ms\":{\"type\":[\"integer\",\"null\"],\"minimum\":0}},\"required\":[\"begin_ms\",\"end_ms\"],\"additionalProperties\":false}}},\"required\":[\"tag_id\",\"version_no\",\"sections\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"external_tag_id\":{\"type\":\"integer\",\"minimum\":1}},\"required\":[\"external_tag_id\"],\"additionalProperties\":false}]}}',`tags_json`)", name: "chk_post_versions_tags_json_schema"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
@@ -700,9 +715,11 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_030000) do
|
|||||||
add_foreign_key "materials", "tags"
|
add_foreign_key "materials", "tags"
|
||||||
add_foreign_key "materials", "users", column: "created_by_user_id"
|
add_foreign_key "materials", "users", column: "created_by_user_id"
|
||||||
add_foreign_key "materials", "users", column: "updated_by_user_id"
|
add_foreign_key "materials", "users", column: "updated_by_user_id"
|
||||||
|
add_foreign_key "nico_tag_relations", "external_tags", column: "nico_tag_id"
|
||||||
add_foreign_key "nico_tag_relations", "tags"
|
add_foreign_key "nico_tag_relations", "tags"
|
||||||
add_foreign_key "nico_tag_relations", "tags", column: "nico_tag_id"
|
|
||||||
add_foreign_key "nico_tag_versions", "users", column: "created_by_user_id"
|
add_foreign_key "nico_tag_versions", "users", column: "created_by_user_id"
|
||||||
|
add_foreign_key "post_external_tags", "external_tags"
|
||||||
|
add_foreign_key "post_external_tags", "posts"
|
||||||
add_foreign_key "post_implications", "posts"
|
add_foreign_key "post_implications", "posts"
|
||||||
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
||||||
add_foreign_key "post_similarities", "posts"
|
add_foreign_key "post_similarities", "posts"
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace :nico do
|
|||||||
# 既存のタグ Id. 集合
|
# 既存のタグ Id. 集合
|
||||||
kept_tag_ids = tags.pluck(:id).to_set
|
kept_tag_ids = tags.pluck(:id).to_set
|
||||||
# うち内部タグ Id. 集合
|
# うち内部タグ Id. 集合
|
||||||
kept_non_nico_tag_ids = tags.not_nico.pluck(:id).to_set
|
kept_non_nico_tag_ids = tags.pluck(:id).to_set
|
||||||
|
|
||||||
# 記載すべき外部タグ Id. および連携される内部タグ Id. のリスト
|
# 記載すべき外部タグ Id. および連携される内部タグ Id. のリスト
|
||||||
desired_nico_tag_based_ids = []
|
desired_nico_tag_based_ids = []
|
||||||
@@ -123,11 +123,11 @@ namespace :nico do
|
|||||||
desired_non_nico_tag_ids = []
|
desired_non_nico_tag_ids = []
|
||||||
|
|
||||||
datum['tags'].each do |raw|
|
datum['tags'].each do |raw|
|
||||||
name = TagNameSanitisationRule.sanitise("nico:#{ raw }")
|
tag = ExternalTag.find_or_create_by!(platform: :nico, name: raw)
|
||||||
tag = Tag.find_or_create_by_tag_name!(name, category: :nico)
|
|
||||||
|
|
||||||
event_type = tag.nico_tag_versions.exists? ? :update : :create
|
unless tag.nico_tag_versions.exists?
|
||||||
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user: nil)
|
NicoTagVersionRecorder.record!(tag:, event_type: :create, created_by_user: nil)
|
||||||
|
end
|
||||||
|
|
||||||
desired_nico_tag_based_ids << tag.id
|
desired_nico_tag_based_ids << tag.id
|
||||||
|
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする