Rails multiple joins condition query for exact tags with has many through relationships

305 Views Asked by At

I have a problem with filtering products by exact tags as my current query does not return exact matches and I can't seem to get the right query conditions.

Example

Area = ["small","big"] ,Surface = ["smooth","rough"]

Product A has only ["small","smooth","rough"] as tags

If I filter products using ["small","big","smooth","rough"] as the tags, I get product A in my search results but ideally, it should not return any search results.

I have three models, Product,Area and Surface. Area & Surface are linked to Product by a has_many through relationship.

class Product < ActiveRecord::Base
  has_many :product_areas
  has_many :areas, :through => :product_areas
  has_many :product_surfaces
  has_many :surfaces, :through => :product_surfaces

class Area < ActiveRecord::Base
  #Surface model looks exactly the same as Area model
  has_many :product_areas,dependent: :destroy
  has_many :products, :through => :product_areas

My Query

area_ids = params[:area_ids]
surface_ids = params[:surface_ids]
@products = Product.where(nil)
@products = @products.joins(:areas).where('areas.id' => area_ids).group('products.id').having("count(areas.id) >= ?",area_ids.count) unless area_ids.blank?
@products = @products.joins(:surfaces).where('surfaces.id' => surface_ids).group('products.id').having("count(surfaces.id) >= ?",surface_ids.count) unless surface_ids.blank?
1

There are 1 best solutions below

0
Wraithseeker On

I solved this problem just now with this solution.

First I used the names of the models for Area & Surface as their unique identifer as they can have conflicting ids and added them to an array.

Next I looped through the products and created an array of the name identifers and compared the two arrays to check if they intersect. Intersection would mean that the search filters were a correct match and we add the product ID to a third array which stores all the product_ids before doing a query to get the products with those product ids.

@area = Area.all
area_ids = params[:area_ids]
@uniq_names = @area.where(id: area_ids).collect { |m| m.name } 
@products.each do |product|
    @names = product.areas.map { |m| m.name }
    # if intersect, then we add them to filtered product
    if (@uniq_names - @names).empty?
        product_ids << product.id
    end
end
@products = Product.where(id: product_ids)