ruby-on-railssunspotsunspot-railssunspot-solr

Querying multiple models with different attributes using Sunspot


I'm using Sunspot to index and search several models in a Rails project and I need to limit results based on the models' HABTM associations with a Department model. This is because users may not have permission to see records in all departments so results from those departments shouldn't be returned.

Here are the important parts of two of the models:

class Message < ActiveRecord::Base
  has_many :comments, dependent: :destroy
  has_and_belongs_to_many :departments

  searchable do
    text :title, :body
    text :comments do
      comments.map(&:body)
    end
    date :created_at
    integer :department_ids, using: :department_ids, references: Department, multiple: true
  end
end

class Document < ActiveRecord::Base
  has_and_belongs_to_many :departments

  searchable do
    text :name
    date :created_at
    integer :department_ids, using: :department_ids, references: Department, multiple: true
  end
end

And here's the search controller code:

class SearchController < ApplicationController
  def index
    # These arrays are created here for the sake of this example
    document_permitted_departments = [1, 2, 3]
    message_permitted_departments = [3, 4]

    search = Sunspot.search Document, Message do
      # This obviously doesn't work
      with(:department_ids, document_permitted_departments)
      with(:department_ids, message_permitted_departments)
      fulltext params[:q]
      paginate page: params[:page], per_page: SEARCH_RESULTS_PER_PAGE
      order_by :created_at, :desc
    end
    @results = search.results
    @number_of_results = search.total

    respond_to do |format|
      format.js
      format.html
    end
  end
end

The problem is that a user may be able to read documents in Department A and Department B but they should only see messages in Department B.

Is there a way to apply the with scope to a specific model in a multi-model search? Or is there another way of doing this that I'm missing?


Solution

  • After a lot more Googling and some trial and error I finally figured it out. Here's a heavily annotated version of the code I ended up with:

    class SearchController < ApplicationController
      before_filter :authenticate_user!
    
      def index
        # These arrays are created here for the sake of this example
        # Push 0 on to the end because empty arrays break the `with :department_ids` scopes below
        document_permitted_departments = [1, 2, 3].push(0)
        message_permitted_departments = [3, 4].push(0)
    
        search = Sunspot.search Document, Message do
          any_of do # Return anything that matches any of the scopes in this block
            all_of do # Return only those results that match these scopes
              with :class, Document # This limits scopes in this block to Document results
              with :department_ids, document_permitted_departments
            end
    
            all_of do # Return only those results that match these scopes
              with :class, Message # This limits scopes in this block to Message results
              with :department_ids, message_permitted_departments
            end
          end
    
          fulltext params[:q]
          paginate page: params[:page], per_page: SEARCH_RESULTS_PER_PAGE
          order_by :created_at, :desc
        end
        @results = search.results
        @number_of_results = search.total
    
        respond_to do |format|
          format.js # index.js.erb
          format.html # index.html.erb
        end
      end
    end