このコミットが含まれているのは:
@@ -1,5 +1,6 @@
|
|||||||
require 'net/http'
|
require 'net/http'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require 'set'
|
||||||
|
|
||||||
|
|
||||||
class TagsController < ApplicationController
|
class TagsController < ApplicationController
|
||||||
@@ -82,51 +83,27 @@ class TagsController < ApplicationController
|
|||||||
parent_tag_id = params[:parent].to_i
|
parent_tag_id = params[:parent].to_i
|
||||||
parent_tag_id = nil if parent_tag_id <= 0
|
parent_tag_id = nil if parent_tag_id <= 0
|
||||||
|
|
||||||
|
graph = build_with_depth_graph
|
||||||
|
|
||||||
tag_ids =
|
tag_ids =
|
||||||
if parent_tag_id
|
if parent_tag_id
|
||||||
TagImplication.joins(:tag)
|
visible_child_tag_ids(parent_tag_id, graph)
|
||||||
.where(parent_tag_id:)
|
|
||||||
.where(tags: { deprecated_at: nil })
|
|
||||||
.select(:tag_id)
|
|
||||||
else
|
else
|
||||||
Tag.where(deprecated_at: nil)
|
visible_root_tag_ids(graph)
|
||||||
.where.not(id: TagImplication
|
|
||||||
.joins(<<~SQL.squish)
|
|
||||||
INNER JOIN
|
|
||||||
tags parent_tags
|
|
||||||
ON parent_tags.id = tag_implications.parent_tag_id
|
|
||||||
SQL
|
|
||||||
.where('parent_tags.deprecated_at IS NULL')
|
|
||||||
.select(:tag_id))
|
|
||||||
.select(:id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
tags =
|
tags =
|
||||||
Tag
|
Tag
|
||||||
.joins(:tag_name)
|
.joins(:tag_name)
|
||||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||||
.where(category: [:meme, :character, :material])
|
|
||||||
.where(deprecated_at: nil)
|
|
||||||
.where(id: tag_ids)
|
.where(id: tag_ids)
|
||||||
.order('tag_names.name')
|
.order('tag_names.name')
|
||||||
.distinct
|
.distinct
|
||||||
.to_a
|
.to_a
|
||||||
|
|
||||||
has_children_tag_ids =
|
|
||||||
if tags.empty?
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
TagImplication
|
|
||||||
.joins(:tag)
|
|
||||||
.where(parent_tag_id: tags.map(&:id),
|
|
||||||
tags: { category: [:meme, :character, :material],
|
|
||||||
deprecated_at: nil })
|
|
||||||
.distinct
|
|
||||||
.pluck(:parent_tag_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: tags.map { |tag|
|
render json: tags.map { |tag|
|
||||||
TagRepr.base(tag).merge(has_children: has_children_tag_ids.include?(tag.id), children: [])
|
TagRepr.base(tag).merge(has_children: visible_child_tag_ids(tag.id, graph).present?,
|
||||||
|
children: [])
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -371,6 +348,93 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def build_with_depth_graph
|
||||||
|
children_by_parent_id = Hash.new { |h, k| h[k] = [] }
|
||||||
|
parent_ids_by_child_id = Hash.new { |h, k| h[k] = [] }
|
||||||
|
|
||||||
|
TagImplication.pluck(:parent_tag_id, :tag_id).each do |parent_id, child_id|
|
||||||
|
children_by_parent_id[parent_id] << child_id
|
||||||
|
parent_ids_by_child_id[child_id] << parent_id
|
||||||
|
end
|
||||||
|
|
||||||
|
tag_ids = (children_by_parent_id.keys +
|
||||||
|
parent_ids_by_child_id.keys +
|
||||||
|
Tag.where(category: ['meme', 'character', 'material']).pluck(:id)).uniq
|
||||||
|
|
||||||
|
tags_by_id = Tag.where(id: tag_ids)
|
||||||
|
.pluck(:id, :category, :deprecated_at)
|
||||||
|
.each_with_object({ }) do |(id, category, deprecated_at), h|
|
||||||
|
h[id] = { category:, deprecated: deprecated_at.present? }
|
||||||
|
end
|
||||||
|
|
||||||
|
{ children_by_parent_id:, parent_ids_by_child_id:, tags_by_id:,
|
||||||
|
visible_child_tag_ids_by_parent_id: { } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible_root_tag_ids graph
|
||||||
|
graph[:tags_by_id].filter_map do |tag_id, attrs|
|
||||||
|
next unless with_depth_visible_tag?(attrs)
|
||||||
|
next unless visible_root_tag?(tag_id, graph)
|
||||||
|
|
||||||
|
tag_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible_root_tag? tag_id, graph
|
||||||
|
seen = Set.new([tag_id])
|
||||||
|
stack = graph[:parent_ids_by_child_id][tag_id].dup
|
||||||
|
|
||||||
|
until stack.empty?
|
||||||
|
parent_id = stack.pop
|
||||||
|
next if seen.include?(parent_id)
|
||||||
|
|
||||||
|
seen << parent_id
|
||||||
|
|
||||||
|
parent = graph[:tags_by_id][parent_id]
|
||||||
|
next unless parent
|
||||||
|
|
||||||
|
return false unless parent[:deprecated]
|
||||||
|
|
||||||
|
stack.concat(graph[:parent_ids_by_child_id][parent_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible_child_tag_ids parent_tag_id, graph
|
||||||
|
cache = graph[:visible_child_tag_ids_by_parent_id]
|
||||||
|
return cache[parent_tag_id] if cache.key?(parent_tag_id)
|
||||||
|
|
||||||
|
visible_ids = Set.new
|
||||||
|
|
||||||
|
graph[:children_by_parent_id][parent_tag_id].each do |child_tag_id|
|
||||||
|
collect_visible_child_tag_ids(child_tag_id, graph, visible_ids, Set.new([parent_tag_id]))
|
||||||
|
end
|
||||||
|
|
||||||
|
cache[parent_tag_id] = visible_ids.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def collect_visible_child_tag_ids tag_id, graph, visible_ids, seen
|
||||||
|
return if seen.include?(tag_id)
|
||||||
|
|
||||||
|
seen = seen.dup << tag_id
|
||||||
|
tag = graph[:tags_by_id][tag_id]
|
||||||
|
return unless tag
|
||||||
|
|
||||||
|
if tag[:deprecated]
|
||||||
|
graph[:children_by_parent_id][tag_id].each do |child_tag_id|
|
||||||
|
collect_visible_child_tag_ids(child_tag_id, graph, visible_ids, seen)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
visible_ids << tag_id if with_depth_visible_tag?(tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_depth_visible_tag? tag
|
||||||
|
tag[:category].in?(['meme', 'character', 'material']) && !tag[:deprecated]
|
||||||
|
end
|
||||||
|
|
||||||
def build_tag_children tag
|
def build_tag_children tag
|
||||||
material = tag.materials.first
|
material = tag.materials.first
|
||||||
file = nil
|
file = nil
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする