How to boost query depends of attribute's value in ElasticSearch? - ruby-on-rails

I have a Profile class. In ES index I have company_type attribute.
class Profile
...
include Tire::Model::Search
include Tire::Model::Callbacks
def to_indexed_json
{ name: self.name,
company_type: self.company.company_type
}.to_json
end
end
Tire.search('profiles') do
query do
custom_filters_score do
query { all }
filter do
filter :range, last_contact_at: { gte: 7.days.ago }
boost 1
end
score_mode :total
end
end
end.results
I would like to boost by 10 queries if company_type == 'intern'.

did you try adding
filter do
filter :term, company_type: "intern"
boost 10.0
end
to your custom_filters_score filter?

Related

tire and elasticsearch_autocomplete accents and facets

I'm using elasticsearch_autocomplete gem for autocomplete feature.
I have a problem with special characters ñ and accents áéíóú.
Model:
class Car
ac_field :name, :description, :city, :skip_settings => true
def self.ac_search(params, options={})
tire.search load: true, page: params[:page], per_page: 9 do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { term :city, params[:city] } if params[:city].present?
end
end
filter :term, city: params[:city] if params[:city].present?
facet "city" do
terms :city
end
end
end
end
This version works fine with special characters.
e.g.: Query with Martin I get all results with Martín, martín, martin, Martin
With this approach this is the problem:
Now what results is individual words. e.g. A city tagged ["San Francisco", "Madrid"] will end up having three separate tags. Similarly, if I do a query to search on "san francisco" (must { term 'city', params[:city] }), that will fail, while a query on "San" or "Francisco" will succeed. The desired behaviour here is that the tag should be atomic, so only a "San Francisco" (or "Madrid") tag search should succeed.
To fix this problem I create my custom mapping:
model = self
settings ElasticsearchAutocomplete::Analyzers::AC_BASE do
mapping _source: {enabled: true, includes: %w(name description city)} do
indexes :name, model.ac_index_config(:name)
indexes :description, model.ac_index_config(:description)
indexes :city, :type => 'string', :index => :not_analyzed
end
end
With this mapping the problem with multi-words is fixed, and now facets with city field works fine:
Instead of getting the type facets San and Francisco Now I get San Francisco
Now, the problem is that with this mapping inside of the model the search doesn't find results with special characters
e.g.: Query with Martin I get only results with Martin martin
I'm using mongoid instead active record.
How can I fix this problem? I think that the problem is with asciifolding tokenfilter.
I fixed the problem with:
class User
include Mongoid::Document
field :city, :type => String
has_one: car
end
class Car
ac_field :name, :description, :user_city, :skip_settings => true
def self.ac_search(params, options={})
tire.search load: true, page: params[:page], per_page: 9 do
query do
boolean do
must { term :user_city, params[:user_city] } if params[:user_city].present?
end
end
facet "cities" do
terms :user_city
end
end
end
model = self
settings ElasticsearchAutocomplete::Analyzers::AC_BASE do
mapping _source: {enabled: true, includes: %w(car_city name description)} do
indexes :car_city, :type => 'string', :index => :not_analyzed
end
end
def to_indexed_json
to_json(methods: [:user_city])
end
def user_city
user.city
end
end

Using filters with elasticsearch and tire to mimic named scopes in rails

I have an Exhibition model that has three named scopes:
scope :opening, -> { where(start_date: (Date.today .... }
scope :closing, -> { where(end_date: .... }
scope :reception, -> { where("reception is not null")... }
I have the following tire mapping inside the same model:
tire.mapping do
...
indexes :start_date, type: "date"
indexes :end_date, type: "date"
indexes :reception, type: "date"
...
end
And my search method:
def self.search(params)
tire.search(load: true) do
query { string params[:query] } if params[:query].present?
...
end
Inside my exhibitions_controller.rb I have the following (and the same for closing and reception)
def opening_this_week
#exhibitions = Exhibition.opening.search(params)
...
end
When I actually search for one of the indexed fields I get back the correct response of just the exhibitions that meet the query and are opening this week, however, when I want all opening exhibitions and do not pass a search query I get ALL exhibitions, not just those that are opening this week.
What am I doing wrong here? How do I transition from a named scope to a facet? Or should I be doing it as a filter?

Building the right search request with gem Tire and ElasticSearch

I am trying to implement simple search request for my Flower Store, but as I am new to Tire and ElasticSearch, I cannot get how to do it.
I have searchable model Product, and habtm models Category, Colour, Flower. What I want is checkboxes for each association, producing request similiar to this:
http://example.com/search?q=some%20query&category_names[]=bouquet&category_names[]=marriage&colour_names[]=red&colour_names[]=yellow&colour_names[]=white&flower_names[]=rose&flower_names[]=tulip&price_min=100&price_max=1000
But I need to show only those products, which:
a) prestent in any of the requested categories (OR);
b) have all of the requested colours (AND);
c) have all of the requested flowers (AND);
d) enter the price range between price_min and price_max.
The q parameter is to search any text, entered to the text field, in the names of associations (let's say, red roses bouquet), and show it with the upper criteria too.
For now I have only
class Product
include Mongoid::Document
include Tire::Model::Search
include Tire::Model::Callbacks
has_and_belongs_to_many :categories
has_and_belongs_to_many :colours
has_and_belongs_to_many :flowers
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 8) do |search|
search.query { string params[:query] } if params[:query].present?
end
end
def to_indexed_json
self.to_json(methods: [:category_names, :colour_names])
end
def category_names
self.categories.collect { |c| c.name }
end
def colour_names
self.colours.collect { |c| c.name }
end
end
and do not know how to configure it. I read about filters and facets, but because English is not my native I cannot uderstand what is what and how to use them.
I did it so:
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 8) do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { string params[:category_names] } if params[:category_names].present?
must { string params[:colour_names], default_operator: "AND" } if params[:colour_names].present?
must { range :price, { gte: params[:price_min], lte: params[:price_max] } } if params[:price_min].present? && params[:price_max].present?
end
end
# raise to_curl
end
end

Elastic Search/Tire: How do I filter a boolean attribute?

I want to filter the private boolean of my class so it only shows resources that aren't private but it's not working for me. (I dumbed down the code tremendously)
mapping do
indexes :private, type: "boolean"
indexes :name, type: "string"
end
end
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 20) do
query { string params[:query] } if params[:query].present?
# So far I've tried...
# filter :bool, :private => ["false"]
# filter :bool, private: false
end
end
How do I do this correctly?
filter :term, :private => false
Should do the trick. Depending on whether you want to do stuff with facets it may be more efficient to do your filtering a filtered query rather than at the top level, ie
tire.search(...) do
query do
filtered do
query { string, params[:query] }
filter :term, :private => false
end
end
end
It shouldn't change the results though.
You can also do this with a bool filter, but not quite how you tried - within a bool filter you need to build up a structure that says what's optional and what's not
For example
tire.search(load: true, page: params[:page], per_page: 20) do
query { string params[:query] } if params[:query].present
filter :bool, :must => {:term => {:private => true}}
end
A bool filter is slower than using an and filter (which is what tire does behind the scenes if you specify multiple filters) but obviously gives you more flexibility.
You can try:
tire.search(load: true, page: params[:page], per_page: 20) do
query do
boolean do
must { string params[:query] } if params[:query].present?
must { term :private, true }
end
end
end
According to the elasticsearch - guide, booleans are stored as T or F, so I would try filtering by T or F.
For example
filter :terms, :private => ['T']
I haven't actually used tires, this is just based on some research on the guide and in the examples.

RoR: Elasticsearch sort by mapped field

I have Player and Player have fullname from Profil. I use elasticsearch on Player. I need default sort by fullname. How can I set it? My code from Player class file:
......
mapping do
indexes :id, type: 'integer'
indexes :fullname, boost: 10
indexes :name
indexes :surname
indexes :position_name
indexes :info
indexes :club_id, type: 'integer'
indexes :club_name
end
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 20) do
query do
boolean do
if params[:query].present?
must { string params[:query], default_operator: "AND" }
else
must {string '*'}
end
must { term :position_id, params[:position_id]} if params[:position_id].present?
must { term :club_id, params[:club_id]} if params[:club_id].present?
end
end
# filter :term, user_id: params[:user_id] if params[:user_id].present?
# sort { by Item.column_names.include?(params[:sort]) ? params[:sort] : "created_at", %w[asc desc].include?(params[:direction]) ? params[:direction] : "desc" } if params[:query].blank?
sort { by Player.column_names.include?(params[:sort]) ? params[:sort] : :id ,%w[asc desc].include?(params[:direction]) ? params[:direction] : "desc"}
facet "positions" do
terms :position_id
end
facet "clubs" do
terms :club_id
end
# raise to_curl
end
end
def to_indexed_json
to_json(methods: [:fullname, :name, :surname, :position_name, :club_name])
end
def fullname
self.profil.fullname
end
......
If I changed :id to :fullname in sort I have this error:
500 : {"error":"SearchPhaseExecutionException[Failed to execute phase [query], total failure; shardFailures {[DBgvi6JiQAi0FTwhonq8Ag][players][0]: QueryPhaseExecutionException[[players][0]: query[ConstantScore(NotDeleted(cache(_type:player)))],from[0],size[20],sort[<custom:\"fullname\": org.elasticsearch.index.field.data.strings.StringFieldDataType$1#33a626ac>!]: Query Failed [Failed to execute main query]]; nested: IOException[Can't sort on string types with more than one value per doc, or more than one token per field]; }{[DBgvi6JiQAi0FTwhonq8Ag][players][4]: QueryPhaseExecutionException[[players][4]: query[ConstantScore(NotDeleted(cache(_type:player)))],from[0],size[20],sort[<custom:\"fullname\": org.elasticsearch.index.field.data.strings.StringFieldDataType$1#3274eb8a>!]: Query Failed [Failed to execute main query]]; nested: IOException[Can't sort on string types with more than one value per doc, or more than one token per field]; }]","status":500}
I got it! After dumb amounts of research I found this article here. The first answer mentions:
The field user by default gets analyzed and broken down into one or more tokens. If it gets broken down into more than one token, then you can't really sort on it. If you want to sort on it, you either need to set it i(in the mappings) as not analyzed
In your situation, all you would need to do is:
mapping do
indexes :id, type: 'integer'
indexes :fullname, :index => 'not_analyzed'
indexes :name
indexes :surname
indexes :position_name
indexes :info
indexes :club_id, type: 'integer'
indexes :club_name
end
And it should work.
sort { by Player.column_names.include?(params[:sort]) ? params[:sort] : :fullname ,%w[asc desc].include?(params[:direction]) ? params[:direction] : "desc"}
Changing 'id' to 'fullname' should work.

Resources