Mongoid or/any_of unexpected behaviour - ruby-on-rails

I'm having an issue with mongoid any_of. I'm trying to find objects that have either one field > 0, or another one > 0. My query is :
Model.any_of(best_friend_method.gt => 0, method.gt => 0).desc(best_friend_method, method)
It is "translated" in :
#<Mongoid::Criteria
selector: {"$or"=>[{:best_friends_lc_sum=>{"$gt"=>0}, :lc_sum=>{"$gt"=>0}}]},
options: {:sort=>[[:best_friends_lc_sum, :desc], [:lc_sum, :desc]]},
class: FbAlbum,
embedded: false>
As I understand it, this is what I want. But it only returns me 6 results. Model.where(:best_friends_lc_sum.gt => 0).count returns me 6 results too, but Model.where(:lc_sum.gt => 0).count returns me ~850 objects.
I expect my query to return the union of those two : is a mongoid/mongodb error, or am I doing something wrong ?
FYI : mongoid 2.4.5, mongodb 2.0.2, rails 3.1.3
Thanks for your time!

It's because you pass only one args and not 2 args. So it's like you have no $or usage.
Try :
Model.any_of({best_friend_method.gt => 0}, {method.gt => 0}).desc(best_friend_method, method)
In this case the Criteria become :
#<Mongoid::Criteria
selector: {"$or"=>[{:best_friends_lc_sum=>{"$gt"=>0}}, {:lc_sum=>{"$gt"=>0}}]},
options: {:sort=>[[:best_friends_lc_sum, :desc], [:lc_sum, :desc]]},
class: FbAlbum,
embedded: false>
Sometime the usage of {} is mandatory to separate different hash.

In case this helps someone...In Mongoid 3, the Origin gem provides the syntax for querying. Here is the list of methods that you can use to write your Mongoid 3 queries. Among those methods is the or method which allows you to perform an $or query:
# Mongoid query:
Model.or(
{ name: "Martin" }, { name: "Dave" }
)
# resulting MongoDB query:
{
"$or" => [
{ "name" => "Martin" }, { "name" => "Dave" }
]
}
Using the OP's original example, it can be rewritten as:
Model.or(
{ best_friend_method.gt => 0 },
{ method.gt => 0 }
).order_by(
best_friend_method,
method
)
At least one of the hashes passed to the or method must match in order for a record to be returned.

Related

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

Ruby - Access value from json array

I am creating an array of fields
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = { field: field, data: { type: 'Text', description: '' } }
fields_list.push field_def
end
fields_list
end
The fields_list is being set to a jsonb field.
Lets say I pass in
create_fields ['Ford', 'BMW', 'Fiat']
Json result is an array:
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
How can I access the 'Ford' from the json array? Am i creating the array incorrectly? Is there a better way to create this array so I can access the field i want?
This assertion passes assert_equal(3, fields.count)
However i want to get 'Ford' and check it's properties, e.g. type = 'Text', type could equal 'Number' or whatever.
The result of your create_fields method with the specified parameters is the following:
[
{:field=>"Ford", :data=>{:type=>"Text", :description=>""}},
{:field=>"BMW", :data=>{:type=>"Text", :description=>""}},
{:field=>"Fiat", :data=>{:type=>"Text", :description=>""}}
]
It means that if you want to access the line belonging to "Ford", you need to search for it like:
2.3.1 :019 > arr.select{|e| e[:field] == "Ford" }
=> [{:field=>"Ford", :data=>{:type=>"Text", :description=>""}}]
2.3.1 :020 > arr.select{|e| e[:field] == "Ford" }[0][:data][:type]
=> "Text"
This is not optimal, because you need to search an array O(n) instead of using the pros of a hash. If there are e.g.: 2 "Ford" lines, you'll get an array which contains 2 elements, harder to handle collisions in field value.
It would be better if you created the array like:
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = [field, { type: 'Text', description: '' } ]
fields_list.push field_def
end
Hash[fields_list]
end
If you choose this version, you can access the members like:
2.3.1 :072 > arr = create_fields ['Ford', 'BMW', 'Fiat']
=> {"Ford"=>{:type=>"Text", :description=>""}, "BMW"=>{:type=>"Text", :description=>""}, "Fiat"=>{:type=>"Text", :description=>""}}
2.3.1 :073 > arr["Ford"]
=> {:type=>"Text", :description=>""}
2.3.1 :074 > arr["Ford"][:type]
=> "Text"
Both of the above examples are Ruby dictionaries / Hashes.
If you want to create a JSON from this, you will need to convert it:
2.3.1 :077 > require 'json'
=> true
2.3.1 :078 > arr.to_json
=> "{\"Ford\":{\"type\":\"Text\",\"description\":\"\"},\"BMW\":{\"type\":\"Text\",\"description\":\"\"},\"Fiat\":{\"type\":\"Text\",\"description\":\"\"}}"
This is a structure that makes more sense to me for accessing values based on known keys:
def create_fields fields
fields_hash = {}
fields.each do |field|
fields_hash[field] = {type: 'Text', description: ''}
end
fields_hash
end
# The hash for fields_hash will look something like this:
{
Ford: {
type: "Text",
description: ""
},
BMW: {...},
Fiat: {...}
}
This will allow you to access the values like so: fields[:Ford][:type] in ruby and fields.Ford.type in JSON. Sounds like it would be easier to return an Object rather than an Array. You can access the values based on the keys more easily this way, and still have the option of looping through the object if you want.
Obviously, there are several ways of creating or accessing your data, but I'd always lean towards the developer picking a data structure best suited for your application.
In your case currently, in order to access the Ford hash, you could use the Ruby Array#detect method as such:
ford = fields_list.detect{|field_hash| field_hash['field'] == 'Ford' }
ford['data'] # => {"type"=>"Text", "description"=>""}
ford['data']['type'] # => 'Text'
So, you have result of your method:
result =
[
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
]
to get 'Ford' from it you can use simple method detect
result.detect { |obj| obj['field'] == 'Ford' }
#=> { "field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}
Also I recommend you to edit your method to make it more readable:
def create_fields(fields)
fields.map do |field|
{
field: field,
data: {
type: 'Text',
description: ''
}
}
end
end

Ruby Mongo Driver Projection Elemmatch

Following the code in http://www.w3resource.com/mongodb/mongodb-elemmatch-projection-operators.php I have set up a test database using the ruby mongodb driver.
For those following along at home, you first need to install the mongo driver as described at https://docs.mongodb.com/ecosystem/tutorial/ruby-driver-tutorial/#creating-a-client, then run the following commands.
client = Mongo::Client.new([ '127.0.0.1:27017'], :database => 'mydb')
test = client['test']
doc = {
"_id" => 1,
"batch" =>10452,
"tran_details" =>[
{
"qty" =>200,
"prate" =>50,
"mrp" =>70
},
{
"qty" =>250,
"prate" =>50,
"mrp" =>60
},
{
"qty" =>190,
"prate" =>55,
"mrp" =>75
}
]
}
test.insert_one(doc)
Insert all of the different docs created in the w3 tutorial.
If you look at example 2 in the w3 tutorial, the translated ruby find is:
test.find({"batch" => 10452}).projection({"tran_details" => {"$elemMatch" => {"prate" => 50, "mrp" => {"$gte" => 70}}}}).to_a
which returns the same result as in the example.
=> [{"_id"=>1, "tran_details"=>[{"qty"=>200, "prate"=>50, "mrp"=>70}]}, {"_id"=>3}, {"_id"=>4}]
My problem is that I would like to constrain the results with the constraints above (mrp gte 70 etc) while also specifying which fields are returned.
For instance, constraining only the tran_details that have a mrp gte 70, but in the results returned only include the prate field (or any subset of the fields).
I can return only the prate field with the query:
test.find({"batch" => 10452}).projection({"tran_details.prate" => 1}).to_a
I would like to combine the effects of the two different projections, but I haven't seen any documentation about how to do that online. If you string the two projections to each other, only the final projection has an effect.
To anyone out there --
The problem can be solved up to one element by using $elemMatch on projection. However, $elemMatch only returns the first result found. To return only parts of embedded documents multiple layers down that fit certain criteria, you need to use the aggregation framework.
test.find({
'tran_details.prate' => { '$gt' => 56 }
}).projection({
tran_details: {
'$elemMatch' => {
prate: { '$gt' => 56 }
}
},
'tran_details.qty' => 1,
'tran_details.mrp' => 1,
batch: 1,
_id: 0
}).to_a
To return only parts of embedded documents multiple layers down that fit certain criteria, you need to use the aggregation framework.
Here is example code
test.aggregate([{
'$match': {
'$or': [
{'batch': 10452}, {'batch': 73292}]}},
{'$project':
{trans_details:
{'$filter': {
input: '$tran_details',
as: 'item',
cond: {'$and':[
{'$gte' => ['$$item.prate', 51]},
{'gt' => ['$$item.prate', 53]}
]}
}
}
}
}]).to_a
If anyone sees this and knows how to dynamically construct queries in ruby from strings please let me know! Something to do with bson but still trying to find relevant documentation. Thanks -

Nested dynamic array rails

I have created an array
steps = [{'title' =>'abc','content' =>'click this', 'target' => 'bca'}]
tours = ['id'=>'tour', 'steps:' => "#{steps}"]
puts tours
Getting following output :
{"id"=>"tour", "steps:"=>"[{\"title\"=>\"abc\", \"content\"=>\"click this\", \"target\"=>\"bca\"}]"}
The structure of the output is right but i don't want these \ in the output.
What should i do to remove these \.
Thanks!
In ruby "#{}" invoke the to_s method on the object. You can check it run the following code: steps.to_s.
Just use:
tours = ['id'=>'tour', 'steps:' => steps]
Because this:
"[{\"title\"=>\"abc\", \"content\"=>\"click this\", \"target\"=>\"bca\"}]"
is a string representation of:
[{'title' =>'abc','content' =>'click this', 'target' => 'bca'}]
Зелёный has the direct answer for you, however, there's a more pressing issue I would point out -- I think you're getting confused between {hashes} and [arrays]
--
An array is a set of unordered data:
array = [3, 4, 5, 6, 0, 5, 3, "cat", "dog"]
Arrays are mainly used for non-sequential collections of data, a good example being product_ids in a shopping cart.
Arrays can only be identified by using the location of the data inside the array:
array[1] # -> 4
array[2] # -> 5
--
A hash is a collection of key:value pairs:
hash = {name: "Greg", type: "cat"}
Hashes are used when you wish to assign multiple values to a single piece of data, and can be called by referencing the "key" of the hash:
hash["name"] #-> Greg
hash["type"] #-> cat
Whilst you can create an array of hashes:
hash_array = [{name: "Greg", type: "cat"}, {name: "Sulla", type: "Dog"}]
... the problem with this is that you cannot call the hashes directly - they have to be through the array:
hash_array["name"] # -> error
hash_array[0]["name"] #-> "Greg"
Thus, I'd use the following in your example:
steps = {'title' =>'abc','content' =>'click this', 'target' => 'bca'}
tours = {id: 'tour', steps: steps}
tours.inspect #-> { id: "tour", steps: { "title" => "abc", "content" => "click this", "target" => "bca" }

ruby on rails looping through a list of key value elements

I have a list of key value pairs like this.
PERSON_SUMMARY = {
first_names: %w(Mike Tim Jim kevin Alan Sara John Sammy t'Renée),
last_names: %w(Robinson Jackson Fox Terry Ali Brits Tyson Willis-St.\ Paul),
offenses: [
{ offense_name:'Speeding',
penalties: [
{ penalty_name: 'Prison', severity: 'Medium' },
{ penalty_name: 'Ticket', severity: 'Low' }
]
},
{ offense_name:'Shoplifting',
penalties: [
{ penalty_name: 'Prison', severity: 'Medium' },
{ penalty_name: 'Fine', severity: 'Low' }
]
}
]
}
I want to store and print only offense_name,**penalty_name** and severity one by one , but I am not able to get the right syntax.
Here is what I have tried so far:
PERSON_SUMMARY[:offenses].each do |offense|
offense_name = offense[:offense_name]
offense[:penalties].each do |penalty|
penalty_name = penalty[:penalty_name]
severity_val = penalty[:severity]
end
end
EDIT: Eventually I need to insert it into the database table through this function:
PersonOffense.where(person_id: person.id).first_or_create(
name: offense_name,
penalty: penalty_name ,
severity: severity_val
end
But I notice an issue, there are multiple penalty names above. Not sure how to insert them.
For example,
I need to insert offense_name twice in my table so that there are 2 entries in the table.
Speeding Prison Medium
Speeding Ticket Low
EDIT: I like Jesse's answer below. How can I use it to insert it in the same order to my method above (inserting offense_name,penalty_name and severity with the result of the answer given below by Jesse.
Rather than just using each, if you map over the offenses, and then flatten them in the end, you'll get what you want:
offenses = PERSON_SUMMARY[:offenses].map do |offense|
offense[:penalties].map do |penalty|
penalty.merge(name: offense[:offense_name])
end
end.flatten
=> [{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
UPDATE
In ruby, the following is a hash, and you already have a hash
So you can just loop through your new array and create your PersonOffense:
offenses.each do |hash|
PersonOffense.where(person_id: person.id).first_or_create( hash )
end

Resources