Money-Rails is discarding price amount after decimal point

1.7k Views Asked by At

I am building and Rails 5 API where I am trying to send money amount and store it in PostgresQL database. I am sending amount 2.4 but I see in database only 2 is stored. what I am doing wrong?

my migration:

class CreateTransactions < ActiveRecord::Migration[5.1]
  def change
  create_table :transactions do |t|
   t.monetize :transaction_price, amount: { null: true, default: nil }
   t.timestamps
  end
 end
end

my model is:

class Transaction < ApplicationRecord
  monetize :transaction_price_cents
end

my controller:

class TransactionsController < ApiController
      def create
        transaction = Transaction.new(transaction_param)
        if transaction.save
          render json: { status: 'SUCCESS', data:transaction }, status: :ok
        end
      end

      private
      def transaction_param
        params.require(:transaction).permit(:transaction_price_cents)
      end
end

I am sending this json with postman:

{
    "transaction_price_cents": 345.23
}

What I am getting in response:

{
    "status": "SUCCESS",
    "data": {
        "id": 1,
        "transaction_price_cents": 345,
        "transaction_price_currency": "USD",
    }
}

I either want 345.23 or 34523 but its giving me only 345!

2

There are 2 best solutions below

8
Simon Franzen On

Your price in cents! And that's ok!

Handling money in cents is a common pattern. It will also save your life when it comes to rounding errors with taxes or currency exchange. Like in their docs mentioned you should use a helper to output the price in a human readable form:

humanized_money @money_object                       # => 6.50
humanized_money_with_symbol @money_object           # => $6.50
money_without_cents_and_with_symbol @money_object   # => $6

If you accessing the data via an API you could add a human_readable field in your api

def transaction_price_human_readable
  return humanized_money_with_symbol(@money_object) # or self or...
end

Save/Create model: If you get a floating number you could change the floating point into cents before_save

before_save :convert_transaction_price
def convert_transaction_price
   self.transaction_price = (self.transaction_price * 100).to_i
end
0
xpnimi On

I had the same problem.
(EDITED NEW AND CORRECT ANSWER):

All I had to do was to use the provided attribute from the money-rails gem. In my case I had an amount_cents attribute, and I had to use the provided :amount attribute in the form.

<%= f.label :amount %>
<%= f.text_field :amount %>

NOTE: I converted the value of :amount_cents to a float string in the edit.html.erb as followed:

<%= f.label :amount %>
<%= f.text_field :amount, value: number_with_precision(f.object.amount_cents / 100, precision: 2).gsub(/\./, ',') %>

(also note that I had configured money-rails to use EUROs which use "," as delimiter, thats why i have to use .gsbu(/\./, ','))

And here ist the IMPORTANT PART, I had to update my strong_parameters in the controller to permit :amount, and not :amount_cents

    private

        def invoice_params
            params.require(:invoice).permit(…, :amount, …)
        end     

--

(OLD ANSWER):
I came to the conclusion that it is best to change the input value directly in the Frontend to cents. And then send the cents to the backend.

Here is a nice Stimulus Controller which does exactly that: https://gist.github.com/psergi/72f99b792a967525ffe2e319cf746101
(You may need to update that gist to your liking, and also it expects that you use Stimulus in your rails project)

(I leave the old answer in here, because I think it is a good practice to send _cents from the frontend to the backend, but in the moment it is not necessary [for me]. If you want to support more than one currency, you probably want to do it like that and use a .js framework to handle the input conversion -> s.th. like http://autonumeric.org/)