| @@ -78,6 +78,7 @@ class PostsController < ApplicationController | |||||
| if post.save | if post.save | ||||
| post.resized_thumbnail! | post.resized_thumbnail! | ||||
| post.tags = Tag.normalise_tags(tag_names) | post.tags = Tag.normalise_tags(tag_names) | ||||
| post.tags = Tag.expand_parent_tags(post.tags) | |||||
| render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }), | render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }), | ||||
| status: :created | status: :created | ||||
| else | else | ||||
| @@ -111,6 +112,7 @@ class PostsController < ApplicationController | |||||
| post = Post.find(params[:id].to_i) | post = Post.find(params[:id].to_i) | ||||
| tags = post.tags.where(category: 'nico').to_a + Tag.normalise_tags(tag_names) | tags = post.tags.where(category: 'nico').to_a + Tag.normalise_tags(tag_names) | ||||
| tags = Tag.expand_parent_tags(tags) | |||||
| if post.update(title:, tags:, original_created_from:, original_created_before:) | if post.update(title:, tags:, original_created_from:, original_created_before:) | ||||
| render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }), | render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }), | ||||
| status: :ok | status: :ok | ||||
| @@ -11,6 +11,14 @@ class Tag < ApplicationRecord | |||||
| dependent: :destroy | dependent: :destroy | ||||
| has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag | 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 :children, through: :tag_implications, source: :tag | |||||
| has_many :reversed_tag_implications, class_name: 'TagImplication', | |||||
| foreign_key: :tag_id, | |||||
| dependent: :destroy | |||||
| has_many :parents, through: :reversed_tag_implications, source: :parent_tag | |||||
| enum :category, { deerjikist: 'deerjikist', | enum :category, { deerjikist: 'deerjikist', | ||||
| meme: 'meme', | meme: 'meme', | ||||
| character: 'character', | character: 'character', | ||||
| @@ -61,6 +69,29 @@ class Tag < ApplicationRecord | |||||
| tags.uniq | tags.uniq | ||||
| end | end | ||||
| def self.expand_parent_tags tags | |||||
| return [] if tags.blank? | |||||
| seen = Set.new | |||||
| result = [] | |||||
| stack = tags.compact.dup | |||||
| until stack.empty? | |||||
| tag = stack.pop | |||||
| next unless tag | |||||
| tag.parents.each do |parent| | |||||
| next if seen.include?(parent.id) | |||||
| seen << parent.id | |||||
| result << parent | |||||
| stack << parent | |||||
| end | |||||
| end | |||||
| (result + tags).uniq { |t| t.id } | |||||
| end | |||||
| private | private | ||||
| def nico_tag_name_must_start_with_nico | def nico_tag_name_must_start_with_nico | ||||
| @@ -0,0 +1,17 @@ | |||||
| class TagImplication < ApplicationRecord | |||||
| belongs_to :tag, class_name: 'Tag' | |||||
| belongs_to :parent_tag, class_name: 'Tag' | |||||
| validates :tag_id, presence: true | |||||
| validates :parent_tag_id, presence: true | |||||
| validate :parent_tag_mustnt_be_itself | |||||
| private | |||||
| def parent_tag_mustnt_be_itself | |||||
| if parent_tag == tag | |||||
| errors.add :parent_tag_id, '親タグは子タグと同一であってはなりません.' | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,9 @@ | |||||
| class CreateTagImplications < ActiveRecord::Migration[8.0] | |||||
| def change | |||||
| create_table :tag_implications do |t| | |||||
| t.references :tag, null: false, foreign_key: { to_table: :tags } | |||||
| t.references :parent_tag, null: false, foreign_key: { to_table: :tags } | |||||
| t.timestamps | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -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: 2025_09_09_075500) do | |||||
| ActiveRecord::Schema[8.0].define(version: 2025_10_09_222200) 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 | ||||
| @@ -107,6 +107,15 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do | |||||
| t.index ["tag_id"], name: "index_tag_aliases_on_tag_id" | t.index ["tag_id"], name: "index_tag_aliases_on_tag_id" | ||||
| end | end | ||||
| create_table "tag_implications", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | |||||
| t.bigint "tag_id", null: false | |||||
| t.bigint "parent_tag_id", null: false | |||||
| t.datetime "created_at", null: false | |||||
| t.datetime "updated_at", null: false | |||||
| t.index ["parent_tag_id"], name: "index_tag_implications_on_parent_tag_id" | |||||
| t.index ["tag_id"], name: "index_tag_implications_on_tag_id" | |||||
| end | |||||
| create_table "tag_similarities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | create_table "tag_similarities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | ||||
| t.bigint "tag_id", null: false | t.bigint "tag_id", null: false | ||||
| t.bigint "target_tag_id", null: false | t.bigint "target_tag_id", null: false | ||||
| @@ -176,6 +185,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do | |||||
| add_foreign_key "posts", "users", column: "uploaded_user_id" | add_foreign_key "posts", "users", column: "uploaded_user_id" | ||||
| add_foreign_key "settings", "users" | add_foreign_key "settings", "users" | ||||
| add_foreign_key "tag_aliases", "tags" | add_foreign_key "tag_aliases", "tags" | ||||
| add_foreign_key "tag_implications", "tags" | |||||
| add_foreign_key "tag_implications", "tags", column: "parent_tag_id" | |||||
| add_foreign_key "tag_similarities", "tags" | add_foreign_key "tag_similarities", "tags" | ||||
| add_foreign_key "tag_similarities", "tags", column: "target_tag_id" | add_foreign_key "tag_similarities", "tags", column: "target_tag_id" | ||||
| add_foreign_key "user_ips", "ip_addresses" | add_foreign_key "user_ips", "ip_addresses" | ||||