Search code examples
ruby-on-railssunspotsunspot-railssunspot-solr

Problems implementing Sunspot search through associations


I'm having a few problems getting Sunspot implemented! I've tried for the last 2 days to get this working and I can't seem to find any solution (although it no doubt has been staring me in the face!).

I'm wanting the user to be able to search for a vehicles manufacturer. When a vehicle is created, it is assigned a manufacturer through an association (as you can see below). Now.. how do I allow a user to enter in the search field (or even better a collection_select of all manufacturers) and it'll filter out the result?

In an ideal world I'd love this to work as my search:

<%= simple_form_for :search,  url: vehicles_path , :method => :get do |f| %>
    <%= select_tag :manufacturer, options_from_collection_for_select(Manufacturer.all, :id, :name) %> 
    <%= submit_tag "Search", :name => nil %>
<% end %>

But alas, I've not been able to find a solution in my Vehicle model.

If that makes any sense to you, I hope you can lead me in the right direction!

Thanks very much!

class Vehicle < ActiveRecord::Base

    belongs_to :manufacturer

    searchable do
        text    :name, :registration
        integer :manufacturer_id
    end

    def to_s
        self.name
    end
end

Then

class VehiclesController < ApplicationController
  before_action :set_vehicle, only: [:show, :edit, :update, :destroy]

  def index
    @manufacturer = Manufacturer.all

    @search = Vehicle.search do
      fulltext params[:search]
    end

    @vehicles = @search.results
  end
end

and

class Manufacturer < ActiveRecord::Base
    has_many :vehicles

    searchable do
        text    :name
    end

    def to_s
        self.name
    end

end

Vehicle index.html.erb

    <%= form_tag vehicles_path, :method => :get do %>
        <%= text_field_tag :search, params[:search] %>
        <%= submit_tag "Search", :name => nil %>
    <% end %>

And my vehicle form

<%= simple_nested_form_for @vehicle, :html=>{:multipart => true } do |f| %>

  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :name %>
    <%= f.association :manufacturer, include_blank: false %>
</div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>

 <% end %>

I've removed most of the unnecessary stuff in my examples just for ease.

And just for some laughs, you can look at my failed attempts!

I have also ran so many rake sunspot:reindex it's crazy.

    #integer :manufacturer_id, :multiple => true

    #integer :manufacturer_id, :references => Manufacturer, :multiple => true

    #integer :manufacturer_id, :references => Manufacturer, :multiple => true do
    #   manufacturers.map {|manufacturer| manufacturer.manufacturer_name}
    #end

    #text :manufacturer_names do |manufacturer|
    #  manufacturer.name { |manufacturer| manufacturer.name }
    #end 

    #integer :manufacturer_id {|manufacturer| manufacturer.name }

    #string :manufacturer_name do
    #   manufacturer.name { |manufacturer| manufacturer.name }
    #end

    #text :manufacturers do
    #   manufacturers.map { |manufacturer| manufacturer.name }
    #end

    #integer :manufacturer_id, :stored => true

Solution

  • The problem is in the way you're doing the search in your controller. You said in the ideal world you would like to select the manufacturers from a select element:

    <%= simple_form_for :search,  url: vehicles_path , :method => :get do |f| %>
    <%= select_tag :manufacturer, options_from_collection_for_select(Manufacturer.all, :id, :name) %> 
    <%= submit_tag "Search", :name => nil %>
    

    If you define your search form in this way, you will have a manufacturer parameter in the URL after you submit the form.

    You want to filter the vehicles by the id of the manufacturer in your controller, so you can not do a full_text search, you have to use with in the search block:

    class VehiclesController < ApplicationController
      def index
        @search = Vehicle.search do
          # Scope the query
          with :manufacturer_id, params[:manufacturer]
        end
    
        @vehicles = @search.results
      end
    end
    

    The sunspot documentation says:

    Fields not defined as text (e.g., integer, boolean, time, etc...) can be used to scope (restrict) queries before full-text matching is performed.

    You can find more information about scoping: https://github.com/sunspot/sunspot#scoping-scalar-fields