Search integers with Sunspot - ruby-on-rails

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

Related

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.

How to check if there is an index on a column through Rails console?

Let's say I do Image.column_names and that shows all the columns such as post_id but how do I check if post_id has an index on it?
There is an index_exists? method on one of the ActiveRecord "connection adapters" classes.
You can use it like on of the following methods:
ActiveRecord::Migration.connection.index_exists? :images, :post_id
ActiveRecord::Base.connection.index_exists? :images, :post_id
If you know the name of the index, instead you'll need to use index_name_exists?
ActiveRecord::Base.connection.index_name_exists? :images, :index_images_on_join_key
As others have mentioned, you can use the following to check if an index on the column exists:
ActiveRecord::Base.connection.index_exists?(:table_name, :column_name)
It's worth noting, however, that this only returns true if an index exists that indexes that column and only that column. It won't return true if you're using compound indices that include your column. You can see all of the indexes for a table with
ActiveRecord::Base.connection.indexes(:table_name)
If you look at the source code for index_exists?, you'll see that internally it's using indexes to figure out whether or not your index exists. So if, like me, their logic doesn't fit your use case, you can loop through these indexes and see if one of them will work. In my case, the logic was thus:
ActiveRecord::Base.connection.indexes(:table_name).select { |i| i.columns.first == column_name.to_s}.any?
It's also important to note, indexes does not return the index that rails automatically generates for ids, which explains why some people above were having problems with calls to index_exists?(:table_name, :id)
The following worked for me:
ActiveRecord::Base.connection.index_exists?(:table_name, :column_name)
For an updated answer, as of Rails 3+ multi-column, uniqueness and custom name are all supported within the #index_exists? method.
# Check an index exists
index_exists?(:suppliers, :company_id)
# Check an index on multiple columns exists
index_exists?(:suppliers, [:company_id, :company_type])
# Check a unique index exists
index_exists?(:suppliers, :company_id, unique: true)
# Check an index with a custom name exists
index_exists?(:suppliers, :company_id, name: "idx_company_id")
Source: Rails 6 Docs

Find all items where match (or like) a text in a Model within Mongoid in Rails 4

I´m working in a project with Rails 4 and Mongodb as back-end helped by the wonderful gem 'Mongoid' and I want to find all items of my model 'Item' matching a search term using 'sql-like' too.
My model looks like:
class Item
include Mongoid::Document
field :name, :type => String
field :importe, :type => BigDecimal
field :tipo, :type => String
end
Trying to do this in the controller but doesn´t works correctly:
Item.where(name: Regexp.new(".*"+params[:keywords]+".*"))
(where "params[keywords]" is the searchterm) because doesn´t returns anything when there are items with this name value.
How do I make this query?
In ruby rails, we need to use this:
condition = /#{params[:keywords]}/i
Item.where(:name => condition)
Item.where({:name => "/#{params[:keywords]}/i"})

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.

How to index translations of one item in Sunspot

I am using Sunspot/Solr to index my Rails website. I index City name by doing the following:
class City < ActiveRecord::Base
searchable do
text :name
...
end
...
Now I am internationalizing the whole site, using the Globalize3 gem. It saves translations in another table, and get these translations out using the normal accessors.
From the example here (http://osdir.com/ml/rails-oceania/2011-11/msg00047.html) they have:
searchable do
# sorting
string(:job_title) { title }
# keyword / fulltext searching
I18n.available_locales.each do |locale|
text(("title_" + locale.to_s).to_sym, :default_boost => 2)
{ eval("title_" + locale.to_s) }
end
end
So essentially for each locale there is an indexing column in Sunspot, like title_en and title_fr. I am wondering if there is a better approach? (too many columns sounds bad to me)
One alternative I am thinking is to concatenate translations of one item as a single string and put it in another text index column.
Also I was thinking if there is something similar to integer :ids, :multiple => true for texts?
So what's a better way to index multiple translations of the same item name?
I implemented single column language index:
LANGUAGES = {
'en' => 'English',
'fr' => 'Français'
}
#inside city model
text :name_alt do
LANGUAGES.keys.reject{|l| l=='en'}.map do |locale|
read_attribute(:name, locale:locale)
end
end
This uses Rails I18n module and Globalize3. 'read_attribute' is part of Globalize3.
This avoids creating columns per language.
I am not sure whether this is better or the multi column approach.

Resources