How do I query array elements in elastic search - ruby-on-rails

This is the index of my model in the elastic search
{
"_index":"cars",
"_type":"car",
"_id":"3275",
"_version":4,
"_score":1,
"_source":{
"category_id": 6,
"car_branches":[
{
"id":32,
"name":"Type1"
},
{
"id":33,
"name":"Type2"
},
{
"id":36,
"name":"Type3"
}
],
}
}
I can query category_id with
Car.__elasticsearch__.search query:{match:{category_id: 6}}
How do I query for car_branches? I tried this
response = Car.__elasticsearch__.search query:{match:{car_branches:[id: 32]}}
I am getting Elasticsearch::Transport::Transport::Errors::BadRequest: [400]

You first need to delete your index first and recreate it. Before doing so, you need to change your mapping and make the car_branches field nested, like this:
indexes :car_branches, type: 'nested' do
indexes :id
indexes :name
Then you'll be able to make the query your want like this:
response = Car.__elasticsearch__.search query:{nested:{path: 'car_branches', query:{term:{'car_branches.id':[32]}}}}

Related

Using scroll api via elasticsearch-model

For the life of me I can't find any reference to using the ElasticSearch scroll api from within Ruby on Rails and the elastisearch-model (or rails or dsl) gem.
The only thing they do reference in the docs is calling scroll directly on the client, which kind of defeats the purpose. Also, it does not use the client or any client settings you've already set in your Rails app.
I want to do something like this.
Here is the ElasticSearch query that works from within the Kibana Dev Tools:
GET model_index/_search?scroll=1m
{
"size": 100,
"query": {
"match": {
"tenant_id": 3196
}
},
"_source": "id"
}
I would have thought that I could call something like
MyModel.search scroll: '1m', ...
but instead it seems like I need to do:
# First create a client by hand
client = Elasticssearch::Client.new
result = client.search index: 'model_index',
scroll: '1m',
body: { query: { match: { tenant_id: 3196 } }, sort: '_id' }
Does anyone have any more user-friendly examples?
As per elasticsearch guide -
We no longer recommend using the scroll API for deep pagination. If you need to preserve the index state while paging through more than 10,000 hits, use the search_after parameter with a point in time (PIT).
Ref - https://www.elastic.co/guide/en/elasticsearch/reference/7.x/scroll-api.html
Further edit for above question -
To scroll on document need to use scroll_id from result, to get next set of result.
body = { query: { match: { tenant_id: 3196 } }, sort: '_id' }
response = Elasticsearch::Client.new.search(
index: 'model_index',
scroll: "1m",
body: body,
size: 3000
)
loop do
hits = response.dig('hits', 'hits')
break if hits.empty?
hits.each do |hit|
# do something
end
response = Elasticsearch::Client.new.scroll(
:body => { :scroll_id => response['_scroll_id'] },
:scroll => '1m'
)
end

Convert mongoid aggregated result to ruby object

I am querying a Model in Rails using mongoid
product1 = Products.where(list_id: params[:list_id])
#records = product1.collection.aggregate([
{ "$match": { "rowdatas.field_value": {"$regex": params[:search], "$options": "i"} }},
{ "$sort": { "rowdatas.field_name": 1 , "rowdatas.field_value": 1 } }
])
After the results are retrieved in #records , their class is Mongoid Collection View. This is practically unusable since its not a Ruby object. How do I use it like a regular object that can be iterated , paginated similar to a mongoid criteria object (like product1).
Solution is to use Mongoid::Document#instantiate method.
product1 = Products.where(list_id: params[:list_id])
#records = product1.collection.aggregate([
{ "$match": { "rowdatas.field_value": {"$regex": params[:search], "$options": "i"} }},
{ "$sort": { "rowdatas.field_name": 1 , "rowdatas.field_value": 1 } }
]).map{|product_attributes| Product.instantiate(product_attributes)}
Make sure you use $limit and $skip to paginate your results.

Merging dynamically generated attributes into a new entry and summing their values

I'm looking for some advice on how to properly merge some key/value pairs into a separate database entry and summing their values.
I have a Task which has a Vendor_Upload which has many Vendor_Shipping_Logs which has many Vendor_Shipping_Log_Products. I'm not sure if the deep nesting makes a difference, but the important values to look at here are the Item_ID and Quantity.
This is currently how the parameters are spit out:
Parameters: {
"task"=>{
"task_type"=>"Vendor Upload",
"vendor_upload_attributes"=>{
"upload_type"=>"Warranty Orders",
"vendor_shipping_logs_attributes"=>{
"1490674883303"=>{
"guest_name"=>"Martin Crane",
"order_number"=>"33101",
"vendor_shipping_log_products_attributes"=>{
"1490675774108"=>{
"item_id"=>"211",
"quantity"=>"3"
},
"1490675775147"=>{
"item_id"=>"213",
"quantity"=>"6"
}
}
},
"1490674884454"=>{
"guest_name"=>"Frasier Crane",
"order_number"=>"33102",
"vendor_shipping_log_products_attributes"=>{
"1490675808026"=>{
"item_id"=>"214",
"quantity"=>"10"
},
"1490675808744"=>{
"item_id"=>"213",
"quantity"=>"1"
}
}
},
"1490674885293"=>{
"guest_name"=>"Niles Crane",
"order_number"=>"33103",
"vendor_shipping_log_products_attributes"=>{
"1490675837184"=>{
"item_id"=>"211",
"quantity"=>"3"
}
}
},
"1490674886373"=>{
"guest_name"=>"Daphne Moon",
"order_number"=>"33104",
"vendor_shipping_log_products_attributes"=>{
"1490675852950"=>{
"item_id"=>"213",
"quantity"=>"8"
},
"1490675853845"=>{
"item_id"=>"214",
"quantity"=>"11"
}
}
}
}
}
}
}
Upon submission I want to merge each unique Vendor_Shipping_Log_Products Item_IDs and sum their quantities into a new Stockmovement_Batch as a nested Stockmovement to keep my inventories up to date.
See example patameters here of what I would like the output to look like:
Parameters: {
"stockmovement_batch"=>{
"stockmovement_type"=>"Ecomm Order",
"stockmovements_attributes"=>{
"1490676054881"=>{
"item_id"=>"211",
"adjust_quantity"=>"-6"
},
"1490676055897"=>{
"item_id"=>"213",
"adjust_quantity"=>"-15"
},
"1490676057616"=>{
"item_id"=>"214",
"adjust_quantity"=>"-21"
}
}
}
}
Is this something I can do all in one simple go, or do I have to stick with doing each process in a separate form?
First you need to separate out the values you want to iterate through:
data = params.require("task")
.require("vendor_upload_attributes")
.require("vendor_shipping_logs_attributes")
Then pull the vendor_shipping_log_products_attributes and flatten it to an array of hashes:
logs = data.values.map do |h|
h["vendor_shipping_log_products_attributes"].values
end.flatten
# => [{"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"6"}, {"item_id"=>"214", "quantity"=>"10"}, {"item_id"=>"213", "quantity"=>"1"}, {"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"8"}, {"item_id"=>"214", "quantity"=>"11"}]
Then we merge the data by creating a intermediary hash where we use the item_id as keys.
stockmovements = logs.each_with_object({}) do |hash, memo|
id = hash["item_id"]
memo[id] ||= []
memo[id].push(hash["quantity"].to_i)
end
# => {"211"=>[3, 3], "213"=>[6, 1, 8], "214"=>[10, 11]}
We then can then map the result and sum the values:
stockmovements.map do |(k,v)|
{
item_id: k,
adjust_quantity: 0 - v.sum
}
end
# => [{:item_id=>"211", :adjust_quantity=>-6}, {:item_id=>"213", :adjust_quantity=>-15}, {:item_id=>"214", :adjust_quantity=>-21}]

Multiple Aggregations on same level in Elasticsearch-rails

I am trying to perform multiple aggregations at the same level with ElasticSearch using the elasticsearch-rails and elasticsearch-model gems.
In the query hash that I am generating, I have the following -
def query_hash(params, current_person = nil, manager_id = nil)
aggs = {}
aggs[:home_country_id] = {terms: {field: "home_country_id"}}
aggs[:home_region_id] = {terms: {field: "home_region_id"}}
{
sort: [ { created_at: { order: "desc" } }, id: { order: "desc" } ],
aggs: aggs
}
end
The response I stored in an object es_response.
When I search for both the aggregations, I can only find the last one in the response.
es_response.response["aggregrations"] only has the response of the latest aggregation object, home_region_id.
I couldn't find much documentation on the ES Reference on structuring multiple aggregations on the same level although there was a lot about nesting aggregations.
How can I fix this?
My ES version is 5.1

How can I express this SQL in Elasticsearch-rails and Elasticsearch-model?

I used gem elasticsearch-rails and elasticsearch-model and I have difficult to write this query in elasticsearch-rails.
SELECT "news".* FROM "news"
WHERE "news"."is_active" = 'true' AND
((priority is not null AND created_at > '2014-07-08 08:55:52.888587') OR
(created_at > '2014-07-08 08:55:52.888820' AND is_persisted = 't') )
ORDER BY "news"."priority" ASC, "news"."created_at" DESC
LIMIT 10 OFFSET 0
In my previous project I used "Tire Model", I used something like this:
filter :bool, must: {and: [{term: {field with options}}, {term: {field with options}}]}, It works in tire model
But if I use something like this in elasticsearch-rails, it throws missing filtered error
I write something like this for filtering active record:
def self.news_index(page = 1)
query =:
response=self.elasticsearch.search query: { match: { is_active: true }}
response=response.page(page)
end
In the above method, I want to add combined filter with bool option. Can anyone guide me?
Elasticsearch-ruby is far closer to the elasticsearch DSL when it comes to querying. Most of the time you'll be passing in hashes (or hash-like objects) to the query method.
Something like this should get you close:
self.search query: {
filtered: {
query: { match: { is_active: true }},
filter: {
bool: {
must: {
and: [{term: {field with options}}, {term: {field with options}}]
}
}
}
}
}
The difference is that instead of filter being a method call in the tire query which took arguments :bool and the filter. You now need to specify a :filter key with a hash value which then contains a :bool key with your existing filter as the value.

Resources