diff --git a/app/logical/concerns/searchable.rb b/app/logical/concerns/searchable.rb index b0a7a4a2e..27fe0cf1f 100644 --- a/app/logical/concerns/searchable.rb +++ b/app/logical/concerns/searchable.rb @@ -9,6 +9,22 @@ module Searchable relation.where(all.where_clause.invert.ast) end + # Combine two relations like `ActiveRecord::Relation#and`, but allow structurally incompatible relations. + def and_relation(relation) + q = all + raise "incompatible FROM clauses: #{q.to_sql}; #{relation.to_sql}" if !q.from_clause.empty? && q.from_clause != relation.from_clause + raise "incompatible GROUP BY clauses: #{q.to_sql}; #{relation.to_sql}" if !q.group_values.empty? && q.group_values != relation.group_values + + q = q.select(q.select_values + relation.select_values) if !relation.select_values.empty? + q = q.from(relation.from_clause.value) if !relation.from_clause.empty? + q = q.joins(relation.joins_values + q.joins_values) if relation.joins_values.present? + q = q.where(relation.where_clause.ast) if relation.where_clause.present? + q = q.group(relation.group_values) if relation.group_values.present? + q = q.order(relation.order_values) if relation.order_values.present? && !relation.reordering_value + q = q.reorder(relation.order_values) if relation.order_values.present? && relation.reordering_value + q + end + # Search a table column by an Arel operator. # # @see https://github.com/rails/rails/blob/master/activerecord/lib/arel/predications.rb diff --git a/app/models/post.rb b/app/models/post.rb index 6cb2ce65d..83e494327 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1391,7 +1391,7 @@ class Post < ApplicationRecord post_query = PostQuery.normalize(query, current_user: user, tag_limit: tag_limit, safe_mode: safe_mode) post_query.validate_tag_limit! posts = post_query.with_implicit_metatags.posts - merge(posts) + and_relation(posts) end def search(params, current_user) @@ -1409,7 +1409,7 @@ class Post < ApplicationRecord ) if params[:tags].present? - q = q.user_tag_match(params[:tags], current_user) + q = q.where(id: user_tag_match(params[:tags], current_user).select(:id)) end if params[:order].present? diff --git a/test/functional/modqueue_controller_test.rb b/test/functional/modqueue_controller_test.rb index 8d5542e2f..ebb164c54 100644 --- a/test/functional/modqueue_controller_test.rb +++ b/test/functional/modqueue_controller_test.rb @@ -58,6 +58,16 @@ class ModqueueControllerTest < ActionDispatch::IntegrationTest assert_equal([], response.parsed_body.pluck("id")) end + should "filter the disapproved: metatag correctly" do + post1 = create(:post, is_pending: true) + post2 = create(:post, is_deleted: true) + create(:post_disapproval, post: post2, reason: "poor_quality") + + get_auth modqueue_index_path(search: { tags: "disapproved:poor_quality" }), @admin, as: :json + assert_response :success + assert_equal([], response.parsed_body.pluck("id")) + end + should "include appealed posts in the modqueue" do @appeal = create(:post_appeal) get_auth modqueue_index_path, @admin diff --git a/test/unit/post_query_builder_test.rb b/test/unit/post_query_builder_test.rb index 59fc281d4..7c94b0028 100644 --- a/test/unit/post_query_builder_test.rb +++ b/test/unit/post_query_builder_test.rb @@ -1,8 +1,8 @@ require 'test_helper' class PostQueryBuilderTest < ActiveSupport::TestCase - def assert_tag_match(posts, query, current_user: CurrentUser.user, tag_limit: nil, **options) - assert_equal(posts.map(&:id), Post.user_tag_match(query, current_user, tag_limit: tag_limit, **options).pluck("posts.id")) + def assert_tag_match(posts, query, relation: Post.all, current_user: CurrentUser.user, tag_limit: nil, **options) + assert_equal(posts.map(&:id), relation.user_tag_match(query, current_user, tag_limit: tag_limit, **options).pluck("posts.id")) end def assert_search_error(query, current_user: CurrentUser.user, **options) @@ -1481,6 +1481,25 @@ class PostQueryBuilderTest < ActiveSupport::TestCase assert_tag_match([post2, post1], "id:#{post1.id} or rating:q") end + should "work on a relation with pre-existing scopes" do + post1 = create(:post, rating: "g", is_pending: true, tag_string: ["1girl"]) + post2 = create(:post, rating: "s", is_flagged: true, tag_string: ["1boy"]) + create(:post_disapproval, post: post2, reason: "poor_quality") + + assert_tag_match([post1], "1girl", relation: Post.pending) + assert_tag_match([post1], "1girl", relation: Post.in_modqueue) + assert_tag_match([post1], "1boy", relation: Post.in_modqueue) + assert_tag_match([post2, post1], "comments:0", relation: Post.in_modqueue) + assert_tag_match([post2, post1], "comments:0 notes:0", relation: Post.in_modqueue) + + assert_tag_match([post2], "-1girl", relation: Post.in_modqueue) + assert_tag_match([post2], "disapproved:poor_quality", relation: Post.in_modqueue) + + assert_tag_match([], "rating:g", relation: Post.where(rating: "e")) + assert_tag_match([], "id:#{post1.id}", relation: Post.where(id: 0)) + assert_tag_match([], "order:artcomm", relation: Post.in_modqueue) + end + should "not allow conflicting order metatags" do assert_search_error("order:score ordfav:a") assert_search_error("order:score ordfavgroup:a")