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

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.

Related

Sort an array of hashes by value if such value is not nil (using sort_by)

I am trying to perform a sort_by on a hash, but whenever I have a nil value I get:
comparison of DateTime with nil failed
My goal is to perform a nil check (.present?) on x[:last_posted_at] inside the sort_by method. Is that possible? Example code:
posts = [
{ "name"=>"Alice", "last_posted_at"=> some_datetime },
{ "name"=>"Bob", "last_posted_at"=> nil},
{ "name"=>"Clark", "last_posted_at"=> some_datetime - 1}
]
# expected result
posts.sort_by.{ |x| x[:last_posted_at] } #compare only if value is not nil
#=> [{"name"=>"Alice", "last_posted_at"=> some_datetime},
# {"name"=>"Clark", "last_posted_at"=> some_datetime - 1},
# {"name"=>"Bob", "last_posted_at"=> nil}]
I looked into the sort_by documentation and some of the posts here in stackoverflow, but I cannot find my answer. Any help or links are welcome! Thanks in advance!
I like Schwern's approach. But if there are more records without a date then another option might be to separate record without dates from the records with dates and only sort thoses with a date like this:
posts
.partition { |v| v['last_posted_at'] } # separate by date presence
.tap { |v| v.first.sort_by! { |v| v['last_posted_at']} } # only sort entries with a date
.flatten # combine into one list again
Use presence to return the value or nil, and || to return a default value if it is blank.
# Something sufficiently old to be older than any other time.
nil_time = Time.at(0)
posts.sort_by.{ |x|
x[:last_posted_at].presence || nil_time
}
Note: DateTime is deprecated.

Query on Params

I want to get records based on params received. Sometimes I receive 2 params or sometimes 3. I have written a code to get results when all 3 params received but when I receive only 1 param am getting 0 results.
Example
Office.where(state: params[:state], type: params[:type]).where("name like ?","%#{params[:name]}%")
When i get values like
{state: "KA", type: "private", name: "google"}
But when I get the only {name: "google"} I get no records
I have tried with condition
If params[:name].present? && params[:state].present? && params[:type].present?
query
elsif condition
query
end
Let me know how can I solve this or any better way
You can do something like this
In controller
filter_params = params.slice(:state, :type, :name)
Office.filter(filter_params)
In Office model
scope :state, -> (state) { where(state: state) }
scope :type, -> (type) { where(type: type) }
scope :name, -> (name) { where("name LIKE ?", "%#{name}%") }
def self.filter(filter_params)
results = where(nil)
filter_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
PS: It runs a single query irrespective of the number of params!
Hope that helps!
If a parameter is missing, it will probably be blank. If you pass them all in this will result in clauses like type = ''. For example, if only name is passed in you'll get something like...
where name like '%google%' and type = '' and state = ''
You need to strip out the blank fields. There are various ways to do this. Since you have a special case, the name clause, one good way to handle this is to build the query piece by piece.
query = Office.all
query = query.where(state: params[:state]) if params[:state].present?
query = query.where(type: params[:type]) if params[:type].present?
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?
The query does not execute until you fetch the values from query.
If there's a lot of simple parameters you can make a Hash and remove the pairs with a blank value.
qparams = {
state: params[:state],
type: params[:type]
}.select { |k,v|
v.present?
}
query = Office.where(qparams)
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?
Or use the handy compact_blank gem.
using CompactBlank
qparams = {
state: params[:state],
type: params[:type]
}.compact_blank
query = Office.where(qparams)
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?

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.

How do you get the Chewy gem to filter results?

I just started on the Chewy gem, looks like a great idea, but I'm unable to get it working properly with even a super basic query.
I have indexed my Lead model as LeadsIndex.
Here I have a query that works:
irb(main):068:0> LeadsIndex.filter.first
LeadsIndex Search (8.9ms) {:body=>{}, :index=>["leads"], :type=>[]}
=> #<LeadsIndex::Lead:0x007ff76324cbf8 #attributes={"id"=>"14", "name"=>"Victoria Roob", "_score"=>1.0, "_explanation"=>nil},
#_data={"_index"=>"leads", "_type"=>"lead", "_id"=>"14", "_score"=>1.0, "_source"=>{"name"=>"Victoria Roob"}}>
But when I try to search for that very record, it shows no results:
irb(main):071:0> LeadsIndex.filter { name == "Victoria Roob" }.first
LeadsIndex Search (7.4ms) {:body=>{:query=>{:filtered=>{:query=>{:match_all=>{}}, :filter=>{:term=>{"name"=>"Victoria Roob"}}}}}, :index=>["leads"], :type=>[]}
=> nil
Am I doing something wrong?
You can either match phrase:
LeadsIndex.query(match_phrase: {name: "Victoria Root"}).first
or else chain the queries together:
LeadsIndex.filter { name == "Victoria" }.filter { name == "Roob" }.first

groovy query is being generated with "and' rather than "or"

I am trying to run a query to find entries based on 3 fields with "or" conditions. This is my query:
def results = Person.createCriteria().list(params) {
eq "skillset.id", original.skillset.id
or { eq "primarySkill.id", original.primarySkill.id }
or { eq "jobRole.id", original.jobRole.id }
not { eq "id", original.id }
order "name", "asc"
}
In the person object, the 3 fields that I care about: skillset, primarySkill, and jobRole, are instances to another object. I want to find any person where any of those fields match, except for the original person. However, the hibernate query appears to be making an "and" query rather than an "or" query. I could type out the SQL myself, but I'd like to learn how to do it the groovy way.
And this is the query generated by hibernate
select this_.id as id5_0_,
this_.version as version5_0_,
this_.account_id as account3_5_0_,
this_.band as band5_0_, t
his_.end_date as end5_5_0_,
this_.job_role_id as job6_5_0_,
this_.name as name5_0_,
this_.primary_skill_id as primary8_5_0_,
this_.professional_market_place as professi9_5_0_,
this_.project_delivery_manager_id as project10_5_0_,
this_.project_manager_id as project11_5_0_,
this_.project_name as project12_5_0_,
this_.rate as rate5_0_,
this_.resource_deployment_manager_id as resource14_5_0_,
this_.skillset_id as skillset15_5_0_,
this_.start_date as start16_5_0_,
this_.work_location as work17_5_0_
from person this_ where this_.skillset_id=?
and (this_.primary_skill_id=?)
and (this_.job_role_id=?)
order by this_.name asc limit ?
I believe it should be
def results = Person.createCriteria().list(params) {
or {
eq "skillset.id", original.skillset.id
eq "primarySkill.id", original.primarySkill.id
eq "jobRole.id", original.jobRole.id
}
not { eq "id", original.id }
order "name", "asc"
}

Resources