Setting default precision for all decimal numbers in rails - ruby-on-rails

Is there a way to force Rails to output all decimal numbers with a certain precision? All of the decimal fields in my database are currency amounts, so I'd like decimal numbers to show by default with a precision of 2 (i.e. 2.40). I know I can use a helper function like "number_to_currency" to do this to each individual number, but that seems a bit tedious and unnecessary.

If you're concerned about overriding "to_s" on Float having possible unforeseen side-effects, your next best bet is probably to just create a new method, but still as a core extension. Something like this:
class Float
def c
sprintf('%.2f', self)
end
end
Then can't have any unforeseen consequences, and then anywhere you'd want to display the number with two decimal places, you'd just call .c. For example:
message = "The account balance is $#{amount.c}."
Not automatic, but not much extra typing, and no possible side-effects that overriding to_s could potentially cause.

Well if you don't mind monkey patching ruby, you can add a something like this (put it in an initializer at "/config/initializers/core_extensions.rb"):
class Float
def to_s
sprintf('%.2f', self)
end
end

Related

Rails: Storing BigDecimal in Database

I am handling a bunch of BigDecimals, which I want to store to my database. Ideally without any loss of accuracy. I don't know the nicest way to achieve that.
This came to my mind:
t.decimal :value, precision: 1000, scale: 30
It does not look like a good way to approach this problem. 1. it still compromises accuracy. 2. It's is getting unnecessarily large.
Is there a way to store the object, e.g.: #<BigDecimal:586a238,'0.563E0',9(36)> to the database (within a text column) and then re-initialize it as a BigDecimal?
You might want to look into composed_of.
But I prefer using custom getter and setter methods because I thing they are easier to read and to understand.
Example: Imagine your attribute is named foo and you want to use BigDecimal in the app but store the value as a string in the database:
def foo
BigDecimal.new(read_attribute(:foo))
end
def foo=(foo)
write_attribute(:foo, foo.to_s)
end
By default a PostgreSQL Decimal has a range of "up to 131072 digits before the decimal point; up to 16383 digits after the decimal point". Is that not enough?
https://www.postgresql.org/docs/9.1/static/datatype-numeric.html
Just use:
t.decimal :value

Rails converting string with float value to integer

I have a model that has an attribute named value this is saved in the db as an integer.
I input the number in the view using a text_field, I am also performing a method on it :before_save that takes the value (something like 21.37) and using the money gem convert it to just cents.
However, it seems that before I can even perform the method that converts it from a float to an integer it is being converted to some kind of integer and the decimal is being lost.
I have tested this by outputting value in the method that runs before_save: and it round it to 21
Does anyone know why this might be happening, Im not performing any other changes to it.
I'm guessing you're doing something like Coupon.new(params) in your controller, and since Rails knows Coupon's value should be an integer, it helpfully calls to_i on params[:value] for you under the covers, which turns 21.37 into 21. Here's a rather inelegant but effective fix:
params[:value] ~= /.*\.(\d+)/
params[:value] = params[:value].to_f * (10**$1.length) if $1
Do that before you do Coupon.new(params).
Another option is to simply assume you don't care about anything after the 2nd decimal place (you're dealing with cents after all). Then you can simply do params[:value] = params[:value].to_f.round(2) * 100.
Also you should consider using number_field instead of text_field in your view so you can at least be sure you're getting a number. Something like number_field(:coupon, :value, step: 0.01) should ensure your users can enter decimals.
I agree with #kitkat seems like something that would happen in the model layer. You might be able to implement your conversion to cents logic in 'before_validation' or perhaps a custom setter for 'coupon'.

Price fields and cents separator

In my application I have several price fields. In the model they are of type float and when I want to display them, I use the number_to_currency method provided by NumberHelper and this works fine. However, the input in the form is a problem. Here in The Netherlands, the divider for decimal amounts is a comma, but some people use a dot. Currently, only a decimal amount with a dot gets saved properly. When a decimal is used, only the whole amount gets saved.
So I probably need a before_save sanitizer which replaces a comma with a dot. But I need this for several models. What is the best way to do this?
As the api documentation describes the method number_to_currency has the option :separator you may use.
number_to_currency("1234567890,50", unit: "£", separator: ",", delimiter: "")
Maybe you can set the locale and get the same result.
I have that problem in Germany, too.
I've been using the delocalize gem to perform the "reverse localization", although it still has some problems with Rails 4. (It works, though)
PS: Don't use floating point types for representing currency, you'll get rounding problems. Use BigDecimal instead.

Control ActiveRecord creation argument type and characteristics

I have a model with column amount which is a decimal in the database. I'd like to ensure that only a BigDecimal with certain precision is ever given when this model is instantiated. I've written specs to test the scenario when a Float is provided, and then I have a before_create callback that raises an error if it's not a BigDecimal.
However, by the time the value gets to the before_create callback, Rails has already converted it to a BigDecimal. This is nice I supposed, and I can probably still check for precision, but since I don't know precisely how rails goes about converting, it would be nice to check for proper argument type and precision further up the chain.
Is there any way to do this?
From http://api.rubyonrails.org/classes/ActiveRecord/Base.html
Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first. That can be done by using the _before_type_cast accessors that all attributes have. For example, if your Account model has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
Try to override amount=
def amount=(val)
# checking / conversion goes here, new_val as result
self[:amount] = new_val # don't use self.amount = new_val to avoid endless loop
end

floating point precision in ruby on rails model validations

I am trying to validate a dollar amount using a regex:
^[0-9]+\.[0-9]{2}$
This works fine, but whenever a user submits the form and the dollar amount ends in 0(zero), ruby(or rails?) chops the 0 off.
So 500.00 turns into 500.0 thus failing the regex validation.
Is there any way to make ruby/rails keep the format entered by the user, regardless of trailing zeros?
I presume your dollar amount is of decimal type. So, any value user enters in the field is being cast from string to appropriate type before saving to the database. Validation applies to the values already converted to numeric types, so regex is not really a suitable validation filter in your case.
You have couple of possibilities to solve this, though:
Use validates_numericality_of. That way you leave the conversion completely to Rails, and just check whether the amount is within a given range.
Use validate_each method and code your validation logic yourself (e.g. check whether the value has more than 2 decimal digits).
Validate the attribute before it's been typecasted:
This is especially useful in
validation situations where the user
might supply a string for an integer
field and you want to display the
original string back in an error
message. Accessing the attribute
normally would typecast the string to
0, which isn‘t what you want.
So, in your case, you should be able to use:
validates_format_of :amount_before_type_cast, :with => /^[0-9]+\.[0-9]{2}$/, :message => "must contain dollars and cents, seperated by a period"
Note, however, that users might find it tedious to follow your rigid entry rules (I would really prefer being able to type 500 instead 500.00, for example), and that in some locales period is not a decimal separator (if you ever plan to internationalize your app).
In general if you wish to “remember” the decimal precision of a floating point value, you should use a decimal type, not a binary float.
On the other hand, I'm not certain why you would wish to force the string representation in such a strict manner… How about accepting any number and formatting it with e.g. number_to_currency?
Usually with money it's best to store it as an integer in cents (500 cents is $5.00). I use the Money gem to handle this.

Resources