Group and sum 2 columns in rails activerecord - ruby-on-rails

I have this query:
Order.joins(cart: { cart_items: :product })
.group("products.name", "cart_items.amount")
.count
.map { |a, b| { name: a[0], amount: a[1] * b} }
It returns something like this:
[{:name=>"Lorem", :amount=>1},
{:name=>"Lorem", :amount=>2},
{:name=>"Foo", :amount=>1}]
The problem is because if I have 2 cart_items with the same product, it returns 2 values, as you can see in the product "Lorem"
I expect something like this:
[{:name=>"Lorem", :amount=>3},
{:name=>"Foo", :amount=>1}]

This is what you want:
Order.joins(cart: { cart_items: :product })
.group("products.name")
.select("products.name as product_name", "SUM(cart_items.amount) as cart_items_amount")
.map { |order| { name: order.product_name, amount: order.cart_items_amount } }

Related

Plucking out all hash keys that has a specific word

How do you pluck out a hash key that has for example
Hash 1
{sample => {apple => 1, guest_email => my_email#example.com }}
Hash 2
{guest => {email => my_email#example.com}}
Lets say I want 1 method that will pluck out the email from either of those hashes, is there any way I can that like lets say hash.get_key_match("email")
You may use Hash#select to only return keypairs, matching the block:
h = { guest_email: 'some_mail', other_key: '123', apple: 1 }
h.select { |key, _value| key =~ /email/ }
#=> { guest_email: 'some_mail' }
I guess that you need the deep search.
There is no method from the box.
You need use recursion for this goal.
I suspect that your issue can be solved by:
class Hash
def deep_find(key, node = self)
searchable_key = key.to_s
matched_keys = node.keys.select { |e| e.to_s.match?(searchable_key) }
return node[matched_keys.first] if matched_keys.any?
node.values
.select { |e| e.respond_to?(:deep_find) }
.map { |e| e.deep_find(key, e) }
.flatten.first
end
end
h = {a_foo: {d: 4}, b: {foo: 1}}
p (h.deep_find(:foo))
# => {:d=>4}
h = {a: 2, c: {a_foo: :bar}, b: {foo: 1}}
p (h.deep_find(:foo))
# => :bar
you can use this
hash = { first_email: 'first_email', second_email: 'second_email' }
hash.select { |key, _value| key =~ /email/ }.map {|k, v| v}

Rails group and sum array of objects

A production has_many :production_lines,
production_line belongs_to :item,
item has_one :recipe,
recipe has_many :recipe_lines,
recipe_line belongs_to :item,
production_line and recipe line have attribute quantity. I need to group recipe_lines for a production by item, with quantity that equals to production_line.quantity * recipe_line.quantity
def item_quantities
array = production_lines.map do |p|
p.item.recipe.recipe_lines.map do |r|
{
item_id: r.item_id,
item_name: r.item.name,
quantity: r.quantity * p.quantity
}
end
end
array.flatten(1).group_by { |p| p[:item_id] }
.transform_values { |vals| vals.sum { |val| val[:quantity] } }
end
This returns:
item_quantities = {
1: 10,
2: 5
}
where key is item_id and value is quantity. Values are correct.
However I would like to return:
item_quantities = [
{
id: 1,
name: "Tomato",
quantity: 10,
},
{
id: 2,
name: "Carrot",
quantity: 5
}
]
How should I change my solution to achieve that?
First of all, your nested map followed by flatten(1) can be simplified by making the first map into flat_map. If you do this you could remove the flatten(1).
From this point your code is most of the way there, but you could make the following changes to get the desired output:
you can group by multiple attributes, name and id. In another language you might use a tuple for this. Ruby doesn't have tuples, so we can just use a len-2 array:
.group_by { |p| [p[:item_id], p[:item_name]] }
.transform_values { |vals| vals.sum { |val| val[:quantity] } }
At this point you have a hash mapping [id,name] tuple to quantity:
{ [1,"foo"] => 123, [2, "bar"] => 456 }
and you can coerce this to the desired data type using reduce (or each_with_object, if you prefer):
.reduce([]) do |memo, ((id, name), quantity)|
memo << {
id: id,
name: name,
quantity: quantity
}
end
The wierd looking ((id, name), quantity) is a kind of destructuring. See https://jsarbada.wordpress.com/2019/02/05/destructuring-with-ruby/ specifically the sections on "Destructuring Block Arguments" and "Destructuring Hashes".

Using group_by but return an array of hashes

I simply want to group cities by their state and from there have the array of the hash key (i.e. State Name) return an array of hash data pertaining to it's cities. Right now I have something like this:
City.all.group_by { |c| c.state.name }
Which will return:
{
"Illinois": [# < City id: 3, name: "Chicago", state_id: 3 > ],
"Texas": [# < City id: 2, name: "Houston", state_id: 2 > ],
"California": [# < City id: 1, name: "Los Angeles", state_id: 1 > ],
"New York": [# < City id: 4, name: "New York City", state_id: 4 > ]
}
Notice how it returns an array of rails objects. Instead I want to return an array of hashes with certain attributes, like their id and name.
The reason the grouped values are Rails objects (your models) is due to the fact that you also start with these objects. You can use the attributes method to retrieve the attributes of a model instance as a hash.
The following achieves the result you want:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map(&:attributes) }
If you only want specific attributes, use slice instead:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map { |city| city.slice(:id, :name) } }
Note that slice will return an ActiveSupport::HashWithIndifferentAccess instance. Which mostly can be used in the same manner as a normal hash, but returns the same value for both hash[:name] and hash['name']. If you rather use a normal hash append a to_hash call after the slice call.
This should be enough for you
City.all.group_by { |c| c.state.name }.map {|k,v| [k, v.attributes] }.to_h
and to select only specified attributes do
v.attributes.slice(:name, :id)
One of the easiest approach is to convert it into json object
City.all.as_json.group_by { |c| c.state.name }
this will fix the issue

Is it possible to return a hash with named keys form an ActiveRecord group query?

The following query works as expected:
Purchase.all.group( :user_id ).sum( :price )
It returns an array of hashes:
[{ 1 : 234 }, ...
Is there a way to return an array of hashes with keys?
[{ id : 1, price : 234 }, ...
You can return an ActiveRecord::Relation with a single query.
Purchase.select("user_id as id, sum(price) as price").group("user_id")
I believe this will do:
Purchase.all.group(:user_id).sum(:price).map do |k, v|
{
id: k.keys.pop,
price: k.values.pop
}
end
Although a bit hacky, works.

Build hash index from collection in ruby

here is what I"m trying to get from collection of records:
{
"transaction": {
"amount":"45.55",
"currency":"CAD",
},
"detail": {
"0": {
"sku":"Product_id",
},
"1": {
"sku":"Product_id",
},
"2": {
"sku":"Product_id",
}
}
}
I need to loop through order items and build hash with index.
# Order model
order has_many :items, class_name: "LineItem"
Order.column_names
=> ["id", "amount", "currency"]
# LineItem model
LineItem.column_names
=> ["id", "sku", "order_id"]
Here is what I have so far but looks like I can't do this:
{
transaction: {
amount: order.subtotal,
currency: order.currency,
},
detail: {
order.items.each_with_index do |item, index|
index: {
sku: item.sku
}
end
}
}.to_json
So, let's try to build nested hash separately but no luck either.
ha = items.each_with_index do |item, index|
index => Hash.new(sku: item.sku)
end
Here is what I ended up doing
body = {
transaction: {
amount:"45.55",
currency:"CAD",
}
}
items_hash = {}
items.each_with_index do |item, index|
h = { "#{index}" => { sku: item.sku } }
items_hash.merge!(h)
end
details = { "transaction_detail" => items_hash }
hash = hash.merge!(details)
hash.to_json

Resources