I'm writing a simple online shop on ruby and have a problem with items filtering. I'm trying to filter items by few options (eg 'color': 'red' and 'brand': 'zara').
I expect to see only items that have both 'red' and 'zara' options.
Everything is OK when I filter by one option. But with few options, it shows all items that have at least one of the options.
My filtering scope in Item model:
scope :by_options,
->(options) { joins(:items_options).where(items_options: { option_id: options }).distinct }
Items controller:
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
Generated query:
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]]
I also tried to filter by options twice, but in this case @items are empty (6 and 1 are just examples, because I have item that have both of this options:
@items = Item.by_options(6).by_options(1)
Generated query:
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]]
DB schema:
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
You can try:
scope :by_options,
->(options) {
self.joins(:items_options)
.having('SUM(items_options.option_id in (?)) = ?', options, options.size)
}
You're basically saying: "Give me all items that have all the items_options.option_id I specified".
This is a little bit dirty, and I haven't tested it, but from the top of my head it should do the trick.
Otherwise, would you mind linking an SQL schema creation statement to your post so we could run the queries more easily?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.