Simple calculation not working before save, Rails - ruby-on-rails

I've got a base_price field, a shipping_price field, and a total_price field.
It's a simple base_price plus shipping_price equals total_price. For some reason, I cannot get this simple calculation to work when saving my model.
Here's my code:
item.rb
before_save :total_price_calculator
private
def total_price_calculator
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
end
It's failing to make the calculation and save it in the database and I'm not really getting an error as to why.

Try to the following
self.total_price it automatically saves integer when assigned to this any value integer, that's why you don't need to call to_i
After modified
before_save :total_price_calculator
private
def total_price_calculator
self.total_price = base_price.to_i + shipping_price.to_i
end
You don't need to use self on base_price and shipping_price because that is are already have values which given from form.
Or you can use directly looks like below
before_save {self.total_price = self.base_price.to_i + self.shipping_price.to_i}
Hope it helps

You are applying a method on an assignment here and I suspect that this might be the issue, try it like this:
def total_price_calculator
self.total_price = self.base_price + self.shipping_price
end
total_price is going to be an int anyway

Try using another methods like before_update/create/validate, inserting breakpoint too see what happens there, returning false to cancel all following callbacks.
Perhaps this is relatable:
Rails: How to use before_save to change a field value based on another field?

try
def total_price_calculator
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
true
end
sometimes if it comes out as nil, that returns false for your validation which will rollback the record and not save.
Or to further debug try...
def total_price_calculator
puts self.base_price.to_i
puts self.shipping_price.to_i
puts self.base_price.to_i + self.shipping_price.to_i
puts self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
true
end
if the last puts returns false or nil, your save will fail validation and not save.

Related

Increment field within validator

I have a custom validator that checks if the user has entered the correct SMS code. When the user enters the wrong code I need to log the failed attempt and limit their retries to 3 per code.
I have created the following validator that works however the field is not being incremented.
def token_match
if token != User.find(user_id).verification_token
User.find(user_id).increment!(:verification_fails)
errors.add(:sms_code, "does not match")
end
end
The problem is as soon as I add the error the previous statement is rolled back. If I comment out the errors.add line then the increment works however there is no higher level validation performed.
Change your custom validator to be:
def token_match
if token != User.find(user_id).verification_token
errors.add(:sms_code, "does not match")
end
end
and add in your model after_validation callback to be like this:
after_validation: increase_fails_count
def increase_fails_count
unless self.errors[:sms_code].empty?
user = User.find_by(:id => user_id)
user.increment!(:verification_fails)
user.save
end
end
You can use #update_columns in your validator. It writes directly to db.
u = User.find(user_id)
u.update_columns(verification_fails: u.verification_fails + 1)
This worked for me. But if for some reason it doesn't work for you, maybe you can try running it in a new thread,which creates a new db connection:
Thread.new do
num = User.find(user_id).verification_fails
ActiveRecord::Base.connection_pool.with_connection { |con| con.exec_query("UPDATE users SET verification_fails = #{num} WHERE id = #{user_id}") }
end.join

Rails Check If Record Is First

I am iterating through a list of records. I need to check that if a record is first do XYZ and if not do ABC. Unfortunately I cant do this:
user = User.first
or
user = User.find(:id)
user.first?
Solution posted below
1. Make method to grab next and previous records
def next
[Model].where("id > ?", id).first
end
def prev
[Model].where("id < ?", id).last
end
2. Make method to check if record is first
def first?(record)
[Model].first == record
end
3. check if record is first
records.each do |record|
if record.first?(record)
record.update_attributes(attr: record.attr + record.attr)
else
prev_rec = [Model].find(record.id).prev
record.update_attributes(attr: prev_rec.attr + record.attr )
end
end
returns true or false
One improvement i would make sure that [Model].first is persistent so that it doesn't make a call to the database each time the loop is run.

Rails variable returns two different values?

For some weird reason an instance variable I have puts out two different values on two different occasions.
$ puts #project.to_yaml
gives:
id: 3
title: '123'
created_at: 2014-04-07 23:54:18.253262000 Z
updated_at: 2014-04-09 09:20:33.847246000 Z
amount_donated: 50000
and
$ #project.amount_donated
gives:
nil
Explain this one to me because I'm terribly lost.
EDIT
Project model
class Project < ActiveRecord::Base
require 'date'
attr_accessor(:amount_donated)
before_save :convert_params
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
update_column(:amount_donated, value.to_i) shows that you have a column amount_donated, but attr_accessor :amount_donated shows that you have a virtual attribute. So which one is it?
I'd suggest removing attr_accessor :amount_donated
edit:
The attr_accessor :amount_donated does something like this:
class Project < ActiveRecord::Base
require 'date'
before_save :convert_params
def amound_donated
#amount_donated
end
def amound_donated=(value)
#amount_donated = value
end
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
Thus when you accessed #project.amount_donated you were actually accessing the getter method amount_donated not the column (ActiveRecord getter).
Seems that to_yaml saw the column instead of the ActiveRecord's getter.
Try this, might be you are using cached copy of #project
#project.reload.amount_donated

Rails optimistic locking update within a loop appears to work until I check from outside of the loop

I'm using optimistic locking on a Rails model. Inside of a loop, I update and save this model (or, rather, many instances of this model).
From inside the loop, I output the "before" and "after" values, and the field appears to be updated correctly. But afterward, when I find the models by ID, I see that the field is not updated. Can anyone spot my error?
class Usage::LeadDistributionWeight < ActiveRecord::Base
attr_accessible :agent_id, :tag_value_id, :weight, :premium_limit, :countdown, :lock_version, :tag_value
def increment_countdown!
self.countdown = self.countdown + self.weight
save
rescue ActiveRecord::StaleObjectError
attempts_to_crement_countdown ||= 0
attempts_to_crement_countdown += 1
self.increment_countdown! unless attempts_to_crement_countdown > 5
false
end
def self.increment_countdowns parent_id, lead_type_id
if lead_type_id.present?
joins(:agent)
.where("#{reflect_on_association(:agent).table_name}.parent_id = ?", parent_id)
.where(tag_value_id:lead_type_id)
.all(readonly:false).each { |weight|
prev = weight.countdown
if weight.increment_countdown!
puts "#{prev} differs from #{weight.countdown}"
else
puts "no difference!"
end
}
end
end
end

How to format values before saving to database in rails 3

I have a User model with Profit field. Profit field is a DECIMAL (11,0) type. I have a masked input on the form which allows user to input something like $1,000. I want to format that value and remove everything except numbers from it so i will have 1000 saved. Here is what i have so far:
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
end
But it keeps saving 0 in database. Looks like it is converting it to decimal before my formatting function.
Try this:
def profit=(new_profit)
self[:profit] = new_profit.gsub(/[^0-9]/, '')
end
First of all, this:
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
is pretty much the same as this:
def format_values
return if(self.profit.nil?)
p = self.profit
s = p.to_s
s.delete!('^0-9')
end
So there's no reason to expect your format_values method to have any effect whatsoever on self.profit.
You could of course change format_values to assign the processed string to self.profit but that won't help because your cleansing logic is in the wrong place and it will be executed after '$1,000' has been turned into a zero.
When you assign a value to a property, ActiveRecord will apply some type conversions along the way. What happens when you try to convert '$1,000' to a number? You get zero of course. You can watch this happening if you play around in the console:
> a = M.find(id)
> puts a.some_number
11
> a.some_number = 'pancakes'
=> "pancakes"
> puts a.some_number
0
> a.some_number = '$1,000'
=> "1,000"
> puts a.some_number
0
> a.some_number = '1000'
=> "1000"
> puts a.some_number
1000
So, your data cleanup has to take place before the data goes into the model instance because as soon as AR gets its hands on the value, your '$1,000' will become 0 and all is lost. I'd put the logic in the controller, the controller's job is to mediate between the outside world and the models and data formatting and mangling certainly counts as mediation. So you could have something like this in your controller:
def some_controller
fix_numbers_in(:profit)
# assign from params as usual...
end
private
def fix_numbers_in(*which)
which.select { |p| params.has_key?(p) }.each do |p|
params[p] = params[p].gsub(/\D/, '') # Or whatever works for you
end
end
Then everything would be clean before ActiveRecord gets its grubby little hands on your data and makes a mess of things.
You could do similar things by overriding the profit= method in your model but that's really not the model's job.
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit = profit.to_s.gsub(/\D/,'') if profit
end
end
def format_values
self.profit.to_d!
end
I recommend you to write custom setter for this particular instance variable #profit:
class User
attr_accessor :profit
def profit= value
#profit = value.gsub(/\D/,'')
end
end
u = User.new
u.profit = "$1,000"
p u.profit # => "1000"
I would suggest using the rails helper of number with precision. Below is some code.
Generic Example:
number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
Rails code Example:
def profit=(new_profit)
number_with_precision(self[:profit], :precision => 1, :significant => true)
end

Resources