I use money-rails gem in my rails application.
I have model Transaction.
class Transaction < ActiveRecord::Base
monetize :amount_money, as: :amount, with_model_currency: :currency, numericality: {greater_than_or_equal_to: 0}
end
And form for adding new transaction.
= simple_form_for [:post_manager, #transaction] do |f|
= f.label t('activerecord.attributes.transaction.amount')
= f.input :amount, class: 'form-control'
= f.submit t('views.transactions.submit.create'), class: 'btn btn-md btn-success'
In my controller action:
def create
#transaction = Transaction.new(transaction_params)
#transaction.save
respond_with(#transaction, location: post_manager_transactions_path)
end
In my money initializer:
MoneyRails.configure do |config|
config.register_currency = {
priority: 1,
iso_code: 'BYR',
name: 'Belarusian Ruble',
symbol: 'Br',
disambiguate_symbol: 'BYR',
subunit_to_unit: 1,
symbol_first: false,
decimal_mark: '.',
thousands_separator: ' ',
iso_numeric: '974',
smallest_denomination: 100
}
end
When I try to add new transaction:
In my controller action:
[1] pry(#<PostManager::TransactionsController>)> #transaction
=> #<Transaction:0x000001018ebfa0 id: nil, kind: "withdraw", amount_money: 12300, note: "vf", approved: nil, wallet_id: 1, category_id: 1, created_at: nil, updated_at: nil>
[2] pry(#<PostManager::TransactionsController>)> params
=> {"utf8"=>"✓",
"authenticity_token"=>"hAHFdamHK7CI41zXiHUCSb+RUg+57JR9sZTIhi2frcLEQELakQuOvhs8xaWMwK32XbxTsTfplCQJA7XigsueLQ==",
"transaction"=>{"kind"=>"withdraw", "category_id"=>"1", "amount"=>"123", "note"=>"vf"},
"commit"=>"Создать операцию",
"controller"=>"post_manager/transactions",
"action"=>"create"}
So. In my params :amount is 123, but in my new transaction :amount is 12300, so i have 2 extra zeros in my amount money field.
And I really don't know how to fix it. Maybe someone have had such problem before?
This is the expected behaviour of the money-rails gem. It will save the currency amount in it's lowest denomination in order to prevent rounding errors.
Since you have not specified the currency of the amount you're entering, it defaults to USD and converts it to cents, which is why you're seeing that result:
$123 * 100 = 12300
The gem is smart enough to convert currency amounts into their lowest denomination. However, it needs to know the currency it is working with, since not all currencies behave the same.
The way you approach this is dependent on your application logic. However, as a test you could add the following hidden field to your form that would yield the result you expect.
= f.input :amount_currency, as: :hidden, input_html: { value: "BYR" }
L
Related
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!
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
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/)
I have a string field in my databse
class CreateMHolidays < ActiveRecord::Migration
def change
create_table :m_holidays do |t|
t.string :open_schedule, :limit => 50
end
end
end
I am using time_select to get the value for open_schedule field.
<%= f.time_select :open_schedule, {minute_step: 01, include_blank: true,:default =>{:hour => '00', :minute => '00'},:ignore_date => true}, {:class => 'form-control'} %>
In my controller I try
#m_holidays = MHoliday.new(m_holiday_params)
#open_schedule_hrs = (params[:m_holidays]['open_schedule(4i)']).to_s
#open_schedule_mns = (params[:m_holidays]['open_schedule(5i)']).to_s
#m_holidays.open_schedule = #open_schedule_hrs + ':' + #open_schedule_mns
But when I try to save the record I am getting
ActiveRecord::MultiparameterAssignmentErrors (1 error(s) on assignment
of multiparameter attributes [error on assignment [3, 3] to
open_schedule (Missing Parameter - open_schedule(1))])
This is the first time I am using time_select and I must use it with a string field rather than :time. How to go about this? Any help much appreciated
You're getting the ActiveRecord::MultiparameterAssignmentErrors because of the mass parameter assignment on the line #m_holidays = MHoliday.new(m_holiday_params). This might be due to m_holiday_params containing parameters that your MHoliday model doesn't know what to do with.
Try filtering out everything related to the open_schedule input from m_holiday_params. If you have an m_holiday_params method like this:
def m_holiday_params
params.require(:m_holiday).permit('open_schedule(4i)', 'open_schedule(5i)', ...)
end
then omit the open_schedule parameters:
def m_holiday_params
params.require(:m_holiday).permit(...)
end
Then you can manually set up your open_schedule string, as you've already done.
I am using the Money gem and composed_of as per this answer: https://stackoverflow.com/a/3843805/4162458
I have an integer column in my database, :discount
product.rb
class Product < ActiveRecord::Base
composed_of :discount_display,
:class_name => 'Money',
:mapping => %w(discount cents),
:converter => Proc.new { |value| Money.new(value) }
And in my form:
<%= f.number_field :discount_display, class: 'form-control', min: "0" %>
However, if I enter 12, it saves in the database as 12 and displays on the form when I refresh as 0.12.
How can I have it so that when you enter "12" it is saved as 1200 in the db and displays properly, as that answers seems to say should happen?
As per documentation,
Represents monetary values as integers, in cents. This avoids floating
point rounding errors.
Hence, if you expect collect dollars from the user, then, you need to convert it to cents before saving in DB. Perhaps, your converter proc should be
Proc.new { |value| Money.new(value * 100) }
or
Proc.new { |value| Money.from_amount(value, "USD") }
The first time I try to submit the form I get the error saying
"Price is not a valid number"
It's OK the second time I try to submit it (with the same valid data in :price field).
If I don't add validation in the model, then the form is submitted, but value of price is not saved.
What could be going on? Is there something special about .decimal field?
db schema:
t.decimal "price"
model
validates :price, numericality: { :greater_than => 0, :less_than_or_equal_to => 100000000 }
form view file
<%= f.number_field :price, class: "short red" %>
controller
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
if #product.save
redirect_to #product
else
render :new
end
end
private
def product_params
params.require(:product).permit(:name, :description, :image, :price, :user_id)
end
logs
Started POST "/products" for xxx.132 at 2014-10-15 22:56:51 +0000
Processing by ProductsController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"abte/LtO0T/ZtSXQIuXVVjjUvwHw5jDUJ1yIKCOWRx2=",
"product"=>{"name"=>"", "description"=>"", "user_id"
=>"1"}, "commit"=>"Submit"}
Some things you can check:
The snippet from your form starts f.number_field. Check that you are using something like <%= form_for(#product) do |f| %> at the top of the form.
Try to create a product using the rails console.
In the rails console, try something like this:
> p = Product.new
> p.valid?
#=> TRUE or FALSE should appear
> p.errors.full_messages.to_sentence
# you should see a full list of all failed validations from your Product model
If these don't help, try pasting in the entire product_controller.rb and _form.html.erb files into your question, and I'll take a look again.
Try to change your migration to:
t.decimal :price, precision: 8, scale: 2 #for example
Then, change validation to:
validates :price, numericality: {greater_than_or_equal_to: 0.01, :less_than_or_equal_to => 100000000 }
In PostgreSQL next implementations behavior with :decimal columns:
PostgreSQL: :precision [1..infinity], :scale [0..infinity]. No
default.
I hope, this example from "Agile Web Development with Rails 4" help you to understand validation of decimal numbers:
it’s possible to enter a number such as 0.001 into this field. Because
the database stores just two digits after the decimal point, this
would end up being zero in the database, even though it would pass the
validation if we compared against zero. Checking that the number is at
least 1 cent ensures only correct values end up being stored.
I am using the Money gem for handling transaction amounts. I'd like to use different currencies for different transactions (but without conversions). The default currency is set in the money.rb:
config.default_currency = :usd
Even though I can set different currencies when creating a Transaction, the view always shows the amount in US dollars. For example this 12.00 transaction with RUB as currency:
<Transaction id: 100, amount_cents: 1200, currency: "RUB", created_at: "2013-12-11 09:32:52", updated_at: "2013-12-11 09:32:52">
Is being shown as a USD transaction in my views.
<% transactions.each do |transaction| %>
<%= transaction.amount_cents.to_money.format %>
...
=> $12.00
Here's the code from my Transaction.rb (just in case I'm missing something)
composed_of :amount_cents,
class_name: 'Money',
mapping: %w(amount_cents cents),
converter: Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : Money.empty }
monetize :amount_cents, as: "amount"
Any ideas on how to override the default? I'll be thankful for any advice.
Resolved the issue using amount instead of amount_cents.