Return the value if the value exists - ruby-on-rails

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

Related

Call a variable from another model in Ruby on Rails

I'd like to ask a little question. It's rather trivial I guess but I might just look for the wrong solutions to my issue so I cant get it working.
I have a model called request.rb which has a method called self.dates
def self.dates
from_date = Request.last.date.to_date
to_date = Request.last.todate.to_date
weekdays = (from_date..to_date).select { |d| (1..5).include?(d.wday)}.count
weekend_days = (from_date..to_date).select { |d| [0, 6].include?(d.wday)}.count
end
I have another model called hotels.rb where I'd like to call the variables weekdays and weekend_days for the price calculation.
def self.best_deal_nm
weekday_nm_tot = (Request.dates.weekdays * pricea.wdpricenm) + (Request.dates.weekdays * pricea.wepricenm)
weekend_nm_tot = (Request.dates.weekend_days * priceb.wdpricenm) + (Request.dates.weekend_days * priceb.wepricenm)
[weekday_nm_tot, weekend_nm_tot].min
end
Unfortunately the code above doesnt work. My question is therefore how can I possibly call these two variables in my hotel model.
Thanks a lot for help
Just return all info in last line into your self.dates method like
def self.dates
from_date = Request.last.date.to_date
to_date = Request.last.todate.to_date
weekdays = (from_date..to_date).select { |d| (1..5).include?(d.wday)}.count
weekend_days = (from_date..to_date).select { |d| [0, 6].include?(d.wday)}.count
{'from_date' => from_date, 'to_date' => to_date, 'weekdays' => weekdays, 'weekend_days' => weekend_days}
end
After call Request.dates from hotels.rb you could access to all variables added to hash.
weekday_nm_tot = (Request.dates['weekdays'] * pricea.wdpricenm) + (Request.dates['weekdays'] * pricea.wepricenm)

contextual binding in rails

Normally i do like this
for loops
# app/view/products/index.html.haml
- #products.each do |product|
= product.name
= product.foo
= product.bar
normal scoping
# app/view/products/show.html.haml
= #product.name
= #product.price
= #product.xyz
See in above situation, i am repeating myself. I am using same product word every time. I want it something like which could attach/bind the method as per my context.
I rather prefer to do something like this
For loops i like do something like
- #products.each(context_binding: true) do
= name
= foo
= bar
for scoping
- context_binding #product do
= name
= price
= xyz
I guess that is possible and can be done with method missing i dont know how to do it. Can you give some hints so i can archive such type of things.
Just to give you an idea:
class With
def initialize(obj)
#obj = obj
#str = ''
end
def method_missing(name, *args, &block)
#str += #obj.send(name, *args, &block).to_s
end
end
def with(obj, &block)
With.new(obj).instance_eval(&block).to_s
end
Product = Struct.new(:name, :price)
product = Product.new('apple', '2')
output = with(product) do
name
price
end
puts output

Ruby method return more than one variable

I need to return to my Rails view more than one variable from method... But how could i do this?
For example now i have
def my1
#price = 1
#price
end
but how could i also return other valuem somethin like:
def my1
#qnt = 2
#price = 1
#price, #qnt
end
?
Also my idea is to split them to string like
#price + "/-/" + #qnt
and then just split them via /-/ in view.... But this is a bad practice...
So how could i get from one method two or more variables?
Return an array:
def my1
qnt = 2
price = 1
[price, qnt]
end
then you can do this:
p, q = my1() # parentheses to emphasize a method call
# do something with p and q
Option 2
Or you can return a custom object, like this:
require 'ostruct'
def my1
qnt = 2
price = 1
OpenStruct.new(price: price, quantity: qnt)
end
res = my1() # parentheses to emphasize a method call
res.quantity # => 2
res.price # => 1
Use another object that will hold the variables and return it. You can then access the variables from that object;
Array
The easiest way is to return an Array:
def my1
#qnt = 2
#price = 1
[ #price, #qnt ]
end
price, quantity = my1
Hash
But you could also return a Hash:
def my1
#qnt = 2
#price = 1
{ :quantity => #qnt, :price = #price
end
return_value = my1
price = return_value[:price]
quantity = return_value[:quantity]
# or
price, quantity = [ return_value[:price], return_value[:quantity] ]
Custom Class
Or a custom Class:
class ValueHolder
attr_reader :quantity, :price
def initialize(quantity, price)
#quantity = quantity
#price = price
end
end
def my1
#qnt = 2
#price = 1
ValueHolder.new(#qnt, #price)
end
value_holder = my1
price = value_holder.price
quantity = value_holder.quantity
# or
price, quantity = [ value_holder.price, value_holder.quantity ]
OpenStruct
You could use OpenStruct as Sergio mentioned.

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

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

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

Resources