ruby-on-railsrubyelasticsearchsearchkickadvanced-search

Setting up an advanced search with searchkick, coping with associations with multiple models (Rails)


For my Rails app I am trying to set up an advanced search with Searckick (elasticsearch). They thing I am trying to do is:

I've got it fixed so far, that I can search on a User, but I'm not sure how to be able to search on these other models as well.

My routes:

Rails.application.routes.draw do
  ActiveAdmin.routes(self)
  devise_for :users, controllers: {sessions: "sessions", registrations:       
  "registrations"}
  # For details on the DSL available within this file, see
  http://guides.rubyonrails.org/routing.html

  root 'pages#home'

  get "/news", to: 'pages#news'

  get "welcome_back", to: 'pages#welcome_back'

  get "/profile", to: "profile#show"

  resources :profiles do
    collection do
      get :autocomplete
    end
  end

  namespace :profile do
    resources :locations
    resources :positions
    resources :competences
  end
end

A user belongs to a Location, has multiple Competences through a joined table. In other words: a user has a location_id and you can call .competences on a user, to see which users_competences the User has.

Can anyone tell me how to set up this search?

My Profiles controller:

class ProfilesController < ApplicationController

  def index
    query = params[:search].presence || "*"
    @users = User.search(query, suggest: true, operator: "or")
  end

  def autocomplete
    render json: ["Test"]
  end

end

I have tried to work with a def self(search) in my model, but this isn't working.

What I tried:

  def self.search(search)
       where(["User.first_name LIKE ?","%#{search}%"])
       where(["User.last_name LIKE ?","%#{search}%"])
       where(["User.competences.collect{&:name} IN ?","%#{search}%"])
       joins(:location).where("location.name LIKE ?", "%#{search}%")
     else
       all
     end
   end

Solution

  • TL;DR define a search_data method to customize your search index

    self.search as currently defined in your code is incorrect Your self.search method would be a way to search via ActiveRecord and whatever database you are using. It is not how you search via Searchkick.

    For Searchkick If you want to return User profiles that match searches for user attributes, competences, and locations then you should define the user attributes, competences, and locations that you want to search via custom mappings using the search_data method, documented here:

    class User < ActiveRecord::Base
      searchkick locations: ["location"]
      # this line will eager load your assocations
      scope :search_import, -> { includes(:location, :competences) }
    
      def search_data
        {
          first_name: first_name,
          last_name: last_name,
          competences: competences.map(&:name),
          # from your code above it looks like you want to search location.name
          location_name: location.name
          # the following merged hash would allow you to search by location latitude and 
          # longitude coordinates if you have them
          ## if you don't use latitude and longitude then you don't need to include 
          ## `locations: ["location"] when you call searchkick in the User model on line 2 
          ## above or the merged location hash below
        }.merge(location: {lat: location.latitude, lon: location.longitude})
      end
    end
    

    Now your search query in the Profiles#index action will work:

    @users = User.search(query, suggest: true, operator: "or")
    

    There is no need for the self.search method that you defined.

    Other Notes