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?
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
Adding to Simon answer,
I have found one more sceneario. Where u want to apply condition in one model and match other with fulltext only.
My first try was this
def search_all(options)
Sunspot.search(Post, Tag) do
any_of do
all_of do
with :class, Post
with(:status, :published)
end
end
fulltext options[:search]
group(:class) { limit 30 }
end
end
This was giving me only Post but not Tag, somehow status: :published is applied on Tag and no results coming for Tag. I tried some other variations too.
Finally I figured out one solution.
Sunspot.search(Post, Tag) do
any_of do
all_of do
with :class, Post
with(:status, :published)
end
# This might look weiered to put all_of without any filter, However
# without the next all_of block, status: :published is applied in tags and no results appear for tags.
# Meaning any_of is ignored if there is one nested block and Post all_of is applied for entire filter.
all_of do
with :class, Tag
end
end
fulltext options[:search]
group(:class) { limit 30 }
end
This solution worked. May be This might have some other solution. Still I m happy with this.