From 3155d0ee40d51a000400f52d0c882669c41b559d Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 8 Mar 2026 18:05:38 +0900 Subject: [PATCH] =?UTF-8?q?#68=20=E3=81=BE=E3=81=A5=E3=81=AF=20NOT=20?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/controllers/posts_controller.rb | 40 ++++++++++++++++----- backend/app/controllers/tags_controller.rb | 3 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index f9f68f5..75b380b 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -84,7 +84,7 @@ class PostsController < ApplicationController title = params[:title].presence url = params[:url] thumbnail = params[:thumbnail] - tag_names = params[:tags].to_s.split(' ') + tag_names = params[:tags].to_s.split original_created_from = params[:original_created_from] original_created_before = params[:original_created_before] @@ -125,7 +125,7 @@ class PostsController < ApplicationController return head :forbidden unless current_user.gte_member? title = params[:title].presence - tag_names = params[:tags].to_s.split(' ') + tag_names = params[:tags].to_s.split original_created_from = params[:original_created_from] original_created_before = params[:original_created_before] @@ -192,7 +192,7 @@ class PostsController < ApplicationController private def filtered_posts - tag_names = params[:tags].to_s.split(' ') + tag_names = params[:tags].to_s.split match_type = params[:match] if tag_names.present? filter_posts_by_tags(tag_names, match_type) @@ -202,19 +202,41 @@ class PostsController < ApplicationController end def filter_posts_by_tags tag_names, match_type - tag_names = TagName.canonicalise(tag_names) + literals = tag_names.map do |raw_name| + { name: TagName.canonicalise(raw_name.sub(/\Anot:/i, '')).first, + negative: raw_name.downcase.start_with?('not:') } + end - posts = Post.joins(tags: :tag_name) + return Post.all if literals.empty? if match_type == 'any' - posts.where(tag_names: { name: tag_names }).distinct + literals.reduce(Post.none) do |posts, literal| + posts.or(tag_literal_relation(literal[:name], negative: literal[:negative])) + end + else + literals.reduce(Post.all) do |posts, literal| + ids = tagged_post_ids_for(literal[:name]) + if literal[:negative] + posts.where.not(id: ids) + else + posts.where(id: ids) + end + end + end + end + + def tag_literal_relation name, negative: + ids = tagged_post_ids_for(name) + if negative + Post.where.not(id: ids) else - posts.where(tag_names: { name: tag_names }) - .group('posts.id') - .having('COUNT(DISTINCT tag_names.id) = ?', tag_names.uniq.size) + Post.where(id: ids) end end + def tagged_post_ids_for(name) = + Post.joins(tags: :tag_name).where(tag_names: { name: }).select(:id) + def sync_post_tags! post, desired_tags desired_tags.each do |t| t.save! if t.new_record? diff --git a/backend/app/controllers/tags_controller.rb b/backend/app/controllers/tags_controller.rb index bf4c821..b97fcaa 100644 --- a/backend/app/controllers/tags_controller.rb +++ b/backend/app/controllers/tags_controller.rb @@ -17,8 +17,7 @@ class TagsController < ApplicationController end def autocomplete - q = params[:q].to_s.strip - return render json: [] if q.blank? + q = params[:q].to_s.strip.sub(/\Anot:/i, '') with_nico = bool?(:nico, default: true) present_only = bool?(:present, default: true)