I am trying to write a method that will find each price and add it to the current price.
Quote.rb:
class Quote < ActiveRecord::Base
has_many :items
def calc_price
sum = 0
items.each do |item|
item.price
end
sum = (item1 + item2 etc)
end
end
items.inject(0){|sum, item| sum += item.price}
Here's a nice feature about the more current Rubies:
values = [1,2,3,4,5]
values.inject(:+) # => 15
Now, that said, you're working with a database, so have it sum the records. From the documentation:
Calculates the sum of values on a given column. The value is returned with the same data type of the column, 0 if there’s no row. See calculate for examples with options.
Person.sum('age') # => 4562
In this case, you can offload the calculation to the database.
def total_price
items.sum('price')
end
Reference: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-sum
You need to sum the price within the .each loop
class Quote < ActiveRecord::Base
has_many :items
def calc_price
sum = 0
items.each do |item|
sum += item.price
end
end
end
You can further compact that loop to a single line using
items.each {|item| sum += item.price}
How about
items.map(&:price).inject(0, :+)
The map part maps the items array to an array of prices and the inject part starts with 0 and adds each element of the price array.
Mapping StackOverflow question
inject docs
inject examples
Related
I have a Campaign model that has_many items.
Each item has :quantity and :price
I want to calculate the total price for the Campaign.
I tried with this but won't work.
def campaign_subtotal(campaign)
return campaign.items.sum(item.price * item.quantity)
end
How is this approached?
Thanks a lot
The naive implementation is:
class Campaign < ApplicationRecord
has_many :items
def subtotal
items.sum { |item| item.price * item.quantity }
end
end
You were somewhat close but you need to pass a block to calculate the sum per item. It also makes more sense to make this an instance method rather then a helper.
However if you want to use the subtotal in database queries or you want to display a bunch of campaigns efficiently you would want to calculate it in SQL with a subquery, window query or lateral join. You should also consider recording the price per line (the subtotal) instead of recalculating it all the time.
I have a model which contains items for sale. The model is mirroring an API, so I can't change it's structure.
In this model I have the fields price and sold_quantity.
It doesn't seem right to iterate through the query with a loop.
current_user.items.each do |item|
total += item.price * item.sold_quantity
end
Is there a way to get the same total using only ActiveRecord? Like .sum(:price) but multiplying by sold_quantity?
You probably want this:
current_user.items
.select('items.*, items.price * items.sold AS total')
.all
Or, if you want the totals and nothing else:
current_user.items.pluck('items.price * items.sold')
You can do this:
current_user.items.sum { |item| item.price * item.sold_quantity }
I hope this help you.
For more information sum in AR
One way you could approach this would be to move this logic into the Item class:
class Item < ActiveRecord::Base
...
def total
self.price * self.sold_quantity
end
def self.sold_value
all.map{|a| a.total}.sum
end
end
Here each instance of item is calculating it's own total, and a class method allows you get the sold value on any collection of items.
#user.items.sold_value
Picture a normal invoice: On it, you have several items. Each item has a quantity and a price per unit, among other things (unit and description).
The total amount for each item is calculated like this: quantity * price per unit.
This is done for each item. Then, the overall invoice's net amount is the sum of all the totals. Add VAT, and you have the invoice's gross amount.
This is what I am trying to do with my Rails app. An invoice has many items and accepts nested attributes for them. Generally, this all works fine.
Following the logic, all I need to enter in manually is the price per unit and the quantity for each item as well as the invoice's VAT. The totals and the resulting net and gross amount should be calculated automatically. I want to achieve this using the before_save filter.
Here is my invoice model:
before_save :calculate_net_amount, :calculate_gross_amount
def calculate_net_amount
self.items do |item|
self.net_amount += item.total
end
end
def calculate_gross_amount
self.gross_amount = self.net_amount * (1 + self.vat_rate)
end
This is the item model:
before_save :calculate_total
def calculate_total
self.total = self.quantity * self.price_per_unit
end
And here is my spec that is failing:
it "calculates the net amount from all item totals" do
invoice = FactoryGirl.build(:invoice)
item = invoice.items.build(quantity: 2, unit: "Unit", description: "Desc", price_per_unit: 2)
invoice.save
invoice.net_amount.should == 4
end
It uses this invoice factory:
FactoryGirl.define do
factory :invoice do
association :client
currency "EUR"
vat_rate 0.19
net_amount 1
payment_on "2013-01-01"
status "paid"
end
end
The test basically does the following: An invoice with 2 things that both cost USD 2 should have a net amount of USD 4. Instead, the test returns 1, which seems to come from the factory and apparently isn't overwritten. If I remove it from the fixture, it says that it cannot calculate the gross amount anymore, since it can't use * on any nil object.
I am assuming I am doing something wrong with the filters and the order in which they are called - the total amounts are calculated correctly, so it has to be something about the calculate_net_amount method that's going wrong and as a result it can't calculate the gross amount anymore.
Can you see what I am doing wrong?
self.items do |item|
should be
self.items.each do |item|
Since items is an accessor, which is a method, it can technically take a block, but that block isn't getting called, so no summing is happening. It's an easy typo to make.
As an aside, it's better is to sum using inject:
self.net_amount = self.items.inject(0){|sum, item| sum + item.total}
I'm using Ruby on Rails. I have a couple of models which fit the normal order/order lines arrangement, i.e.
class Order
has_many :order_lines
end
class OrderLines
belongs_to :order
belongs_to :product
end
class Product
has_many :order_lines
end
(greatly simplified from my real model!)
It's fairly straightforward to work out the most popular individual products via order line, but what magical ruby-fu could I use to calculate the most popular combination(s) of products ordered.
Cheers,
Graeme
My suggestion is to create an array a of Product.id numbers for each order and then do the equivalent of
h = Hash.new(0)
# for each a
h[a.sort.hash] += 1
You will naturally need to consider the scale of your operation and how much you are willing to approximate the results.
External Solution
Create a "Combination" model and index the table by the hash, then each order could increment a counter field. Another field would record exactly which combination that hash value referred to.
In-memory Solution
Look at the last 100 orders and recompute the order popularity in memory when you need it. Hash#sort will give you a sorted list of popularity hashes. You could either make a composite object that remembered what order combination was being counted, or just scan the original data looking for the hash value.
Thanks for the tip digitalross. I followed the external solution idea and did the following. It varies slightly from the suggestion as it keeps a record of individual order_combos, rather than storing a counter so it's possible to query by date as well e.g. most popular top 10 orders in the last week.
I created a method in my order which converts the list of order items to a comma separated string.
def to_s
order_lines.sort.map { |ol| ol.id }.join(",")
end
I then added a filter so the combo is created every time an order is placed.
after_save :create_order_combo
def create_order_combo
oc = OrderCombo.create(:user => user, :combo => self.to_s)
end
And finally my OrderCombo class looks something like below. I've also included a cached version of the method.
class OrderCombo
belongs_to :user
scope :by_user, lambda{ |user| where(:user_id => user.id) }
def self.top_n_orders_by_user(user,count=10)
OrderCombo.by_user(user).count(:group => :combo).sort { |a,b| a[1] <=> b[1] }.reverse[0..count-1]
end
def self.cached_top_orders_by_user(user,count=10)
Rails.cache.fetch("order_combo_#{user.id.to_s}_#{count.to_s}", :expiry => 10.minutes) { OrderCombo.top_n_orders_by_user(user, count) }
end
end
It's not perfect as it doesn't take into account increased popularity when someone orders more of one item in an order.
Let's say I have two models Post and Category:
class Post < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :posts
end
Is there a method that will allow me to do something like
posts = Post.find(:all)
p = Array.new
p[1] = posts.with_category_id(1)
p[2] = posts.with_category_id(2)
p[3] = posts.with_category_id(3)
...
or
p = posts.split_by_category_ids(1,2,3)
=> [posts_with_category_id_1,
posts_with_category_id_2,
posts_with_category_id_3]
In other words, 'split' the collection of all posts into arrays by selected category ids
Try the group_by function on Array class:
posts.group_by(&:category_id)
Refer to the API documentation for more details.
Caveat:
Grouping should not performed in the Ruby code when the potential dataset can be big. I use the group_by function when the max possible dataset size is < 1000. In your case, you might have 1000s of Posts. Processing such an array will put strain on your resources. Rely on the database to perform the grouping/sorting/aggregation etc.
Here is one way to do it(similar solution is suggested by nas)
# returns the categories with at least one post
# the posts associated with the category are pre-fetched
Category.all(:include => :posts,
:conditions => "posts.id IS NOT NULL").each do |cat|
cat.posts
end
Something like this might work (instance method of Post, untested):
def split_by_categories(*ids)
self.inject([]) do |arr, p|
arr[p.category_id] ||= []
arr[p.category_id] << p if ids.include?(p.category_id)
arr
end.compact
end
Instead of getting all posts and then doing some operation on them to categorize them which is a bit performance intensive exercise I would rather prefer to use eager loading like so
categories = Category.all(:include => :posts)
This will generate one sql query to fetch all your posts and category objects. Then you can easily iterate over them:
p = Array.new
categories.each do |category|
p[1] = category.posts
# do anything with p[1] array of posts for the category
end
Sure but given your model relationships you I think you need to look at it the other way around.
p = []
1.upto(some_limit) do |n|
posts = Category.posts.find_by_id(n)
p.push posts if posts
end