Algolia rails sortBy dynamically - ruby-on-rails

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.

Related

Elasticsearch querying multiple indices while limiting the fields of one index

I have an Autocomplete class with a results method that queries two different models to return data. It partially works great, but with one major issue.
def results
Searchkick.search #query, index_name: [Location, DiveCenter]
end
I need to limit the hits returned from the index DiveCenter because of the way I have the index set up. It's set up this way because a user will either click on a DiveCenter and be taken to the DiveCenter show page OR click on a location and be taken to /search?(city | country)=value.
searchkick searchable: [:name, :city, :state, :country]
def search_data
{
name: name,
city: location.city,
state: location.state,
country: location.country
}
end
So basically, what I need is this:
def results
Searchkick.search #query, index_name: [Location, DiveCenter], fields: ['location.city', 'location.state', 'location.country', 'dive_center.name']
end
Elasticsearch doesn't provide a way to limit results from a single index.
One approach is to filter the results in Ruby.
Another approach is to use multi search to limit the results for dive centers.

Sunspot: boost elements when match a multiple values field

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.

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.

Assemble a complex SQL query using Activerecord::Relation and/or Arel with 'joins', 'as', and 'like'

I am building a rails 3.2 app using datatables (http://datatables.net) with client-side paging and filtering on most html tables and server-side paging and filtering on some other html tables. I want to do per-column filtering, which is super-easy for the client side tables, but I think I need to construct a sql query for the database to do per-column filtering for the server side tables. I closely followed the example from RailsCast #340 on datatables and got that working.
The challenge is doing sorting and filtering on a column that is really a foreign_key relation to another table. I don't want to sort and filter on the actual contents of the foreign_key values. I want to sort and filter on the '.to_s' values displayed for the linked objects (which is the semantics of using the client-side sort and filter feature). Here is an example:
class Address < ActiveRecord::Base
attr_accessible :city, :line1, :line2, :state, :zip
has_many :people
def to_s
[line1, line2, city, state, zip].join(' ')
end
end
class Person < ActiveRecord::Base
attr_accessible :address, :name
belongs_to :address
end
so the view displaying the people list has two columns, for name and address
<td><%= p.name %></td>
<td><%= p.address %></td>
and what appears in the index table is
John Smith | 1234 Main St Anywhere City AA 12345
so with client-side sorting and filtering I can search for 'Anywhere' in the address column and get all the rows with that term in the address field. Doing the same thing on the server-side seems much more difficult. I think I'm trying to assemble a sql query that looks something like:
select * from people
join address on people.address_id = address.id
where concat(address.line1,
address.line2,
address.city,
address.state,
address.zip) as spec_address like query_term
order by spec_address
(This is not necessarily correct SQL code.)
I've looked at both the ActiveRecord Query Rails guide and anything I could find on Arel without success.
You can do this with a scope on Address which is then merged into the Person query.
class Address < ActiveRecord::Base
scope :anywhere, lambda{|search|
attrs = [:line1, :line2, :city, :state, :zip]
where(attrs.map{|attr| "addresses.#{attr} LIKE :search"}.join(' OR '), search: "#{search}%").
order(*attrs)
}
end
Person.joins(:address).merge(Address.anywhere(query_term))

Searching with thinking_sphinx and filtering results

I have this scenario where I thought it would be pretty basic, but found out that I can't really achieve what I need. This is why I have this question for a thinking_sphinx's expert.
The scenario is this: I need do a search within a list of companies and only return those who has an address (there can be many address by company) which belongs to a particular city or none at all (this I can do).
I have the following models :
class Company < ActiveRecord::Base
has_many :company_addresses
define_index
indexes :name
indexes :description
indexes :keywords
end
end
and
class CompanyAddress < ActiveRecord::Base
end
The CompanyAddress has a city_id property. Without looping through all returned records from a sphinx search, is there a way to achieve the same thing more easily?
I'm using Rails 3.0.3 and thinking_sphinx.
You'll want to add an attribute pointing to the city_id values for the company:
has company_addresses.city_id, :as => :city_ids
And then you can filter on Companies belonging to a specific city:
Company.search 'foo', :with => {:city_ids => #city.id}
If you want both matching to a specific city or has no cities, that's a little trickier, as OR logic for attribute filters is more than a little tricky at best. Ideally what you want is a single attribute that contains either 0, or all city ids. Doing this depends on your database, as MySQL and Postgres functions vary.
As a rough idea, though - this might work in MySQL:
has "IF(COUNT(city_id) = 0, '0', GROUP_CONCAT(city_id SEPARATOR ',')",
:as => :city_ids, :type => :multi
Postgres is reasonably similar, though you may need to use a CASE statement instead of IF, and you'll definitely want to use a couple of functions for the group concatenation:
array_to_string(array_accum(city_id, '0')), ',')
(array_accum is provided by Thinking Sphinx, as there was no direct equivalent of GROUP_CONCAT in PostgreSQL).
Anyway, if you need this approach, and get the SQL all figured out, then your query looks something like:
Company.search 'foo', :with => {:city_ids => [0, #city.id]}
This will match on either 0 (representing no cities), or the specific city.
Finally: if you don't reference the company_addresses association anywhere in your normal fields and attributes, you'll need to force to join in your define_index:
join company_addresses
Hopefully that provides enough clues - feel free to continue the discussion here or on the Google Group.

Resources