Search code examples
ruby-on-railsrubyelasticsearchsearchkick

controlling search results position SearchKick


Im using SearchKick which is great, Im migrating from a really bad search implementation and the product team didn't give me much trust as such before migrating to SearchKick and doing an overhaul to our search, they made me add hardcoded query results, so they can say for this search input I want this product to come up first. right now Im taking the query results that answer a certain the requested query from the db and add them at the top ( I don't care if you want the result at position 48, if there are 4 hard coded results it will be the 4th). although if possible it would be nice to do put them in the middle.

What is the cleanest way to do it with SearchKick, so that the querying will happen inside elastic ( index the hardcoded results in the product to do so )

I have 2 models Product and QueryResult, QueryResult contains a product, a query string & a wanted_rank

in my Product model I do have a method to get search results that looks something like this:

def get_search_results(query_string)
  # get search results from elastic using searchkick
  search_results = Product.search(query_string)
  
  # get hardcoded results matching this query
  hardcoded_results = QueryResults.where(query: query).order(:wanted_rank).map(&:product)
  
  # remove hardcoded results from search_results
  search_results = search_results - hardcoded_results

  # return results where hardcoded results are first
  hardcoded_results + search_results
end

In the end I want all the search logic to happen over elastic including inserting hardcoded search results


Solution

  • So after some very helpful comments and more search I found a solution. first of all my first mistake was to try to fix it using boosts instead of order, to do so we index all QueryResults of a specific product under a field called called query_results. for a product x with query results:

    {
      query: 'foo',
      wanted_rank: 1,
    },
    {
      query: 'bar',
      wanted_rank: 2,
    }
    

    I will index:

    {
      name: 'x',
      query_results: {
        'foo': 1,
        'bar': 2
      }
    }
    

    than when searching, given a attribute named query, I will do as follow:

    Product.search(
      query,
      order: [ 
        { "query_results.#{query}": { unmapped_type: :long },
        { _score: :desc } 
      ]
    )
    

    2 important things to notice:

    1. use unmapped_type this will tell elastic what mapping to use when there is no mapping, each random query that does not have a query result ( which is most of them ) will have no mapping for "query_results.#{query}" because it wont be indexed, as such we add unmapped_type to tell elastic if you have no mapping act like its long.
    2. both when searching and when saving the db I downcase and strip query so it will match properly.

    also I index the queries under another field and do a search over it with low weight to make sure that the product will come up for that query.