Convert mongoid aggregated result to ruby object - ruby-on-rails

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.

Related

How to merge same hashes in array?

If I have:
array = [{:external_product_id=>"A", :quantity=>1}, {:external_product_id=>"A", :quantity=>2}, {:external_product_id=>"B", :quantity=>1}]
and want to transform it into:
array = [{:external_product_id=>"A", :quantity=>3}, {:external_product_id=>"B", :quantity=>1}]
i.e., merging products with the same id ("A") together. Is there any easier way to do this than using map, select, etc?
anything like this?
array.group_by { |item| item[:external_product_id] }
.map do |external_product_id, items|
{
external_product_id: external_product_id,
quantity: items.sum { |item| item[:quantity] }
}
end
=> [{:external_product_id=>"A", :quantity=>3}, {:external_product_id=>"B", :quantity=>1}]

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

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}]

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.

Parsing an array of objects by attributes in rails 3.2

Is it possible to parse an array of objects to select them by attribute? I have a situation where I need to display all objects of a model grouped by an attribute on the index page. What I had been doing in my controller is this...
#xx_controller.rb
#group1 = City.where(:population => 'big')
#group2 = City.where(:population => 'medium')
#group3 = City.where(:population => 'small')
But I'd prefer to do something like this in the controller...
#cities = City.all
And in my view something along the lines of a query, rather than prepackaged instance variables -
#cities.where....
Any thoughts?
If you don't mind loading everything at once from the database, you can do:
#cities = City.all.group_by(&:population)
Which returns a hash whose keys are the possible values for the population attribute.
Then, on your view, you can access the cities on each 'group' by doing #cities['small'], #cities['medium'] and so on.
Do you mean something like this?
#cities = City.all
small_cities = #cities.select { |city| city.population == 'small' }
medium_cities = #cities.select { |city| city.population == 'medium' }
big_cities = #cities.select { | city| city.population == 'big' }

Resources