1

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!

naqib83
  • 128
  • 3
  • 14
  • Shouldn't the ammount be 100 times greater if you want to do the logic in dollars? It's even in the name associated to the value `transaction_price_cent`. – neznidalibor Mar 07 '18 at 19:39
  • I din get exactly. Did you mean I have to multiply the price with 100 (I mean make it cents) before sending it to database? I thought the Gem will do that for me. – naqib83 Mar 07 '18 at 19:49

2 Answers2

1

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
Simon Franzen
  • 2,628
  • 25
  • 34
  • so, when I am sending price, I should be sending in cents? like if I have $345.23 then I have to send it like 43523? is that what you meant? – naqib83 Mar 07 '18 at 21:26
  • Yes, you also have to save the price in cents. you can do this in a before_save action – Simon Franzen Mar 07 '18 at 21:35
  • You should also check your migration. In the docs they say something about "*_cents" in the attribute name... – Simon Franzen Mar 07 '18 at 21:39
  • Hi, I have tried this `before_save :convert_transaction_price def convert_transaction_price self.transaction_price = (self.transaction_price * 100).to_i end` But it still showing me wrong value. Now, it shows me 23400 instead of 23423. am I missing anything? – naqib83 Mar 07 '18 at 23:09
  • Actually I am having exactly this problem: https://github.com/RubyMoney/money-rails/issues/396 Do you have any solution for that? – naqib83 Mar 08 '18 at 02:17
  • Ok remove the before_save . Are you getting the correct value in cents? – Simon Franzen Mar 08 '18 at 02:40
  • Let's hope the authors of the plugin will help – Simon Franzen Mar 08 '18 at 02:40
  • It doesn't matter even if I remove the before_save . – naqib83 Mar 08 '18 at 15:12
0

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/)

xpnimi
  • 167
  • 1
  • 1
  • 13