Sanitize dollar value before saving - ruby-on-rails

I have an input field on a form that has the users enter in a dollar amount. I'm using autoNumeric to mask that field so that as a user inputs 1000, it displays 1,000.00 in the text field.
The column in the database (requested_amount) is a decimal with precision => 8 and scale => 2.
I've made a callback based on other SO answers that attempts to strip the comma out of the input before validating and saving to the database.
def strip_commas_from_dollar_fields
self.requested_amount = self.requested_amount.to_s.gsub(',', '')
end
# Callback
before_validation :strip_commas_from_dollar_fields
The params hash that is passed through then I submit the form:
"requested_amount"=>"80,000.00"
However, when I view the number in console, it shows:
=> 80.0
I need the number to be 80000.00 in the database.

Because:
def strip_commas_from_dollar_fields
p self.requested_amount // <- here is 80.0 already
self.requested_amount = self.requested_amount.to_s.gsub(',', '')
end
check it. So, instead of this way try to create wrapper around requested_amount field:
def requested_amount_wrapper=(val)
self.requested_amount = val.to_s.gsub(',', '')
end
def requested_amount_wrapper
self.requested_amount
end
and use this wrapper field in your hash parameters:
"requested_amount_wrapper"=>"80,000.00"

Related

Rails convert integer value before save

I have an database with an enginze size for the car
Therefore user can write something like 2.5 (like in liters) or 2500 (cc)
Later on I have an sorting and it should using 999-9999 values to compare
I came up the function below, but I would like it be more flexible. Moreover, 2.5 causing the result of 2000 now because looks like Rails converting value before triggering before_save
How do I make convert right and detect if there is an point or comma in input?
before_save :covert_engine
private
def covert_engine
if self.car_engine_size.present?
if Math.log10(self.car_engine_size).to_i + 1 < 4
self.car_engine_size = self.car_engine_size * 1000
end
end
end
P.S. self.car_engine_size is an integer in database
If you want the user to be able to use different units of input I would make it explicit to the user by letting them select the unit.
Start by creating a virtual attribute
class Car
attr_accessor :engine_size_unit
end
Add the attribute to the form and whitelist it in the controller
<%= f.number_field :engine_size %>
<%= f.select :engine_size_unit, ['cc', 'l']) %>
Then convert the value in the model using the value before the typecast
class Car
before_save :covert_engine,
if: -> { car_engine_size.present? && engine_size_unit == 'l' }
def covert_engine
converted = car_engine_size_before_type_cast * 1000
self[:car_engine_size] = converted.to_i
end
end

Rails input comma separated value from text field and store in different columns

I have a model named RaceTimings which records the timing of each student in a race.
I want to take the input of the form in format minute:seconds:microseconds from a single text field and store the values in 3 different columns of the same model.
I have already been through other links but could not find any solution.
Can anyone suggest how this can be done?
Just use the def something=(val) function which you call all the time when you are using a = to set some variable.
class RaceTiming
# unless you dont have those fields in your database
attr_accessor :minutes, :seconds, :microseconds
def time
# returns "12:14:24" as a string
[minutes, seconds, microseconds].join(":")
end
def time=(val)
#split the val into an array ["11", "04", "123"] if it was 11:04:123
val = val.split(":")
self.minutes = val[0]
self.seconds = val[1]
self.microseconds = val[2]
end
end
you call it by
record = RaceTiming.new
record.time = "12:44:953"
# do what you need to do
Assuming that your text field is giving the minutes, seconds and microseconds in "minute:seconds:microseconds" format, you can do something like this:
a = "minute:seconds:microseconds".split(':')
minutes, seconds, microseconds = a[0], a[1], a[2]
RaceTiming.update_attributes(:minutes => minutes, :seconds => seconds, :microseconds => microseconds)

Rails serialized hash - is it possible to specify a data type?

I'm serializing form input into a hash. Is it possible to force some of the inputs into integers?
For example, if I do #user.update_attributes(params[:user]), and one of the params is supposed to be a number, that number will get stored a string like "123".
Is it possible to make sure it is stored as an integer?
To be clear:
My User model looks like
class User < ActiveRecord::Base
...
store :preferences, accessors: [:max_capacity]
...
end
and my form might have an input like
<input name="user[preferences][max_capacity]" type="text"/>
This will cause max_capacity to be stored as a String. Can I force it to be an integer when I store it?
You can modify the user object that is stored in the params hash directly before you save it. If you convert max_capacity from a string into an integer you will want to account for the case when a user submits a bad value for max_capacity. This can be handled with a begin/rescue block.
In your Controller action you could do the following:
def update
max_capacity = params[:user][preferences][max_capacity]
begin
# Attempt to convert into an integer
Integer(max_capacity)
rescue
# If max_capacity was incorrectly submitted: 'three', '04', '3fs'.
redirect_to edit_user_path, :alert => "Unable to update user. Invalid max capacity."
else
If it can be converted, do so, and update the user object in the params hash.
params[:user][preferences][max_capacity] = Integer(max_capacity)
end
#user.update_attributes(params[:user])
.
.
.
end

Rails: How can I validate my records so that no two records are the same?

I've been trying to get the validates_uniqueness_of to work for my database. I pull in records from a CSV file, and I want to make sure that I record them all but when I check the next time I don't want to save them all again if they're just duplicates.
Example Object
PlayerStats {session_date, uniform_number, last_name, first_name, throws, throws_caught, throws_dropped, intercepted_throws, defended_throws }
Example Records
2013-01-01, 11A, Jacobsen, Mike, 11, 4, 7, 0 0
2013-01-01, 11A, Jacobsen, Mike, 0, 0, 0, 2, 1
I want to keep both of these records, but when I try to validate like so...
validates_uniqueness_of :uniform_number, :scope => [:session_date, :last_name]
this will only keep for instance the first record and consider the second a duplicate.
I would like to have it where when the second record goes through a save attempt it will save the second record also.
The problem is that you are only validating uniqueness of three fields and not all fields. You should add all your fields to :scope. But that will not be very good performance wise if you have a table with too many rows. I would suggest you generate a token out of all the fields you want to validate uniqueness of and add uniqueness validation on token. Going this route you have to add one more column to your table to store the calculated token. Don't forget to add unique index on the column to get the optimum performace. After that following should do the trick:
before_validate :generate_unique_token
# assuming you named your slug column `unique_token`
validates :unique_token, uniqueness: true
private
# add all fields you want to validate uniqueness on
UNIQUE_FIELDS = [:uniform_number, :session_date, :more_fields]
def generate_unique_token
return if self.unique_token.present?
token_string = ''
# additional comma is to ensure that [1, 10] and [11, 0] don't get treated as same input
UNIQUE_FIELDS.each {|field| token_string << self.send(field).to_s << ','}
self.unique_token = token_string
end
This string can get big, but you will not get any false collisions. If you want to control the size of the generated token you can do it like following:
def generate_unique_token
token_string = ''
UNIQUE_FIELDS.each {|field| token_string << self.send(field).to_s << ','}
self.unique_token = compress_token(token_string)
end
def compress_token(token_string)
# you can further compress the token by encoding it in base 62/64
::Digest::SHA256.hexdigest(token_string)
end
But beware, the later solution can have rare false collisions.

Rails / ActiveRecord: field normalization

I'm trying to remove the commas from a field in a model. I want the user to type a number, i.e. 10,000 and that number should be stored in the database as 10000. I was hoping that I could do some model-side normalization to remove the comma. I don't want to depend on the view or controller to properly format my data.
I tried:
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
no worky.
http://github.com/mdeering/attribute_normalizer looks like a promising solution to this common problem. Here are a few examples from the home page:
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
# Using one of our predefined normalizers.
normalize_attribute :price, :with => :currency
# You can also define your normalization block inline.
normalize_attribute :title do |value|
value.is_a?(String) ? value.titleize.strip : value
end
So in your case you might do something like this:
normalize_attribute :title do |value|
value.to_s.gsub(',', '')
end
I think you're doing it right. This test passes:
test "should remove commas from thenumber" do
f = Foo.new(:thenumber => "10,000")
f.save
f = Foo.find(f.id)
assert f.thenumber == "10000"
end
And I used your code.
class Foo < ActiveRecord::Base
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
end
Now, my schema is set up for thenumber to be a string though, not an integer.
Started
.
Finished in 0.049666 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
If you wanted to store this in the db as an integer, then you definitely need to override the setter:
def thenumber=(value)
self['thenumber'] = value.to_s.gsub(',','').to_i
end
If you do it your way, with an integer column, it gets truncated by AR....
>> f.thenumber = "10,000"
=> "10,000"
>> f.thenumber
=> 10
That's a little-known thing with Ruby and integers... it auto-casts by truncating anything that's no longer an integer.
irb(main):004:0> i = "155-brian-hogan".to_i
=> 155
Can be cool for things like
/users/155-brian-hogan
#user = User.find_by_id(params[:id])
But not so cool for what you're doing.
So either change the col to a string and use the filter, or change the setter :)
Good luck!
The problem with doing it that way is that for a while, the non-normalized stuff will exist in the object; if you have code that works on the attributes before stuff gets normalised, then that will be a problem.
You could define a setter:
def thenumber=(value)
# normalise stuff here, call write_attribute
end
Unfortunately I think a lot of the Rails form stuff writes the attributes directly, which is one of the reasons I don't tend to use it.
Or you could normalise the params in the controller before you pass them through.
Does ruby let you interchange between a . and [''] ?
I don't know, I'll try later, but I think you are supposed to use .
self.thenumber = self.thenumber.to_s.gsub(',','')
You should return true from your before_validation method, otherwise if the expression being assigned to self['thenumber'] ends up being nil or false, the data will not be saved, per the Rails documention:
If a before_* callback returns false,
all the later callbacks and the
associated action are cancelled.
Ostensibly, you are trying to normalize here then check the result of the normalization with your Rails validations, which will decide if nil/false/blank are okay or not.
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
return true
end

Resources