Can anyone provide advice on the following please?
I'm using searchkick / elasticsearch and would like to search for a key term or terms across multiple fields (name, manufacturer). So for example if im looking for a product called "myproduct" made my "somemanufacturer" i'd expect to see this result appear if I search either "myproduct", "somemanufacturer" or "myproduct somemanufacturer" as both these terms are included either in name or manufacturer fields.
My problem is the following:
@products = Product.search query
Allows all the search terms listed above and returns expected result however as soon as I add
@products = Product.search query, fields: [:name, :manufacturer_name]
It will only return a result for "myproduct", or "somecompany", but not "myproduct somecompany".
Now this isn't a big deal as I can remove the fields option entirely BUT I need to utilise searchkicks word_start for the name field. So my final query is something like this:
@products = Product.search query, fields: [{name: :word_start}, :manufacturer_name]
I'd like users to search for the 1st string of the product and be able to enter a manufacturer too eg "myprod somecompany" unfortunately this returns zero results when I was hoping it would return the product named myproduct, made by somecompany.
Am i missing something really obvious here? I can change add
operator: 'or'
but really i want to be able to part search on the name, add additional terms and if both are present for a particular record it gets returned.
heres my model code also
class Product < ActiveRecord::Base
searchkick word_start: [:name]
end
Thanks
If all of your fields share the same analyzer, you can use elasticsearch
feature called cross_fields
. If it is not the case, you can use query_string
. Unfortunately searchkick
does not support cross_fields
and query_string
yet. So, you have to do it by yourself.
Index (different analyzers)
searchkick merge_mappings: true, mappings: {
product: {
properties: {
name: {
type: 'string',
analyzer: 'searchkick_word_start_index',
copy_to: 'grouped'
},
manufacturer_name: {
type: 'string',
analyzer: 'default_index',
copy_to: 'grouped'
},
grouped: {
raw: {type: 'string', index: 'not_analyzed'}
}
}
}
}
Search with cross_fields
@products = Product.search(body: {
query: {
multi_match: {
query: query,
type: "cross_fields",
operator: "and",
fields: [
"name",
"manufacturer_name",
"grouped",
]
}
}
}
Search with query_string
@products = Product.search(body: {
query: {
query_string: {
query: query,
default_operator: "AND",
fields: [
"name",
"manufacturer_name",
"grouped",
]
}
}
}
Update - Changed my answer to the use of different analyzers and multi fields following the this solution.
Unfortunately passing the query to elasticsearch
by yourself you lose searchkick
features like highlight and conversions, but, you can still do it, adding it to the elasticsearch
query.
Adding hightlight to the query
@products = Product.search(body: {
query: {
...
}, highlight: {
fields: {
name: {},
manufacturer_name: {}
}
}
Adding conversions to the query
@products = Product.search(body: {
query: {
bool: {
must: {
dis_max: {
queries: {
query_string: {
...
}
}
}
},
should: {
nested: {
path: 'conversions',
score_mode: 'sum',
query: {
function_score: {
boost_mode: 'replace',
query: {
match: {
"conversions.query": query
}
},
field_value_factor: {
field: 'conversions.count'
}
}
}
}
}
}
}
The easiest way to do this is to combine the fields into one with the search_data
method.
class Product
def search_data
{
full_name: "#{manufacturer_name} #{name}"
}
end
end
Be sure to reindex afterwards. Then use full name to search. All of Searchkick's features will continue to work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With