Search code examples
ruby-on-railsrails-activerecord

Rails 7 Advanced Search Chain Where


I'd like to setup an advanced search of a Rails resource (i.e Product) where I can perform both positive and negative searches. For example:

  • Product is a phone
  • Product is not made by Apple
  • Product was made in the last 16 months

I can pass multiple parameters to a page but is there a way to chain queries?

@results = Product.where("lower(type) LIKE ?", "%#{search_term.downcase}%").where(....

I'd like to use a combination of where and where.not:

  def search
    word1 = params[:word_1]
    word2 = params[:word_2]
    if word1.starts_with?('not')
      chain1 = where.not("lower(tags) LIKE ?", "%#{word1.downcase}%")
    else
      chain1 = where("lower(tags) LIKE ?", "%#{word1.downcase}%")
    end
    if word2.starts_with?('not')
      chain2 = where.not("lower(tags) LIKE ?", "%#{word2.downcase}%")
    else
      chain2 = where("lower(tags) LIKE ?", "%#{word2.downcase}%")
    end
    @products = Product.chain1.chain2
  end

but I get the following error:

undefined method where' for #ProductsController:0x0000000000ac58`


Solution

  • You can chain where like this

    Product.
      where(type: "phone").
      where.not(factory: "Apple").
      where(manufactered_at: 16.months.ago..)
    

    Also rails 7 introduces invert_where (it inverts all condtions before it) so you can

    Product.
      where(factory: "Apple").invert_where.
      where(type: "phone").
      where(manufactered_at: 16.months.ago..)
    
    

    You can use scopes

    class Product < ApplicationRecord
      scope :phone, -> where(type: "phone")
      scope :apple, -> where(factory: "Apple")
      scope :manufacatured_last, ->(period) { where(manufactered_at: period.ago..) }
    end
    
    Product.apple.invert_where.phone.manufacatured_last(16.months)