ActiveRecord запрашивает несколько с AND вместо OR в отношении has_and_belongs_to_many - PullRequest
1 голос
/ 04 октября 2019

Я пытаюсь настроить поиск для моего приложения Rails.

Есть 2 модели с отношением has_and_belongs_to_many:

class Post
  has_and_belongs_to_many :tags
end

class Tag
  has_and_belongs_to_many :post
end

Для подтверждения вот таблицы:

create_table "posts" do |t|
  t.bigint "user_id"
  t.string "title"
  ...
end

create_table "tags" do |t|
  t.string "name"
end

create_table "tags_posts", id: false do |t|
  t.bigint "post_id", null: false
  t.bigint "tag_id", null: false
  t.index ["post_id", "tag_id"]
  t.index ["tag_id", "post_id"]
end

Я хочу сделать запрос AND: All POSTS that have TAGS 1, 2, AND 3.

Наиболее близким, что я получил, является этот простой запрос, но он возвращает OR вместо AND - всесообщения, которые имеют 1 из этих 3 тегов. Post.includes(:tags).where(tags: { id: [1, 2, 3] }).

Как получить запрос, подобный этому, где я могу легко добавить N номеров идентификаторов тегов в запрос ActiveRecord и сделать его запросом AND.

Бонусный вопрос- есть ли способ добавить параметр, в котором он возвращает сообщения, для которых в сообщении существует не менее M тегов. Итак ALL POSTS that have AT LEAST 2 TAGS of 1, 2, AND 3?

Обновление

Я нашел это: https://stackoverflow.com/a/7994175/659820

Для перевода:

Post.find_by_sql("
  SELECT p.* FROM posts p,
    post_tags pt1,
    post_tags pt2,
    tags t1,
    tags t2
  WHERE
    p.id = pt1.post_id
  AND t1.id = 4
  AND t1.id = pt1.post_tag_id
  AND p.id = pt2.post_id
  AND t2.id = 11
  AND t2.id = pt2.post_tag_id
")
  1. Этосамый эффективный способ сделать это?
  2. Бонусный вопрос до сих пор остается без ответа - дать несколько M совпадений для предоставленных N тегов?

1 Ответ

1 голос
/ 04 октября 2019

Вы можете решить эту проблему с помощью таблицы соединений. Чтобы получить правильные сообщения с помощью таблицы соединений, нам нужно посмотреть количество сообщений, соответствующих условию:

tag_ids = [1, 2, 3] # You can use tag name instead of id
Post.joins(:tags).where(tags: { id: tag_ids }).group(:id).having("count(*) = ?", tag_ids.size)

ОБНОВЛЕНИЕ

Это должноработа:

class Post < ApplicationRecord
  def self.tagged_with(post_tags, m = nil)
    m ||= post_tags.size
    Post.joins(:tags).where(tags: { id: post_tags }).group(:id).having("count(*) >= ?", m)
  end
end

tag_ids = [1, 2, 3]
Post.tagged_with(tag_ids)
#=> Returns posts which has all tags.
Post.tagged_with(tag_ids, 2)
#=> Returns posts which has at least two of given tags.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...