MongoDB Ruby aggregation with dates - ruby-on-rails

I'm having trouble with the MongoDB ruby driver (via mongoid) using aggregation.
I would like to match against a date using comparison operators.
match = { '$match' => { 'created_at' => { '$gte' => DateTime.parse('2012-08-01') } } }
group = { '$group' => { '_id' => 'foo' } }
MyModel.collection.aggregate([match, group])
I don't know what to put in the first line for the date. The code as written above will throw me an undefined method __bson_dump__ for DateTime exception. Using a string doesn't seem to work, either.
Any suggestions are welcome. MongoID's built in methods give me what I need for the selection but not for the grouping.

Figured it out, use a Time object instead of DateTime
match = { '$match' => { 'created_at' => { '$gte' => Time.parse('2012-08-01') } } }

Try this.
h = { '$match' => { 'created_at' => { '$gte' => new Date("2012/08/01") } } }

Related

Mongoid Syntax for running range queries with or condition

Suppose we need to write a range query in Mongoid. Let the field to be queried be range_field, then we do something like this
where(:range_field.lte => some-date-time, :range_field.gte => some-date-time)
But if i want to run a query to choose any of multiple ranges, i'd have to do
.or({:range_field.lte => some-date-time1, :range_field.gte => some-date-time2},{:range_field.lte => some-date-time3, :range_field.gte => some-date-time4})
This apparently doesn't work.
How can I run such queries with Mongoid?
When you say:
:range_field.lte => some_date_time
you're calling a method, lte, that Mongoid monkey patches in Symbol. That method returns an Origin::Key instance that is wrapped around the underlying $lte operator. Somewhere inside Mongoid that Origin::Key will be converted to something that MongoDB will understand:
{ range_field: { $lte: some_date_time } }
If you look at what
where(:range_field.lte => t1, :range_field.gte => t2)
becomes by calling selector on the result, you'll see something like this:
{
"created_at" => {
:$gte => t2,
:$lte => t1
}
}
and everything will work fine.
However, if we use #or and call selector to see the underlying query, we see that Mongoid is expanding the Origin::Keys one by one and merging the results:
or({:range_field.lte => t1, :range_field.gte => t2})
# is expanded as though as you said
or({ :range_field => { :$lte => t1 }, :range_field => { :$gte => t2 } })
# which is the same as
or({ :range_field => { :$gte => t2 } })
Essentially, Mongoid is being inconsistent as to how it expands the Origin::Keys. You'll even get the same confusing result if you use :$or instead of #or:
where(:$or => [ {:range_field.lte => t1, :range_field.gte => t2} ]).selector
will say:
{ "$or" => [ { "range_field" => { "$gte" => t2 } } ] }
The solution is to not use the Symbol monkey patched methods and do that part by hand:
or(
{ :range_field => { :$lte => t1, :$gte => t2 } },
{ :range_field => { :$lte => t3, :$gte => t4 } },
...
)

What would be the best way to convert this hash?

I have a Ruby hash that I need to convert to another format.
Considering that the 'array' size is unknown/unlimited, how would you 'flatten' this hash into the desired format?
Original Hash
{
:parameters => {
:'userResponse.objectInstanceType' => 'QuesAnsResponse',
:'userResponse.quesAnsDetailArray' => {
:'0' => {
'.answer'=> 'Texas'
},
:'1' => {
'.answer' => 'w3schools'
}
}
}
}
Desired Format:
{
:parameters => {
:'userResponse.objectInstanceType' => 'QuestionAnsResponse',
:'userResponse.quesAnsDetailArray[0].answer' => 'Texas',
:'userResponse.quesAnsDetailArray[1].answer' => 'w3schools'
}
}
If you can make a few assumptions about the structure of the input i.e. that it has the numbered responses inside a "userResponse.quesAnsDetailArray" section then you could do something along these lines:
new_hash = { 'parameters' =>
{ 'userResponse.objectInstanceType' => 'QuestionAnsResponse' } }
hash['parameters']['userResponse.quesAnsDetailArray'].each_pair do |index, details|
details.each_pair do |field, value|
new_hash['parameters']["userResponse.quesAnsDetailArray[#{index}]#{field}"] = value
end
end
I have assumed the hash is as follows:
h = {
'parameters'=> {
'userResponse.objectInstanceType'=> 'QuesAnsResponse',
'userResponse.quesAnsDetailArray'=> {
'0'=> {
'.answer'=> '',
'.answerFieldType'=> 'text',
'.isRequired'=> 'true',
'.metaData'=> 'QUESTION_1',
'.questionFieldType'=> 'label',
'.question'=> 'What is the name of your state?'
},
'1'=> {
'.answer'=> '',
'.answerFieldType'=> 'text',
'.isRequired'=> 'true',
'.metaData'=> 'QUESTION_2',
'.questionFieldType'=> 'label',
'.question'=> 'What is the name of your first school'
}
}
}
}
and would convert it to the desired format as follows:
{ 'parameters'=> Hash[[
['userResponse.objectInstanceType',
h['parameters']['userResponse.objectInstanceType']],
*h['parameters']['userResponse.quesAnsDetailArray'].
flat_map { |ndx,f|
["userResponse.quesAnsDetailArray[#{ndx}]"].product(f.to_a) }.
map { |prefix,(suffix,value)| [prefix+suffix, value] } ]]
}
#=> {"parameters"=>
{"userResponse.objectInstanceType"=>"QuesAnsResponse",
"userResponse.quesAnsDetailArray[0].answer"=>"",
"userResponse.quesAnsDetailArray[0].answerFieldType"=>"text",
"userResponse.quesAnsDetailArray[0].isRequired"=>"true",
"userResponse.quesAnsDetailArray[0].metaData"=>"QUESTION_1",
"userResponse.quesAnsDetailArray[0].questionFieldType"=>"label",
"userResponse.quesAnsDetailArray[0].question"=>
"What is the name of your state?",
"userResponse.quesAnsDetailArray[1].answer"=>"",
"userResponse.quesAnsDetailArray[1].answerFieldType"=>"text",
"userResponse.quesAnsDetailArray[1].isRequired"=>"true",
"userResponse.quesAnsDetailArray[1].metaData"=>"QUESTION_2",
"userResponse.quesAnsDetailArray[1].questionFieldType"=>"label",
"userResponse.quesAnsDetailArray[1].question"=>
"What is the name of your first school"}}

Insert values from active record into a hash

I have this:
produtos = LineItem.select('codigosku, quantity').where("cart_id = #{session[:cart_id] } ")
I need to insert the result of this select (produto variable), here:
message = Hash.new
message = {
"tem:carrinho" => {"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => {"gpa:DadosListaProdutoCarrinhoDTO" =>
{
HERE! VALUES OF "PRODUTOS VARIABLE"
}
}
}
}
How can I do this?
Thanks in advance!
create your array:
line_items_array = line_items.map{|li| li.attributes }
Then insert the array within your hash.
like in apneadiving example, use map to create an array from the produtos data; use attributes to return all data (it is a hash) from your selected data
message = {
"tem:carrinho" => {
"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => {
"gpa:DadosListaProdutoCarrinhoDTO" => produtos.map { |item| item.attributes }
}
}
}
or if you need to be more specific about the keys in the produtos and append it after initialization
# initialize the Produtos to nil
message = {
"tem:carrinho" => {
"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => nil
}
}
# build an array of DadosListaProdutoCarrinhoDTO
list = produtos.map do |item|
{
"gpa:DadosListaProdutoCarrinhoDTO" => {
"codigosku" => item.codigosku,
"quantity" => item.quantity
}
}
end
# set the Produtos key to an array of DadosListaProdutoCarrinhoDTO
message["tem:carrinho"].merge!({ "gpa:Produtos" => list })

How to filter search by attribute only if it exists using ElasticSearch and Tire?

Right now I wrote
Tire.search INDEX_NAME do
query do
filtered do
query { string term }
filter :or, { missing: { field: :app_id } },
{ terms: { app_id: app_ids } }
end
end
end.results.to_a
Well returning items that either have no app_id or one that matches your terms sounds like a job for an or filter - I'd try
filter :or, [
{:not => {:exists => {:field => :app_id}}},
{:terms => {:app_id => app_ids}}
]

Why savon :attributes! not working with an object called Objects

When supplying savon with:
hash = {
"Objects" => { //stuff here },
:attributes! => { "Objects" => {"xsi:type" => "Something"}}
}
I get:
<Objects>...</Objects>
When supplying savon with anything else i get the expected result:
hash = {
"foo" => { //stuff here },
:attributes! => { "foo" => {"xsi:type" => "Something"}}
}
I get:
<foo xsi:type="Something"></foo>
I must use the string "Objects" as the key. I am coding to a 3rd party SOAP web service. I cannot use a symbol because the first letter would become a lower cap.
thanks,
You have to change :attributes! to :#xsi:type=>"Something" within the hash where you want the attribute
Like:
"foo"=>{:#xsi:type=>'something', //stuff here}

Resources