yellowreign
yellowreign

Reputation: 3638

Rails Amounts in Thousands Are Truncated

In my Rails 5 app, I read in a feed for products. In the JSON, when the price is over $1,000, it the JSON has a comma, like 1,000.

My code seems to be truncating it, so it's storing as 1 instead of 1,000.

All other fields are storing correctly. Can someone please tell me what I'm doing wrong?

In this example, the reg_price saves as 2, instead of 2590.

json sample (for reg_price field):

[
  {
    "reg_price": "2,590"
  }
]

schema

create_table "products", force: :cascade do |t|
  t.decimal  "reg_price",                           precision: 10, scale: 2
end

model

response = open_url(url_string).to_s
products = JSON.parse(response)

products.each do |product|
  product = Product.new(
    reg_price: item['reg_price']
  )
  product.save
end

Upvotes: 1

Views: 155

Answers (2)

coreyward
coreyward

Reputation: 80090

The reason this is happening has nothing to do with Rails.

  1. JSON is a pretty simple document structure and doesn't have any support for number separators. The values in your JSON document are strings.
  2. When you receive a String as input and you want to store it as an Integer, you need to cast it to the appropriate type.
  3. Ruby has built in support for this, and Rails is using it: "1".to_s #=> 1
  4. The particular heuristic Ruby uses to convert a string to an integer is to take any number up to a non-numerical character and cast it as an integer. Commas are non-numeric, at least by default, in Ruby.

The solution is to convert the string value in your JSON to an integer using another method. You can do this any of these ways:

  1. Cast the string to an integer before sending it to your ActiveRecord model.
  2. Alter the string in such a way that the default Ruby casting will cast the string into the expected value.
  3. Use a custom caster to handle the casting for this particular attribute (inside of ActiveRecord and ActiveModel).

The solution proposed by @Danil follows #2 above, and it has some shortcomings (as @tadman pointed out).

A more robust way of handling this without getting down in the mud is to use a library like Delocalize, which will automatically handle numeric string parsing and casting with consideration for separators used by the active locale. See this excellent answer by Benoit Garret for more information.

Upvotes: 2

Danil Speransky
Danil Speransky

Reputation: 30463

You are not doing anything wrong. Decimals don't work with comma separator. I'm not sure there is a nice way to fix the thing. But as an option you could define a virtual attribute:

def reg_price=(reg_price)
  self[:reg_price] = reg_price.gsub(',', '')
end

Upvotes: 2

Related Questions