Storing Prices with Cents in Rails - ruby-on-rails

Ok I have a Stripe charge to which I am applying taxes. Stripe takes in a number as cents, so it leaves you with a number like 10015 instead of 100.15.
In my controller, I am sending the number to ActiveRecord as 10015/100.0
When I retrieve it, it gives me #<BigDecimal:7fca81f71130,'0.1243E3',18(27)>>
Whats going on ?
I tried
rails g migration add_expense_to_user expense:integer
and
rails g migration add_expense_to_user expense:decimal
to whose migration I added
add_column :user, :expense, :decimal, precision: 6, scale: 2
which is the current setup.
How do I store / retrieve the value if it is stored as 10015/100

The BigDecimal is just the type that Rails uses for decimal types in DBs. 0.1243E3 is scientific notation, short for 0.1243 x 10³ - ie. 124.3
You can get a regular float from it by just using the .to_f method in Ruby, or you can pass a BigDecimal into other Rails helpers, like number_to_currency(the_big_decimal) will produce "$124.30"
So, in other words, with the BigDecimal you probably already have what you're asking for in this question.

When you access the data, you need to call .to_f.
In irb:
a = BigDecimal.new(5, 2)
a
=> #<BigDecimal:1ad7c98,'0.5E1',9(27)>
a.to_f
=> 5.0

The value is stored inside the BigDecimal object with arbitrary precision. The "common" representation of non-integer values (i.e. floats) however doesn't provide this precision. Thus, BigDecimal provides various options to convert its value to other types.
You can e.g use expense.to_f to get a floating point representation of the BigDecimal value (and thus loosing the precision along the way). Alternatively, if you just want to print the value, you could use one of the to_s method to format the value as a string:
expense.to_s
# => "124.3"
See the BigDecimal documentation for details.

Related

Rails console shows value in scientific notation

I have model named Dish which has an attribute cost of float type. I've set precision to 15 and scale to 2. The problem is in rails console, the value for cost is displayed in scientific notation.
cost: 0.102e2
How to display in decimals?
you can use to_f for simple format, but for more options you can also use NumberHelper module, you can format with many format from this helper module , here is reference
from your console:
include ActionView::Helpers::NumberHelper
number_with_precision(cost, precision: 2)
To prevent ActiveRecord from displaying decimal values in scientific notation, you can set the :float column type when defining your model's attributes. This will cause ActiveRecord to display decimal values in float format rather than scientific notation.
For example, you might define your model's attributes like this:
class MyModel < ApplicationRecord
attribute :my_decimal_value, :float
end
This will cause ActiveRecord to display the my_decimal_value attribute in float format rather than scientific notation.

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'.

Rails is messing up BigDecimals by throwing in Floating Point Logic

I'm playing around with rails a bit and I have found something strange. For storing a money value I use the typical decimal data type which active record converts to BigDecimal. I considered this to be precise and I thought to avoid the odd behavior of floating point math. But when I store 99.99 to the db everything works fine, but when the records gets loaded by active record it loses precision and converts to something like 99.9899999999. This looks like a floating point issue.
I made some tests and found out that creating a BigDecimal like this b = BigDecimal.new("99.99") leads to a "clean" variable but building it this way b = BigDecimal.new(99.99) leads to the "unclean" version that I want to avoid.
I guess, that ActiveRecord reconstructs the BigDecimal with an intermediate float when loading the record from the database. This is not what I want and I would like to know if it can be avoided.
Ruby Version 1.9.3p0
Rails 3.2.9
Sqlite 3.7.9
Your problem is that you're using SQLite and SQLite doesn't have native support for numeric(m,n) data types. From the fine manual:
1.0 Storage Classes and Datatypes
Each value stored in an SQLite database (or manipulated by the database engine) has one of the following storage classes:
NULL. The value is a NULL value.
INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number.
TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
BLOB. The value is a blob of data, stored exactly as it was input.
Read further down that page to see how SQLite's type system works.
Your 99.99 may be BigDecimal.new('99.99') in your Ruby code but it is almost certainly the REAL value 99.99 (i.e. an eight byte IEEE floating point value) inside SQLite and there goes the neighborhood.
So switch to a better database in your development environment; in particular, develop on top of whatever database you're going to be deploying on.
Don't use floating point for monetary values
Yes, exactly, SQLite is messing up your BigDecimal values.
The fundamental problem is that the FP format cannot store most decimal fractions correctly.
I believe you have about four choices:
Round everything to, say, two decimal places so that you never notice the slightly-off values.
Store your BigDecimal values in SQLite with the TEXT or BLOB storage class.
Use a different db that has some sort of decimal string support.
Scale everything to integral values and use the INTEGER storage class.
The problem is that FP fractions are rational numbers of the form x/2n. But the decimal monetary amounts have fractions that are x/(2n * 5m). The representations just aren't compatible. For example, in 0.01 ... 0.99, only 0.25, 0.50, and 0.75 have exact binary representations.

Float Value being Truncated in RoR 3.2

When saving my longtitude and latitude in my Rails 3.2 app, the value's getting truncated on save.
I've tried in the console and it's saving the full value:
item.update_attributes(:latitude => '51.07763839854951')
item.latitude:
=> 51.07763839854951
Saving the same value in the browser gives me an output of:
51.0865174
What can I do to prevent this?
The value is probably being truncated when it is stored in the DB. Regardless of the exact cause, if this value is precise and needs to be stored and retrieved losslessly, a floating-point number is probably the wrong data type to use. Floating point numbers can lose precision when you perform certain arithmetic operations on them, so they are not appropriate for values which must be exact.
When you are defining your DB schema using Rails migrations, you can use the :decimal type for decimal values which must be stored precisely. (When ActiveRecord pulls these values out of the DB, they will become BigDecimal objects rather than Floats You can do arithmetic on BigDecimals without losing precision.)

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