可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I\'m working on a very basic shopping cart system.
I have a table items
that has a column price
of type integer
.
I\'m having trouble displaying the price value in my views for prices that include both Euros and cents. Am I missing something obvious as far as handling currency in the Rails framework is concerned?
回答1:
You\'ll probably want to use a DECIMAL
type in your database. In your migration, do something like this:
# precision is the total number of digits
# scale is the number of digits to the right of the decimal point
add_column :items, :price, :decimal, :precision => 8, :scale => 2
In Rails, the :decimal
type is returned as BigDecimal
, which is great for price calculation.
If you insist on using integers, you will have to manually convert to and from BigDecimal
s everywhere, which will probably just become a pain.
As pointed out by mcl, to print the price, use:
number_to_currency(price, :unit => \"€\")
#=> €1,234.01
回答2:
Here\'s a fine, simple approach that leverages composed_of
(part of ActiveRecord, using the ValueObject pattern) and the Money gem
You\'ll need
- The Money gem (version 4.1.0)
- A model, for example
Product
- An
integer
column in your model (and database), for example :price
Write this in your product.rb
file:
class Product > ActiveRecord::Base
composed_of :price,
:class_name => \'Money\',
:mapping => %w(price cents),
:converter => Proc.new { |value| Money.new(value) }
# ...
What you\'ll get:
- Without any extra changes, all of your forms will show dollars and cents, but the internal representation is still just cents. The forms will accept values like \"$12,034.95\" and convert it for you. There\'s no need to add extra handlers or attributes to your model, or helpers in your view.
product.price = \"$12.00\"
automatically converts to the Money class
product.price.to_s
displays a decimal formatted number (\"1234.00\")
product.price.format
displays a properly formatted string for the currency
- If you need to send cents (to a payment gateway that wants pennies),
product.price.cents.to_s
- Currency conversion for free
回答3:
Common practice for handling currency is to use decimal type.
Here is a simple example from \"Agile Web Development with Rails\"
add_column :products, :price, :decimal, :precision => 8, :scale => 2
This will allow you to handle prices from -999,999.99 to 999,999.99
You may also want to include a validation in your items like
def validate
errors.add(:price, \"should be at least 0.01\") if price.nil? || price < 0.01
end
to sanity-check your values.
回答4:
Use money-rails gem. It nicely handles money and currencies in your model and also has a bunch of helpers to format your prices.
回答5:
Using Virtual Attributes (Link to revised(paid) Railscast) you can store your price_in_cents in an integer column and add a virtual attribute price_in_dollars in your product model as a getter and setter.
# Add a price_in_cents integer column
$ rails g migration add_price_in_cents_to_products price_in_cents:integer
# Use virtual attributes in your Product model
# app/models/product.rb
def price_in_dollars
price_in_cents.to_d/100 if price_in_cents
end
def price_in_dollars=(dollars)
self.price_in_cents = dollars.to_d*100 if dollars.present?
end
Source: RailsCasts #016: Virtual Attributes: Virtual attributes are a clean way to add form fields that do not map directly to the database. Here I show how to handle validations, associations, and more.
回答6:
If you are using Postgres (and since we\'re in 2017 now) you might want to give their :money
column type a try.
add_column :products, :price, :money, default: 0
回答7:
Definitely integers.
And even though BigDecimal technically exists 1.5
will still give you a pure Float in Ruby.
回答8:
If someone is using Sequel the migration would look something like:
add_column :products, :price, \"decimal(8,2)\"
somehow Sequel ignores :precision and :scale
(Sequel Version: sequel (3.39.0, 3.38.0))
回答9:
I am using it on this way:
number_to_currency(amount, unit: \'€\', precision: 2, format: \"%u %n\")
Of course that the currency symbol, precision, format and so on depends on each currency.
回答10:
You can pass some options to number_to_currency
(a standard Rails 4 view helper):
number_to_currency(12.0, :precision => 2)
# => \"$12.00\"
As posted by Dylan Markow
回答11:
My underlying APIs were all using cents to represent money, and I didn\'t want to change that. Nor was I working with large amounts of money. So I just put this in a helper method:
sprintf(\"%03d\", amount).insert(-3, \".\")
That converts the integer to a string with at least three digits (adding leading zeroes if necessary), then inserts a decimal point before the last two digits, never using a Float
. From there you can add whatever currency symbols are appropriate for your use case.
It\'s definitely quick and dirty, but sometimes that\'s just fine!
回答12:
Simple code for Ruby & Rails
<%= number_to_currency(1234567890.50) %>
OUT PUT => $1,234,567,890.50
回答13:
Just a little update and a cohesion of all the answers for some aspiring juniors/beginners in RoR development that will surely come here for some explanations.
Working with money
Use :decimal
to store money in the DB, as @molf suggested (and what my company uses as a golden standard when working with money).
# precision is the total number of digits
# scale is the number of digits to the right of the decimal point
add_column :items, :price, :decimal, precision: 8, scale: 2
Few points:
:decimal
is going to be used as BigDecimal
which solves a lot of issues.
precision
and scale
should be adjusted, depending on what you are representing
If you work with receiving and sending payments, precision: 8
and scale: 2
gives you 999,999.99
as the highest amount, which is fine in 90% of cases.
If you need to represent the value of a property or a rare car, you should use a higher precision
.
If you work with coordinates (longitude and latitude), you will surely need a higher scale
.
How to generate a migration
To generate the migration with the above content, run in terminal:
bin/rails g migration AddPriceToItems price:decimal{8-2}
or
bin/rails g migration AddPriceToItems \'price:decimal{5,2}\'
as explained in this blog post.
Currency formatting
KISS the extra libraries goodbye and use built-in helpers. Use number_to_currency
as @molf and @facundofarias suggested.
To play with number_to_currency
helper in Rails console, send a call to the ActiveSupport
\'s NumberHelper
class in order to access the helper.
For example:
ActiveSupport::NumberHelper.number_to_currency(2_500_000.61, unit: \'€\', precision: 2, separator: \',\', delimiter: \'\', format: \"%n%u\")
gives the following output
2500000,61€
Check the other options
of number_to_currency helper.
Where to put it
You can put it in an application helper and use it inside views for any amount.
module ApplicationHelper
def format_currency(amount)
number_to_currency(amount, unit: \'€\', precision: 2, separator: \',\', delimiter: \'\', format: \"%n%u\")
end
end
Or you can put it in the Item
model as an instance method, and call it where you need to format the price (in views or helpers).
class Item < ActiveRecord::Base
def format_price
number_to_currency(price, unit: \'€\', precision: 2, separator: \',\', delimiter: \'\', format: \"%n%u\")
end
end
And, an example how I use the number_to_currency
inside a contrroler (notice the negative_format
option, used to represent refunds)
def refund_information
amount_formatted =
ActionController::Base.helpers.number_to_currency(@refund.amount, negative_format: \'(%u%n)\')
{
# ...
amount_formatted: amount_formatted,
# ...
}
end