Reputation: 888
Data structure is as follows:
class Job
has_many job_sections
has_many job_products, through job_sections
end
class JobSection
belongs_to job
has_many job_products
end
class JobProduct
belongs_to product
belongs_to job_section
end
When I call job.job_products i could end up with something like this:
#<JobProduct:0x007ff4128b0ca0
id: 18133,
product_id: 250,
quantity: 3,
frozen_cache: {},
discount: 0.0,
#<JobProduct:0x007ff4128b00c0
id: 18134,
product_id: 250,
quantity: 1,
frozen_cache: {},
discount: 0.0]
As you can see the product_id is identical in both instances. How do I merge the contents of the arrays by product id so I retrieve and act on them as aggregated values?
In a way, I need to be able to act on job products by their product_id
rather than their id
.
Effectively the result being something like this...
[#<SomeFancySeerviceObjectMaybe?
product_id: 250,
quantity: 4,
frozen_cache: {},
discount: 0.0]
Do I opt for a little Plain Old Ruby Object to handle them all, or do I have to rethink the architecture of this, or is there (hopefully!) a bit of Rails secret sauce that than can help me out?
*FYI Job Section is a recent addition to the architecture, and I don't think its has been particularly well thought through. However, I can't spend too much time reversing what's already in place. This set up isn't ideal, I'm probably the sixth dev in as many years to start picking this apart.
Your suggestions are most welcome. Thank you
Upvotes: 0
Views: 126
Reputation: 24541
In SQL this would be something like this:
SELECT SUM(quantity)
FROM job_products
WHERE product_id = 250
GROUP BY product_id
You can do that in ActiveRecord too. If you just want an integer, you can use pluck
:
total_quantity = job.job_products.
group(:product_id).
pluck("SUM(job_products.quantity)").
first
You can also pluck
several columns if you want (in Rails 4+), which is why it returns an array. So if you want average discount at the same time, it's easy.
If you would prefer a JobProduct
instance, you can get that too, but in your case a lot of the attributes will be nil
because of the grouping. But you can say:
summary = job.job_products.
group(:product_id).
select("SUM(job_products.quantity) AS total_quantity").
first
And you'll get a read-only JobProduct
with an extra attribute named total_quantity
. So you can do summary.total_quantity
. But because of the grouping, summary
will have a nil
id
, discount
, etc. Basically it will only have attributes matching the things you select
. This is a little weird, but sometimes it lets you write methods that work both on "real" JobProduct
s and for these summary versions.
Upvotes: 1