jedi
jedi

Reputation: 2198

Rails decimal returned as string in API

I have a field called price in my db which is a decimal field and in my JSON API response it is returned as a String.

I wanted to apply some validations to the field that would allow max 2 digits after period and found out that if I use decimal field I can then apply the precision on DB level.

t.decimal "price", precision: 10, scale: 2

Then I want to calculate total price in a service object:

services/statistics/monthly_rides_generator.rb

class CurrentMonthRidesGenerator
    def initialize(current_user)
      @current_user = current_user
    end

    def call
      user_current_month_rides_by_day.map do |date, rides_on_day|
        {
          day: formatted_date(date),
          total_distance: total_distance(rides_on_day),
          avg_ride: avg_ride(rides_on_day),
          avg_price: avg_price(rides_on_day),
          total_price: total_price(rides_on_day)
        }
      end
    end

    ...

    def total_price(rides)
      rides.map(&:price).sum
    end
  end

app/api/statistics/stats_api.rb

get '/current_month' do
  Statistics::CurrentMonthRidesGenerator.new(current_user).call
end

but in API response this field is a String.

{
    "day": "November, 8th",
    "total_distance": "9km",
    "avg_ride": "9km",
    "avg_price": "100.0PLN",
    "total_price": "100.0"
}

I want this field to be returned as it was saved because I need a float/decimal number in the front end to then do other calculations.

Why is it returning a String when it is a decimal field? How can I fix it?

Upvotes: 1

Views: 3197

Answers (3)

jedi
jedi

Reputation: 2198

I decided to solve this problem by changing the column type to Float, renaming it to price_cents, adding money-rails and monetizing the column. Then I just call to_f or round the value right before displaying it or sending it to front-end in API response.

Upvotes: 0

Jimmy Baker
Jimmy Baker

Reputation: 3255

It's returning as a string because Decimal/BigDecimal in ruby is a very precise number. Javascript/JSON doesn't have a data type that is as precise as ruby's BigDecimal.

You can easily see this in your browser if you open up the console: enter image description here

If you convert the value to a float in your serializer then it will be a float in the JSON object that is returned by your API, but you'll want to be careful of how your clients use this data. If they don't use a library that can handle precision then you're going to get rounding errors and often be off on your calculations by a penny.

I've been using the decimal.js library for things like this and it works out great. https://github.com/MikeMcl/decimal.js/

Upvotes: 6

Troy Carlson
Troy Carlson

Reputation: 3121

One way to solve this is to cast it to the desired type in a serializer:

# app/serializers/your_model_serializer.rb

class YourModelSerializer < ActiveModel::Serializer
  attributes :day,
             :total_distance,
             :avg_ride,
             :avg_price,
             :total_price

  def total_price
    object.total_price.to_f
  end
end

Upvotes: 2

Related Questions