Как выбрать записи данных из таблицы соединений с несколькими опциями? - PullRequest
1 голос
/ 04 марта 2020

Я пишу простой онлайн-магазин на ruby и у меня проблема с фильтрацией товаров. Я пытаюсь фильтровать элементы по нескольким параметрам (например, «цвет»: «красный» и «бренд»: «zara»).

Я ожидаю увидеть только элементы с опциями 'red' и 'zara'.

Все в порядке, когда я фильтрую по одной опции. Но с несколькими опциями отображаются все элементы, у которых есть хотя бы одна из опций.

Моя область фильтрации в модели элементов:

scope :by_options,
    ->(options) { joins(:items_options).where(items_options: { option_id: options }).distinct }

Контроллер элементов:

class ItemsController < ApplicationController
  def index
    @items = Item.all

    filtering_params(params).each do |key, value|
      @items = @items.public_send("by_#{key}", value) if value.present?
    end
  end

  private
    def filtering_params(params)
      params.slice(:category, :options)
    end
  end

Сгенерированный запрос:

SELECT DISTINCT "items".* FROM "items" INNER JOIN "items_options" ON "items_options"."item_id" = "items"."id" WHERE "items_options"."option_id" IN (?, ?)  [["option_id", 1], ["option_id", 6]]

Я также дважды пытался отфильтровать по опциям, но в этом случае @items пустые (6 и 1 - просто примеры, потому что у меня есть элемент, у которого есть обе эти опции:

@items = Item.by_options(6).by_options(1)

Сгенерированный запрос:

SELECT DISTINCT "items".* FROM "items" INNER JOIN "items_options" ON "items_options"."item_id" = "items"."id" WHERE "items_options"."option_id" = ? AND "items_options"."option_id" = ?  [["option_id", 6], ["option_id", 1]]

Схема БД:

create_table "items", force: :cascade do |t|
  t.string "name", null: false
  t.decimal "price", null: false
  t.integer "available_count", null: false
  t.integer "category_id"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
  t.index ["category_id"], name: "index_items_on_category_id"
end

create_table "items_options", id: false, force: :cascade do |t|
  t.integer "item_id", null: false
  t.integer "option_id", null: false
end

create_table "options", force: :cascade do |t|
  t.string "name", null: false
  t.integer "filter_id"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
  t.index ["filter_id"], name: "index_options_on_filter_id"
end

1 Ответ

2 голосов
/ 04 марта 2020

Вы можете попробовать:

scope :by_options,
    ->(options) { 
          self.joins(:items_options)
              .having('SUM(items_options.option_id in (?)) = ?', options, options.size)
     }

Вы в основном говорите: "Дайте мне все предметы, которые имеют все items_options.option_id, которые я указал".

Это немного грязно и я не проверял его, но с самого начала это должно сработать.

В противном случае, не могли бы вы связать оператор создания схемы SQL с вашим постом, чтобы мы могли выполнять запросы легче?

...