Sunspot: boost elements when match a multiple values field - ruby-on-rails

I have an object Foo which has many objects Bee.
class Foo
has_many :bees
I index my object A with Sunspot SOLR like this.
searchable do
text :title, boost: 5
text :content, boost: 2
integer :bee_ids, multiple: true
...
end
to keep track of Bee ids related to my Foo object.
Now I have a User that performs searches on Foos objects. The User has many Bees too.
class User
has_many :bees
...
end
When I search Foos objects I would like to boost objects which have bees that matches User's bees.
Foo.search do
fulltext query
any_of do
...
with(:bee_ids, #user.bees.pluck(:id))
end
end
I want to give priorities to objects that matches user's interests. Any idea?

Well. I found a solution for the problem above.
You need to use bq parameter to do that and give a boost to that condition.
I added
adjust_solr_params do |params|
params[:bq] = " bee_ids_im:(#{#user.bees.pluck(:id).join(' OR ')})^20"
end
and I give a boost to the above condition of 20 times.
Actually that the solution to the more generic problem of giving boost to specific conditions in the query.
Sunspot support boost queries with boost command.
So I changed my query with:
Foo.search do
fulltext query do
boost(20.0) do
with(:bee_ids, #user.bees.pluck(:id))
end
end
any_of do
...
with(:bee_ids, #user.bees.pluck(:id))
end
end
and I obtained the same result.
Hope this can help someone else.

Related

Joining two ActiveRecord associations on common attribute

Let's say I have a User model. User has 2 has_many associations, that is User has many pencils and has many cars. Cars and Pencils table has same attribute, :date, and separate such as :speed(car) and :length(pencil). I want to join a user's pencils and cars on their common attribute, :date, so that I have an array/relation [:date, :speed, :length]. How do I achieve that, I tried joins and merge but they were no use.
I'd definitely recommend getting this into a query rather than a loop, for efficiency's sake. I think this will work:
Car.joins(:user => :pencils).where("pencils.date = cars.date")
And if you want to reduce it to the array immediately:
Car.joins(:user => :pencils).where("pencils.date = cars.date").pluck("cars.date", "cars.speed", "pencils.length")
If you need to include matches where date is nil, you might need to add:
Car.joins(:user => :pencils).where("(pencils.date = cars.date) OR (pencils.date IS NULL AND cars.date IS NULL)")
Many more efficient options exist, but here is one possible approach:
class User < ActiveRecord::Base
def get_merged_array
dates = (cars.map(&:date) & pencils.map(&:date))
results = []
dates.each do |date|
cars.where(date: date).each do |car|
pencils.where(date: date).each do |pencil|
results << [date, car.speed, pencil.length]
end
end
end
results
end
end

Algolia rails sortBy dynamically

I'm building a search using Algolia rails but having difficulties when doing sort by dynamically. For example, user can choose from a dropdown to sort by price asc or price desc.
Here is my model where I defined the indices
class Product < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true, if: :publishable? do
attribute :id, :name, :description, :seller_id, :condition, :location, :slug, :status, :city, :state, :stock_quantity,
:shipping_method, :price
attribute :created_at_i do
created_at.to_i
end
attribute :updated_at_i do
updated_at.to_i
end
attribute :price do
price.to_f
end
attributesToIndex ['name', 'unordered(description)', 'seller_id',
'condition', 'location', 'slug', 'created_at', 'updated_at', 'status',
'geo(city)', 'geo(state)']
geoloc :latitude, :longitude
numericAttributesToIndex ["price", "stock_quantity"]
attributesForFaceting ['price', 'condition', 'shipping_method']
end
and the products controller
def index
queries = { hitsPerPage: 5, page: params[:page].to_i, facets: '*',
facetFilters: [
"condition: #{params[:condition]}",
"shipping_method: #{params[:shipping_method]}"
],
numericFilters: [
"price:#{params[:min_price] || 0} to #{params[:max_price] || 999999999999999}"
],
sortBy: ["asc(price)"]
}
if latLng.present?
queries[:aroundLatLng] = latLng
queries[:aroundRadius] = radius
end
#response = Product.search(params[:query],queries)
Algolia returns error "invalid parameter sortBy". I tried to search on algolia documentation but couldn't find any information.
Thanks for helping.
Here is a list of relevant documentation pages from Algolia's website which will help you understand how Algolia's ranking work:
[Getting started] Tweak Ranking and Relevance
The reason our engine is so fast is because each index has its own settings and rankings. This means you are able to create different sets of searchable attributes and attribute ranking relevance by storing your data in multiple indices. This is accomplished using slave indices which are seamlessly synchronized with a master index. Each slave index can then be configured with its own set of business metrics to tune the relevance calculation.
An index has a specific ranking formula that can't be changed. You can however easily overcome this limitation by using slave indices with a different ranking formula.
[Tutorials][Ranking Formula] Tie Breaking algorithm
[FAQ] How does Algolia's tie breaking algorithm work?
These two links will help you understand how the ranking with Algolia work and how you can tweak it. Basically, depending on your use-case, sorting by price before text relevance doesn't really make sense. So, depending on your use case, you might just want to change your customRanking to price, or you might want to add your price attribute at the top of your ranking formula.
[FAQ] What are slave indices and what are their benefits?
This last link explain in more depth what a "slave index" is for Algolia.
[Rails documentation] Multiple sort criteria
[Rails GitHub documentation] Master/Slave
On these last links, you'll find code examples using Algolia's Rails client with multiple slaves. The first one actually showcases exactly your use case: sorting by price.
With all of this in mind, in the end, you're just looking to add these in your model (if you want to use the customRanking way):
add_slave 'Product_by_price_asc', per_environment: true do
customRanking ['asc(price)']
end
add_slave 'Product_by_price_desc', per_environment: true do
customRanking ['desc(price)']
end
Then in your controller, you can query them this way
query_params = { slave: "Product_by_price_#{params[:sort_order]}", hitsPerPage: 5, ... }
Product.search params[:query], query_params
You should probably also do this implementation in the front-end to be able to fully use Algolia's instant search capabilities, see [FAQ] Searching from the front-end or the back-end.

Search integers with Sunspot

I managed to get Sunspot working in my rails setup. My rails setup renders graphs (Chartwell) with input from my database (integers), example: "design: 80, art: 20, code: 40".
Is there a way that I can search for "design" and get all elements with design > 70 (integer, for instance) as output?
You're not giving code, so I'll improvise.
Since you have everything working, I assume you already have defined your design field indexed in searchable definition in your model.
After you've done that, you will have a .search block for Sunspot (or Model) in your active code (most probably in a controller).
So, let's suppose your model name is Graph.
design = params[:design] # guessing, again.
Graph.search do
with(:design).greater_than(design)
# .... other conditions
end
This should work for you.
UPDATE:
Assuming that you have a design:integer column in your db
In your event.rb:
searchable do
text :name, :location, :date_search
integer :design
end
in your events_controller.rb:
#search = Event.search do
fulltext params[:search]
with(:design).greater_than(params[:design].to_i)
end
Note: You should pass :design as a parameter with your search form

Sorting elasticsearch by column weight results with tire gem for rails project

def self.search(params)
return [] unless params[:query].present?
tire.search(load: true) do
query { string(params[:query], fields: %w(title description topics
username discussions)) }
sort do
by "likes", "desc"
by "badges_count", "desc"
end
facet :tags do
terms :tags
end
facet :topics do
terms :topics
end
size params[:size] || 5
end.results
end
I'm attempting to perform a search on a particular model. Although the results are currently sorting based on the most likes, and I'd like to base it more on a percentage basis for each column in the sort block.
for example:
50% for "likes" based on strength of semantic match in another column(:header)
20% for "badges_count" based on "badges_count"
Any help would be great as I am a bit stuck on how to expand the block more and create a mini algorithm to sort based by weight.
Index your columns with boost options.
ex:
class YourParticularModel
mapping do
indexes :likes, boost: 100
indexes :badges_count, boost: 40
# .... other indexes
end
end
Then remove sort from your search method. The query result will auto weight the likes and badges_count matches.

Searching multiple columns with Sphinx

I have model Products with columns:
name, number, description
Index defined for this model looks like this:
define_index do
indexes :name, :sortable => true
indexes :number
indexes :description
where "amount > 0"
has :price
end
Since in description can be lots of random words I want to exclude it from searching sometimes (when user clicks chceckbox 'don't search in descriptions').
I went to the sphinx page and found following:
#(name, number) *pencil* *123*
And it seems like I don't understand how sphinx works. When I execute search
*pencil* *123*
word 'pencil' is found in name and '123' is found in number and I get 1 result. But when I execute
#(name, number) *pencil* *123*
no results are found.
Is searching by columns somehow different?
You can only search on fields when using the :extended match mode - Thinking Sphinx sets this automatically if you use :conditions - but you're constructing a multi-field query yourself, hence why this isn't happening. Try this:
Product.search "#(name, number) *pencil* *123*", :match_mode => :extended
Hope this helps.
It was all about spaces :/
This works:
#(name,number) *pencil* *123*

Resources