ruby-on-rails 如何使用Ransack对单个字段使用多个复选框作为AND查询(而不是OR)进行过滤?

aydmsdu9  于 2023-03-24  发布在  Ruby
关注(0)|答案(1)|浏览(110)

我希望用户能够选择多个不同的标签,以过滤公园的结果。例如,他们应该能够找到一个公园,有以下3个标签:
1.婴儿车友好
1.很多阴凉处
1.野餐桌
我使用Ransack实现了一个表单,用户可以选择多个标签,但它被视为OR查询(返回带有任何标签的公园),而不是AND查询(返回带有所有标签的公园)。
我认为问题是所有的复选框都使用相同的tags_id_in字段。我如何使它作为AND查询工作?
表格

<%= search_form_for @q, url: :parks do |f| %>
  <span class="me-5">
    <% @tags.each do |tag| %>
      <%= f.check_box :tags_id_in, { multiple: true, include_hidden: false, class: "btn-check", id: tag.name.titleize + "Checkbox" }, tag.id %>
      <%= f.label :tags_id_in, tag.name.titleize, class: 'btn btn-outline-secondary border mb-1', for: tag.name.titleize + "Checkbox" %>
    <% end %>
  </span>

  <span class="me-5">
     <%= f.submit 'Apply filters', class: "btn btn-secondary" %>
  </span>

<% end %>

来自日志:

Started GET "/en/parks?q%5Btags_id_in%5D%5B%5D=16&q%5Btags_id_in%5D%5B%5D=19&q%5Btags_id_in%5D%5B%5D=21&commit=Apply+filters" for ::1 at 2023-02-05 09:56:58 +0200
Processing by ParksController#index as HTML
  Parameters: {"q"=>{"tags_id_in"=>["16", "19", "21"]}, "commit"=>"Apply filters", "locale"=>"en"}

  Park Count (19.3ms)  SELECT COUNT(*) FROM (SELECT DISTINCT parks.*, 6371.0 * 2 * ASIN(SQRT(POWER(SIN((32.0908425 - parks.latitude) * PI() / 180 / 2), 2) + COS(32.0908425 * PI() / 180) * COS(parks.latitude * PI() / 180) * POWER(SIN((34.8154259 - parks.longitude) * PI() / 180 / 2), 2))) AS distance, MOD(CAST((ATAN2( ((parks.longitude - 34.8154259) / 57.2957795), ((parks.latitude - 32.0908425) / 57.2957795)) * 57.2957795) + 360 AS decimal), 360) AS bearing FROM "parks" LEFT OUTER JOIN "tagifications" ON "tagifications"."park_id" = "parks"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "tagifications"."tag_id" WHERE "tags"."id" IN (16, 19, 21) AND (parks.latitude BETWEEN -899289.5150762305 AND 899353.6967612305 AND parks.longitude BETWEEN -1061478.8079855754 AND 1061548.4388373755 AND (6371.0 * 2 * ASIN(SQRT(POWER(SIN((32.0908425 - parks.latitude) * PI() / 180 / 2), 2) + COS(32.0908425 * PI() / 180) * COS(parks.latitude * PI() / 180) * POWER(SIN((34.8154259 - parks.longitude) * PI() / 180 / 2), 2)))) BETWEEN 0.0 AND 100000000)) subquery_for_count

更新:我还尝试使用in_all,我认为根据文档应该可以完成我想要的任务。但是,如果选择了多个标记,我就没有结果。生成的SQL查询是...WHERE...("tags"."id" IN (21) AND "tags"."id" IN (22))...
看起来像这里报告的相同问题:github.com/activerecord-hackery/ransack/issues/108

shstlldc

shstlldc1#

生成的SQL显示使用了IN (16,19,21),这被视为一系列OR条件。
为了实现一系列AND条件的等效性,可以考虑使用额外的group by和count,例如

SELECT parks.id
FROM parks
INNER JOIN parks_tags ON parks.id = parks_tags.park_id
INNER JOIN tags ON tags.id = parks_tags.tag_id
WHERE tags.id IN (16,19,21)
GROUP BY parks.id
HAVING COUNT(*) = 3

你必须得到所有3个标签行才能得到3的计数。当然,这是假设你不会有任何标签重复的公园。
以下内容未经验证,但在控制器代码中使用如下逻辑:

# In the ParksController
def index
  @q = Park.ransack(params[:q])
  @parks = @q.result

  # If tags are selected
  if params[:q] && params[:q][:tags_id_in].present?
    # Find parks that have all of the selected tags
    tag_ids = params[:q][:tags_id_in]
    park_ids = @parks.joins(:tags).where(tags: { id: tag_ids }).group('parks.id').having('COUNT(*) = ?', tag_ids.size).pluck(:id)
    @parks = Park.where(id: park_ids)
  end

  # etc.
end

相关问题