Ruby on Rails: Avoiding infinite loop when calling .save during an update - ruby-on-rails

I have an order model that has_many :items. Each item has item.price for the cost of said item. I want to add up all of the item prices in the order for a order.total_price. Right now I'm doing that with
after_save :update_total_price, :if => "self.saved.nil? "
def update_total_price
self.total_price = Item.find(item_ids).inject(0){|sum,item| sum + (item.price * item.amount) } #amount is how many items there are
self.saved = 1
self.save if self.saved
end
This works just fine the first time that I put in the info, but if I try to edit the order, the total_price doesn't get updated because update_total_price doesn't get called because self.saved is not nil.
What can I do to make it so that updating the model will update it, but won't keep on doing an infinite loop of calling .save?

Why not have the update_total_price NOT save the data again.
just set the value in before_update:
before_save :update_total_price
def update_total_price
self.total_price = items.find(:all).inject(0){|sum,item| sum + (item.price * item.amount) }
end

after_save :update_total_price
def update_total_price
self.total_price = find_total_price
self.save_without_callbacks
end
def find_total_price
Item.find(item_ids).inject(0){|sum,item| sum + (item.price * item.amount)
end

Related

Rails association scope by method with aggregate

I'm trying to retrieve association records that are dependent on their association records' attributes. Below are the (abridged) models.
class Holding
belongs_to :user
has_many :transactions
def amount
transactions.reduce(0) { |m, t| t.buy? ? m + t.amount : m - t.amount }
end
class << self
def without_empty
includes(:transactions).select { |h| h.amount.positive? }
end
end
class Transaction
belongs_to :holding
attributes :action, :amount
def buy?
action == ACTION_BUY
end
end
The problem is my without_empty method returns an array, which prevents me from using my pagination.
Is there a way to rewrite Holding#amount and Holding#without_empty to function more efficiently with ActiveRecord/SQL?
Here's what I ended up using:
def amount
transactions.sum("CASE WHEN action = '#{Transaction::ACTION_BUY}' THEN amount ELSE (amount * -1) END")END")
end
def without_empty
joins(:transactions).group(:id).having("SUM(CASE WHEN transactions.action = '#{Transaction::ACTION_BUY}' THEN transactions.amount ELSE (transactions.amount * -1) END) > 0")
end

method containing calculation - Rails 4

i just want to calculate the total sum of all active events that users have paid to attend. If you could advise me i could be grateful as i am very unsure. Many thanks
event.rb
has_many :payments
payment
belongs_to :event
in the event.rb i tried the below method but no success
def self.active_events
active_events = live_events.open_events
active_events.all.each do |event|
event.price * event.payments.count
end
end
You can do this simply in following way,
total = 0
Event.live_events.open_events.find_each { |e| total += e.price * e.payments.count }
In Event.rb place it in a method with meaningful name.
This will work for you.
def self.total_price_for_active_events
total = 0
Event.live_events.open_events.find_each { |e| total += e.price * e.payments.count }
total
end
Most optimized way
def self.total_price_for_active_events
Event.live_events.open_events.joins(:payments).sum("events.price")
end
You're off too a good start! Unfortunately, what you have there is only the beginning; you're generating an array that contains the total sum for each event. All that remains is to add them together:
def self.active_events
active_events = live_events.open_events
costs = active_events.all.each do |event|
event.price * event.payments.count
end
costs.reduce(0) do |sum,x|
sum + x
end
end
You could also get real fancy and simply use:
costs.reduce(0, :+)

How can I iterate through a model then iterate again in my view?

I want to pull data for each of my users. I grab their person_id from my user table, then use each person's ID to figure out how many days each person has available, and show that in my view.
I'm not sure if I am doing this correctly because I am iterating in my controller then again in my view.
def how_many_days_users_have
#my_group = User.all.pluck(:person_id)
#my_group.each do |v|
#indirect_id_v = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:a_code).first
#v_range = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:ac).first
#v_range_taken = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:taken).first
#total_v_hours = #v_range.to_d - #v_range_taken.to_d
#total_v_days = #total_v_hours / 8
end
Then in my view I use this to show me this data:
%tr.trace-table
-#indirect_id_v.each do |idd|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= idd
-#total_v_days.each do |days|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= days
Okay, first things first, move some of that junk to your model, like so:
class Empaccrl < ActiveRecord::Base
def self.all_people
where(person_id: User.all.pluck(:person_id))
end
def self.active_people
all_people.where(is_active: 'Y')
end
def self.active_vacation_data
active_people.select(:person_id, :ac, :taken)
end
def total_v_hours
ac.to_d - taken.to_d
end
def total_v_days
total_v_hours / 8
end
end
Then you can use:
peoples_vacation_information = Empaccrl.active_vacation_data.all
peoples_vacation_information.map do |person|
p "person #{person.person_id} has #{person.total_v_days} vacation days"
end
Honestly, you don't even need all that, but I'm not sure why you are doing what you are doing, so I figured better be safe and add stuff. Whatever you don't need, just ignore.

Return the value if the value exists

In Ruby on Rails, is there a better way to write this:
qty = 2
avg_price = room["price"].to_f if room["price"].present?
total_price = (avg_price * qty) if avg_price.present?
Especially the 2nd and 3rd line. I find myself using the if-else condition too often. Thanks.
You can try something like this:
qty = 2
total_price = room.fetch("price").to_f * qty
But there is a problem with this code, if there is no price field in the hash, it will raise an exception. Does it suffice your needs ?
It is hard to make this shorter, I would just do:
qty, avg_price, total_price = 2, nil, nil
if room["price"]
avg_price = Float(room["price"])
total_price = avg_price * qty
end
What about defining a helper method so that you can directly extract a float from a hash:
class Hash
def get_f key; fetch(key).to_f if key?(key) end # Or `if self[key].present?`
end
and then do:
qty = 2
avg_price = room.get_f("price")
total_price = avg_price * qty if avg_price
Perhaps a more object oriented approach? This approach makes it easier to test the code and might be reuseable.
class PriceCalculator
def init(quantity, price)
#quantity = quantity
#price = price.presence && price.to_f
end
def total
#price * #quantity if #price
end
end
total_price = PriceCalculator.new(2, room["price"]).total

How to simplify my model code?

I am new to Rails and I wonder if there's any way to simplify this code from my model:
class Item < ActiveRecord::Base
def subtotal
if price and quantity
price * quantity
end
end
def vat_rate
if price and quantity
0.19
end
end
def total_vat
if price and quantity
subtotal * vat_rate
end
end
end
As far as I know *before_filter* does not work within models?
I'd do:
class Item < ActiveRecord::Base
VAT_RATE = 0.19
def subtotal
(price || 0) * (quantity || 0)
end
def total_vat
subtotal * VAT_RATE
end
end
Personally I would override the getter methods for price and quantity so that they return zero when not set, which allows your other methods to return valid results when no values are set rather than checking that they are and returning nil.
Additionally, creating a method to provide the VAT rate seems a little overkill for what should be a constant. If it isn't a constant then it should probably be stored in the DB so that it can be modified.
Here's a modification of your model based on my thoughts:
class Item < ActiveRecord::Base
VAT_RATE = 0.19
def price
self.price || 0
end
def quantity
self.quantity || 0
end
def subtotal
price * quantity
end
def total_vat
subtotal * VAT_RATE
end
end

Resources